summaryrefslogtreecommitdiff
path: root/sound/oss/pas2_pcm.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/oss/pas2_pcm.c')
-rw-r--r--sound/oss/pas2_pcm.c437
1 files changed, 437 insertions, 0 deletions
diff --git a/sound/oss/pas2_pcm.c b/sound/oss/pas2_pcm.c
new file mode 100644
index 000000000000..4af6aafa3d86
--- /dev/null
+++ b/sound/oss/pas2_pcm.c
@@ -0,0 +1,437 @@
+/*
+ * pas2_pcm.c Audio routines for PAS16
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ *
+ * Thomas Sailer : ioctl code reworked (vmalloc/vfree removed)
+ * Alan Cox : Swatted a double allocation of device bug. Made a few
+ * more things module options.
+ * Bartlomiej Zolnierkiewicz : Added __init to pas_pcm_init()
+ */
+
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <asm/timex.h>
+#include "sound_config.h"
+
+#include "pas2.h"
+
+#ifndef DEB
+#define DEB(WHAT)
+#endif
+
+#define PAS_PCM_INTRBITS (0x08)
+/*
+ * Sample buffer timer interrupt enable
+ */
+
+#define PCM_NON 0
+#define PCM_DAC 1
+#define PCM_ADC 2
+
+static unsigned long pcm_speed; /* sampling rate */
+static unsigned char pcm_channels = 1; /* channels (1 or 2) */
+static unsigned char pcm_bits = 8; /* bits/sample (8 or 16) */
+static unsigned char pcm_filter; /* filter FLAG */
+static unsigned char pcm_mode = PCM_NON;
+static unsigned long pcm_count;
+static unsigned short pcm_bitsok = 8; /* mask of OK bits */
+static int pcm_busy;
+int pas_audiodev = -1;
+static int open_mode;
+
+extern spinlock_t pas_lock;
+
+static int pcm_set_speed(int arg)
+{
+ int foo, tmp;
+ unsigned long flags;
+
+ if (arg == 0)
+ return pcm_speed;
+
+ if (arg > 44100)
+ arg = 44100;
+ if (arg < 5000)
+ arg = 5000;
+
+ if (pcm_channels & 2)
+ {
+ foo = ((CLOCK_TICK_RATE / 2) + (arg / 2)) / arg;
+ arg = ((CLOCK_TICK_RATE / 2) + (foo / 2)) / foo;
+ }
+ else
+ {
+ foo = (CLOCK_TICK_RATE + (arg / 2)) / arg;
+ arg = (CLOCK_TICK_RATE + (foo / 2)) / foo;
+ }
+
+ pcm_speed = arg;
+
+ tmp = pas_read(0x0B8A);
+
+ /*
+ * Set anti-aliasing filters according to sample rate. You really *NEED*
+ * to enable this feature for all normal recording unless you want to
+ * experiment with aliasing effects.
+ * These filters apply to the selected "recording" source.
+ * I (pfw) don't know the encoding of these 5 bits. The values shown
+ * come from the SDK found on ftp.uwp.edu:/pub/msdos/proaudio/.
+ *
+ * I cleared bit 5 of these values, since that bit controls the master
+ * mute flag. (Olav Wölfelschneider)
+ *
+ */
+#if !defined NO_AUTO_FILTER_SET
+ tmp &= 0xe0;
+ if (pcm_speed >= 2 * 17897)
+ tmp |= 0x01;
+ else if (pcm_speed >= 2 * 15909)
+ tmp |= 0x02;
+ else if (pcm_speed >= 2 * 11931)
+ tmp |= 0x09;
+ else if (pcm_speed >= 2 * 8948)
+ tmp |= 0x11;
+ else if (pcm_speed >= 2 * 5965)
+ tmp |= 0x19;
+ else if (pcm_speed >= 2 * 2982)
+ tmp |= 0x04;
+ pcm_filter = tmp;
+#endif
+
+ spin_lock_irqsave(&pas_lock, flags);
+
+ pas_write(tmp & ~(0x40 | 0x80), 0x0B8A);
+ pas_write(0x00 | 0x30 | 0x04, 0x138B);
+ pas_write(foo & 0xff, 0x1388);
+ pas_write((foo >> 8) & 0xff, 0x1388);
+ pas_write(tmp, 0x0B8A);
+
+ spin_unlock_irqrestore(&pas_lock, flags);
+
+ return pcm_speed;
+}
+
+static int pcm_set_channels(int arg)
+{
+
+ if ((arg != 1) && (arg != 2))
+ return pcm_channels;
+
+ if (arg != pcm_channels)
+ {
+ pas_write(pas_read(0xF8A) ^ 0x20, 0xF8A);
+
+ pcm_channels = arg;
+ pcm_set_speed(pcm_speed); /* The speed must be reinitialized */
+ }
+ return pcm_channels;
+}
+
+static int pcm_set_bits(int arg)
+{
+ if (arg == 0)
+ return pcm_bits;
+
+ if ((arg & pcm_bitsok) != arg)
+ return pcm_bits;
+
+ if (arg != pcm_bits)
+ {
+ pas_write(pas_read(0x8389) ^ 0x04, 0x8389);
+
+ pcm_bits = arg;
+ }
+ return pcm_bits;
+}
+
+static int pas_audio_ioctl(int dev, unsigned int cmd, void __user *arg)
+{
+ int val, ret;
+ int __user *p = arg;
+
+ DEB(printk("pas2_pcm.c: static int pas_audio_ioctl(unsigned int cmd = %X, unsigned int arg = %X)\n", cmd, arg));
+
+ switch (cmd)
+ {
+ case SOUND_PCM_WRITE_RATE:
+ if (get_user(val, p))
+ return -EFAULT;
+ ret = pcm_set_speed(val);
+ break;
+
+ case SOUND_PCM_READ_RATE:
+ ret = pcm_speed;
+ break;
+
+ case SNDCTL_DSP_STEREO:
+ if (get_user(val, p))
+ return -EFAULT;
+ ret = pcm_set_channels(val + 1) - 1;
+ break;
+
+ case SOUND_PCM_WRITE_CHANNELS:
+ if (get_user(val, p))
+ return -EFAULT;
+ ret = pcm_set_channels(val);
+ break;
+
+ case SOUND_PCM_READ_CHANNELS:
+ ret = pcm_channels;
+ break;
+
+ case SNDCTL_DSP_SETFMT:
+ if (get_user(val, p))
+ return -EFAULT;
+ ret = pcm_set_bits(val);
+ break;
+
+ case SOUND_PCM_READ_BITS:
+ ret = pcm_bits;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ return put_user(ret, p);
+}
+
+static void pas_audio_reset(int dev)
+{
+ DEB(printk("pas2_pcm.c: static void pas_audio_reset(void)\n"));
+
+ pas_write(pas_read(0xF8A) & ~0x40, 0xF8A); /* Disable PCM */
+}
+
+static int pas_audio_open(int dev, int mode)
+{
+ int err;
+ unsigned long flags;
+
+ DEB(printk("pas2_pcm.c: static int pas_audio_open(int mode = %X)\n", mode));
+
+ spin_lock_irqsave(&pas_lock, flags);
+ if (pcm_busy)
+ {
+ spin_unlock_irqrestore(&pas_lock, flags);
+ return -EBUSY;
+ }
+ pcm_busy = 1;
+ spin_unlock_irqrestore(&pas_lock, flags);
+
+ if ((err = pas_set_intr(PAS_PCM_INTRBITS)) < 0)
+ return err;
+
+
+ pcm_count = 0;
+ open_mode = mode;
+
+ return 0;
+}
+
+static void pas_audio_close(int dev)
+{
+ unsigned long flags;
+
+ DEB(printk("pas2_pcm.c: static void pas_audio_close(void)\n"));
+
+ spin_lock_irqsave(&pas_lock, flags);
+
+ pas_audio_reset(dev);
+ pas_remove_intr(PAS_PCM_INTRBITS);
+ pcm_mode = PCM_NON;
+
+ pcm_busy = 0;
+ spin_unlock_irqrestore(&pas_lock, flags);
+}
+
+static void pas_audio_output_block(int dev, unsigned long buf, int count,
+ int intrflag)
+{
+ unsigned long flags, cnt;
+
+ DEB(printk("pas2_pcm.c: static void pas_audio_output_block(char *buf = %P, int count = %X)\n", buf, count));
+
+ cnt = count;
+ if (audio_devs[dev]->dmap_out->dma > 3)
+ cnt >>= 1;
+
+ if (audio_devs[dev]->flags & DMA_AUTOMODE &&
+ intrflag &&
+ cnt == pcm_count)
+ return;
+
+ spin_lock_irqsave(&pas_lock, flags);
+
+ pas_write(pas_read(0xF8A) & ~0x40,
+ 0xF8A);
+
+ /* DMAbuf_start_dma (dev, buf, count, DMA_MODE_WRITE); */
+
+ if (audio_devs[dev]->dmap_out->dma > 3)
+ count >>= 1;
+
+ if (count != pcm_count)
+ {
+ pas_write(pas_read(0x0B8A) & ~0x80, 0x0B8A);
+ pas_write(0x40 | 0x30 | 0x04, 0x138B);
+ pas_write(count & 0xff, 0x1389);
+ pas_write((count >> 8) & 0xff, 0x1389);
+ pas_write(pas_read(0x0B8A) | 0x80, 0x0B8A);
+
+ pcm_count = count;
+ }
+ pas_write(pas_read(0x0B8A) | 0x80 | 0x40, 0x0B8A);
+#ifdef NO_TRIGGER
+ pas_write(pas_read(0xF8A) | 0x40 | 0x10, 0xF8A);
+#endif
+
+ pcm_mode = PCM_DAC;
+
+ spin_unlock_irqrestore(&pas_lock, flags);
+}
+
+static void pas_audio_start_input(int dev, unsigned long buf, int count,
+ int intrflag)
+{
+ unsigned long flags;
+ int cnt;
+
+ DEB(printk("pas2_pcm.c: static void pas_audio_start_input(char *buf = %P, int count = %X)\n", buf, count));
+
+ cnt = count;
+ if (audio_devs[dev]->dmap_out->dma > 3)
+ cnt >>= 1;
+
+ if (audio_devs[pas_audiodev]->flags & DMA_AUTOMODE &&
+ intrflag &&
+ cnt == pcm_count)
+ return;
+
+ spin_lock_irqsave(&pas_lock, flags);
+
+ /* DMAbuf_start_dma (dev, buf, count, DMA_MODE_READ); */
+
+ if (audio_devs[dev]->dmap_out->dma > 3)
+ count >>= 1;
+
+ if (count != pcm_count)
+ {
+ pas_write(pas_read(0x0B8A) & ~0x80, 0x0B8A);
+ pas_write(0x40 | 0x30 | 0x04, 0x138B);
+ pas_write(count & 0xff, 0x1389);
+ pas_write((count >> 8) & 0xff, 0x1389);
+ pas_write(pas_read(0x0B8A) | 0x80, 0x0B8A);
+
+ pcm_count = count;
+ }
+ pas_write(pas_read(0x0B8A) | 0x80 | 0x40, 0x0B8A);
+#ifdef NO_TRIGGER
+ pas_write((pas_read(0xF8A) | 0x40) & ~0x10, 0xF8A);
+#endif
+
+ pcm_mode = PCM_ADC;
+
+ spin_unlock_irqrestore(&pas_lock, flags);
+}
+
+#ifndef NO_TRIGGER
+static void pas_audio_trigger(int dev, int state)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&pas_lock, flags);
+ state &= open_mode;
+
+ if (state & PCM_ENABLE_OUTPUT)
+ pas_write(pas_read(0xF8A) | 0x40 | 0x10, 0xF8A);
+ else if (state & PCM_ENABLE_INPUT)
+ pas_write((pas_read(0xF8A) | 0x40) & ~0x10, 0xF8A);
+ else
+ pas_write(pas_read(0xF8A) & ~0x40, 0xF8A);
+
+ spin_unlock_irqrestore(&pas_lock, flags);
+}
+#endif
+
+static int pas_audio_prepare_for_input(int dev, int bsize, int bcount)
+{
+ pas_audio_reset(dev);
+ return 0;
+}
+
+static int pas_audio_prepare_for_output(int dev, int bsize, int bcount)
+{
+ pas_audio_reset(dev);
+ return 0;
+}
+
+static struct audio_driver pas_audio_driver =
+{
+ .owner = THIS_MODULE,
+ .open = pas_audio_open,
+ .close = pas_audio_close,
+ .output_block = pas_audio_output_block,
+ .start_input = pas_audio_start_input,
+ .ioctl = pas_audio_ioctl,
+ .prepare_for_input = pas_audio_prepare_for_input,
+ .prepare_for_output = pas_audio_prepare_for_output,
+ .halt_io = pas_audio_reset,
+ .trigger = pas_audio_trigger
+};
+
+void __init pas_pcm_init(struct address_info *hw_config)
+{
+ DEB(printk("pas2_pcm.c: long pas_pcm_init()\n"));
+
+ pcm_bitsok = 8;
+ if (pas_read(0xEF8B) & 0x08)
+ pcm_bitsok |= 16;
+
+ pcm_set_speed(DSP_DEFAULT_SPEED);
+
+ if ((pas_audiodev = sound_install_audiodrv(AUDIO_DRIVER_VERSION,
+ "Pro Audio Spectrum",
+ &pas_audio_driver,
+ sizeof(struct audio_driver),
+ DMA_AUTOMODE,
+ AFMT_U8 | AFMT_S16_LE,
+ NULL,
+ hw_config->dma,
+ hw_config->dma)) < 0)
+ printk(KERN_WARNING "PAS16: Too many PCM devices available\n");
+}
+
+void pas_pcm_interrupt(unsigned char status, int cause)
+{
+ if (cause == 1)
+ {
+ /*
+ * Halt the PCM first. Otherwise we don't have time to start a new
+ * block before the PCM chip proceeds to the next sample
+ */
+
+ if (!(audio_devs[pas_audiodev]->flags & DMA_AUTOMODE))
+ pas_write(pas_read(0xF8A) & ~0x40, 0xF8A);
+
+ switch (pcm_mode)
+ {
+ case PCM_DAC:
+ DMAbuf_outputintr(pas_audiodev, 1);
+ break;
+
+ case PCM_ADC:
+ DMAbuf_inputintr(pas_audiodev);
+ break;
+
+ default:
+ printk(KERN_WARNING "PAS: Unexpected PCM interrupt\n");
+ }
+ }
+}