ALSA: Turtle Beach Multisound Classic/Pinnacle driver
[sfrench/cifs-2.6.git] / sound / isa / msnd / msnd.c
diff --git a/sound/isa/msnd/msnd.c b/sound/isa/msnd/msnd.c
new file mode 100644 (file)
index 0000000..264e082
--- /dev/null
@@ -0,0 +1,702 @@
+/*********************************************************************
+ *
+ * 2002/06/30 Karsten Wiese:
+ *     removed kernel-version dependencies.
+ *     ripped from linux kernel 2.4.18 (OSS Implementation) by me.
+ *     In the OSS Version, this file is compiled to a separate MODULE,
+ *     that is used by the pinnacle and the classic driver.
+ *     since there is no classic driver for alsa yet (i dont have a classic
+ *     & writing one blindfold is difficult) this file's object is statically
+ *     linked into the pinnacle-driver-module for now. look for the string
+ *             "uncomment this to make this a module again"
+ *     to do guess what.
+ *
+ * the following is a copy of the 2.4.18 OSS FREE file-heading comment:
+ *
+ * msnd.c - Driver Base
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ********************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/fs.h>
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "msnd.h"
+
+#define LOGNAME                        "msnd"
+
+
+void snd_msnd_init_queue(void *base, int start, int size)
+{
+       writew(PCTODSP_BASED(start), base + JQS_wStart);
+       writew(PCTODSP_OFFSET(size) - 1, base + JQS_wSize);
+       writew(0, base + JQS_wHead);
+       writew(0, base + JQS_wTail);
+}
+EXPORT_SYMBOL(snd_msnd_init_queue);
+
+static int snd_msnd_wait_TXDE(struct snd_msnd *dev)
+{
+       unsigned int io = dev->io;
+       int timeout = 1000;
+
+       while (timeout-- > 0)
+               if (inb(io + HP_ISR) & HPISR_TXDE)
+                       return 0;
+
+       return -EIO;
+}
+
+static int snd_msnd_wait_HC0(struct snd_msnd *dev)
+{
+       unsigned int io = dev->io;
+       int timeout = 1000;
+
+       while (timeout-- > 0)
+               if (!(inb(io + HP_CVR) & HPCVR_HC))
+                       return 0;
+
+       return -EIO;
+}
+
+int snd_msnd_send_dsp_cmd(struct snd_msnd *dev, u8 cmd)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&dev->lock, flags);
+       if (snd_msnd_wait_HC0(dev) == 0) {
+               outb(cmd, dev->io + HP_CVR);
+               spin_unlock_irqrestore(&dev->lock, flags);
+               return 0;
+       }
+       spin_unlock_irqrestore(&dev->lock, flags);
+
+       snd_printd(KERN_ERR LOGNAME ": Send DSP command timeout\n");
+
+       return -EIO;
+}
+EXPORT_SYMBOL(snd_msnd_send_dsp_cmd);
+
+int snd_msnd_send_word(struct snd_msnd *dev, unsigned char high,
+                  unsigned char mid, unsigned char low)
+{
+       unsigned int io = dev->io;
+
+       if (snd_msnd_wait_TXDE(dev) == 0) {
+               outb(high, io + HP_TXH);
+               outb(mid, io + HP_TXM);
+               outb(low, io + HP_TXL);
+               return 0;
+       }
+
+       snd_printd(KERN_ERR LOGNAME ": Send host word timeout\n");
+
+       return -EIO;
+}
+EXPORT_SYMBOL(snd_msnd_send_word);
+
+int snd_msnd_upload_host(struct snd_msnd *dev, const u8 *bin, int len)
+{
+       int i;
+
+       if (len % 3 != 0) {
+               snd_printk(KERN_ERR LOGNAME
+                          ": Upload host data not multiple of 3!\n");
+               return -EINVAL;
+       }
+
+       for (i = 0; i < len; i += 3)
+               if (snd_msnd_send_word(dev, bin[i], bin[i + 1], bin[i + 2]))
+                       return -EIO;
+
+       inb(dev->io + HP_RXL);
+       inb(dev->io + HP_CVR);
+
+       return 0;
+}
+EXPORT_SYMBOL(snd_msnd_upload_host);
+
+int snd_msnd_enable_irq(struct snd_msnd *dev)
+{
+       unsigned long flags;
+
+       if (dev->irq_ref++)
+               return 0;
+
+       snd_printdd(LOGNAME ": Enabling IRQ\n");
+
+       spin_lock_irqsave(&dev->lock, flags);
+       if (snd_msnd_wait_TXDE(dev) == 0) {
+               outb(inb(dev->io + HP_ICR) | HPICR_TREQ, dev->io + HP_ICR);
+               if (dev->type == msndClassic)
+                       outb(dev->irqid, dev->io + HP_IRQM);
+
+               outb(inb(dev->io + HP_ICR) & ~HPICR_TREQ, dev->io + HP_ICR);
+               outb(inb(dev->io + HP_ICR) | HPICR_RREQ, dev->io + HP_ICR);
+               enable_irq(dev->irq);
+               snd_msnd_init_queue(dev->DSPQ, dev->dspq_data_buff,
+                                   dev->dspq_buff_size);
+               spin_unlock_irqrestore(&dev->lock, flags);
+               return 0;
+       }
+       spin_unlock_irqrestore(&dev->lock, flags);
+
+       snd_printd(KERN_ERR LOGNAME ": Enable IRQ failed\n");
+
+       return -EIO;
+}
+EXPORT_SYMBOL(snd_msnd_enable_irq);
+
+int snd_msnd_disable_irq(struct snd_msnd *dev)
+{
+       unsigned long flags;
+
+       if (--dev->irq_ref > 0)
+               return 0;
+
+       if (dev->irq_ref < 0)
+               snd_printd(KERN_WARNING LOGNAME ": IRQ ref count is %d\n",
+                          dev->irq_ref);
+
+       snd_printdd(LOGNAME ": Disabling IRQ\n");
+
+       spin_lock_irqsave(&dev->lock, flags);
+       if (snd_msnd_wait_TXDE(dev) == 0) {
+               outb(inb(dev->io + HP_ICR) & ~HPICR_RREQ, dev->io + HP_ICR);
+               if (dev->type == msndClassic)
+                       outb(HPIRQ_NONE, dev->io + HP_IRQM);
+               disable_irq(dev->irq);
+               spin_unlock_irqrestore(&dev->lock, flags);
+               return 0;
+       }
+       spin_unlock_irqrestore(&dev->lock, flags);
+
+       snd_printd(KERN_ERR LOGNAME ": Disable IRQ failed\n");
+
+       return -EIO;
+}
+EXPORT_SYMBOL(snd_msnd_disable_irq);
+
+static inline long get_play_delay_jiffies(struct snd_msnd *chip, long size)
+{
+       long tmp = (size * HZ * chip->play_sample_size) / 8;
+       return tmp / (chip->play_sample_rate * chip->play_channels);
+}
+
+static void snd_msnd_dsp_write_flush(struct snd_msnd *chip)
+{
+       if (!(chip->mode & FMODE_WRITE) || !test_bit(F_WRITING, &chip->flags))
+               return;
+       set_bit(F_WRITEFLUSH, &chip->flags);
+/*     interruptible_sleep_on_timeout(
+               &chip->writeflush,
+               get_play_delay_jiffies(&chip, chip->DAPF.len));*/
+       clear_bit(F_WRITEFLUSH, &chip->flags);
+       if (!signal_pending(current))
+               schedule_timeout_interruptible(
+                       get_play_delay_jiffies(chip, chip->play_period_bytes));
+       clear_bit(F_WRITING, &chip->flags);
+}
+
+void snd_msnd_dsp_halt(struct snd_msnd *chip, struct file *file)
+{
+       if ((file ? file->f_mode : chip->mode) & FMODE_READ) {
+               clear_bit(F_READING, &chip->flags);
+               snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_STOP);
+               snd_msnd_disable_irq(chip);
+               if (file) {
+                       snd_printd(KERN_INFO LOGNAME
+                                  ": Stopping read for %p\n", file);
+                       chip->mode &= ~FMODE_READ;
+               }
+               clear_bit(F_AUDIO_READ_INUSE, &chip->flags);
+       }
+       if ((file ? file->f_mode : chip->mode) & FMODE_WRITE) {
+               if (test_bit(F_WRITING, &chip->flags)) {
+                       snd_msnd_dsp_write_flush(chip);
+                       snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_STOP);
+               }
+               snd_msnd_disable_irq(chip);
+               if (file) {
+                       snd_printd(KERN_INFO
+                                  LOGNAME ": Stopping write for %p\n", file);
+                       chip->mode &= ~FMODE_WRITE;
+               }
+               clear_bit(F_AUDIO_WRITE_INUSE, &chip->flags);
+       }
+}
+EXPORT_SYMBOL(snd_msnd_dsp_halt);
+
+
+int snd_msnd_DARQ(struct snd_msnd *chip, int bank)
+{
+       int /*size, n,*/ timeout = 3;
+       u16 wTmp;
+       /* void *DAQD; */
+
+       /* Increment the tail and check for queue wrap */
+       wTmp = readw(chip->DARQ + JQS_wTail) + PCTODSP_OFFSET(DAQDS__size);
+       if (wTmp > readw(chip->DARQ + JQS_wSize))
+               wTmp = 0;
+       while (wTmp == readw(chip->DARQ + JQS_wHead) && timeout--)
+               udelay(1);
+
+       if (chip->capturePeriods == 2) {
+               void *pDAQ = chip->mappedbase + DARQ_DATA_BUFF +
+                            bank * DAQDS__size + DAQDS_wStart;
+               unsigned short offset = 0x3000 + chip->capturePeriodBytes;
+
+               if (readw(pDAQ) != PCTODSP_BASED(0x3000))
+                       offset = 0x3000;
+               writew(PCTODSP_BASED(offset), pDAQ);
+       }
+
+       writew(wTmp, chip->DARQ + JQS_wTail);
+
+#if 0
+       /* Get our digital audio queue struct */
+       DAQD = bank * DAQDS__size + chip->mappedbase + DARQ_DATA_BUFF;
+
+       /* Get length of data */
+       size = readw(DAQD + DAQDS_wSize);
+
+       /* Read data from the head (unprotected bank 1 access okay
+          since this is only called inside an interrupt) */
+       outb(HPBLKSEL_1, chip->io + HP_BLKS);
+       n = msnd_fifo_write(&chip->DARF,
+                           (char *)(chip->base + bank * DAR_BUFF_SIZE),
+                           size, 0);
+       if (n <= 0) {
+               outb(HPBLKSEL_0, chip->io + HP_BLKS);
+               return n;
+       }
+       outb(HPBLKSEL_0, chip->io + HP_BLKS);
+#endif
+
+       return 1;
+}
+EXPORT_SYMBOL(snd_msnd_DARQ);
+
+int snd_msnd_DAPQ(struct snd_msnd *chip, int start)
+{
+       u16     DAPQ_tail;
+       int     protect = start, nbanks = 0;
+       void    *DAQD;
+       static int play_banks_submitted;
+       /* unsigned long flags;
+       spin_lock_irqsave(&chip->lock, flags); not necessary */
+
+       DAPQ_tail = readw(chip->DAPQ + JQS_wTail);
+       while (DAPQ_tail != readw(chip->DAPQ + JQS_wHead) || start) {
+               int bank_num = DAPQ_tail / PCTODSP_OFFSET(DAQDS__size);
+
+               if (start) {
+                       start = 0;
+                       play_banks_submitted = 0;
+               }
+
+               /* Get our digital audio queue struct */
+               DAQD = bank_num * DAQDS__size + chip->mappedbase +
+                       DAPQ_DATA_BUFF;
+
+               /* Write size of this bank */
+               writew(chip->play_period_bytes, DAQD + DAQDS_wSize);
+               if (play_banks_submitted < 3)
+                       ++play_banks_submitted;
+               else if (chip->playPeriods == 2) {
+                       unsigned short offset = chip->play_period_bytes;
+
+                       if (readw(DAQD + DAQDS_wStart) != PCTODSP_BASED(0x0))
+                               offset = 0;
+
+                       writew(PCTODSP_BASED(offset), DAQD + DAQDS_wStart);
+               }
+               ++nbanks;
+
+               /* Then advance the tail */
+               /*
+               if (protect)
+                       snd_printd(KERN_INFO "B %X %lX\n",
+                                  bank_num, xtime.tv_usec);
+               */
+
+               DAPQ_tail = (++bank_num % 3) * PCTODSP_OFFSET(DAQDS__size);
+               writew(DAPQ_tail, chip->DAPQ + JQS_wTail);
+               /* Tell the DSP to play the bank */
+               snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_START);
+               if (protect)
+                       if (2 == bank_num)
+                               break;
+       }
+       /*
+       if (protect)
+               snd_printd(KERN_INFO "%lX\n", xtime.tv_usec);
+       */
+       /* spin_unlock_irqrestore(&chip->lock, flags); not necessary */
+       return nbanks;
+}
+EXPORT_SYMBOL(snd_msnd_DAPQ);
+
+static void snd_msnd_play_reset_queue(struct snd_msnd *chip,
+                                     unsigned int pcm_periods,
+                                     unsigned int pcm_count)
+{
+       int     n;
+       void    *pDAQ = chip->mappedbase + DAPQ_DATA_BUFF;
+
+       chip->last_playbank = -1;
+       chip->playLimit = pcm_count * (pcm_periods - 1);
+       chip->playPeriods = pcm_periods;
+       writew(PCTODSP_OFFSET(0 * DAQDS__size), chip->DAPQ + JQS_wHead);
+       writew(PCTODSP_OFFSET(0 * DAQDS__size), chip->DAPQ + JQS_wTail);
+
+       chip->play_period_bytes = pcm_count;
+
+       for (n = 0; n < pcm_periods; ++n, pDAQ += DAQDS__size) {
+               writew(PCTODSP_BASED((u32)(pcm_count * n)),
+                       pDAQ + DAQDS_wStart);
+               writew(0, pDAQ + DAQDS_wSize);
+               writew(1, pDAQ + DAQDS_wFormat);
+               writew(chip->play_sample_size, pDAQ + DAQDS_wSampleSize);
+               writew(chip->play_channels, pDAQ + DAQDS_wChannels);
+               writew(chip->play_sample_rate, pDAQ + DAQDS_wSampleRate);
+               writew(HIMT_PLAY_DONE * 0x100 + n, pDAQ + DAQDS_wIntMsg);
+               writew(n, pDAQ + DAQDS_wFlags);
+       }
+}
+
+static void snd_msnd_capture_reset_queue(struct snd_msnd *chip,
+                                        unsigned int pcm_periods,
+                                        unsigned int pcm_count)
+{
+       int             n;
+       void            *pDAQ;
+       /* unsigned long        flags; */
+
+       /* snd_msnd_init_queue(chip->DARQ, DARQ_DATA_BUFF, DARQ_BUFF_SIZE); */
+
+       chip->last_recbank = 2;
+       chip->captureLimit = pcm_count * (pcm_periods - 1);
+       chip->capturePeriods = pcm_periods;
+       writew(PCTODSP_OFFSET(0 * DAQDS__size), chip->DARQ + JQS_wHead);
+       writew(PCTODSP_OFFSET(chip->last_recbank * DAQDS__size),
+               chip->DARQ + JQS_wTail);
+
+#if 0 /* Critical section: bank 1 access. this is how the OSS driver does it:*/
+       spin_lock_irqsave(&chip->lock, flags);
+       outb(HPBLKSEL_1, chip->io + HP_BLKS);
+       memset_io(chip->mappedbase, 0, DAR_BUFF_SIZE * 3);
+       outb(HPBLKSEL_0, chip->io + HP_BLKS);
+       spin_unlock_irqrestore(&chip->lock, flags);
+#endif
+
+       chip->capturePeriodBytes = pcm_count;
+       snd_printdd("snd_msnd_capture_reset_queue() %i\n", pcm_count);
+
+       pDAQ = chip->mappedbase + DARQ_DATA_BUFF;
+
+       for (n = 0; n < pcm_periods; ++n, pDAQ += DAQDS__size) {
+               u32 tmp = pcm_count * n;
+
+               writew(PCTODSP_BASED(tmp + 0x3000), pDAQ + DAQDS_wStart);
+               writew(pcm_count, pDAQ + DAQDS_wSize);
+               writew(1, pDAQ + DAQDS_wFormat);
+               writew(chip->capture_sample_size, pDAQ + DAQDS_wSampleSize);
+               writew(chip->capture_channels, pDAQ + DAQDS_wChannels);
+               writew(chip->capture_sample_rate, pDAQ + DAQDS_wSampleRate);
+               writew(HIMT_RECORD_DONE * 0x100 + n, pDAQ + DAQDS_wIntMsg);
+               writew(n, pDAQ + DAQDS_wFlags);
+       }
+}
+
+static struct snd_pcm_hardware snd_msnd_playback = {
+       .info =                 SNDRV_PCM_INFO_MMAP |
+                               SNDRV_PCM_INFO_INTERLEAVED |
+                               SNDRV_PCM_INFO_MMAP_VALID,
+       .formats =              SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+       .rates =                SNDRV_PCM_RATE_8000_48000,
+       .rate_min =             8000,
+       .rate_max =             48000,
+       .channels_min =         1,
+       .channels_max =         2,
+       .buffer_bytes_max =     0x3000,
+       .period_bytes_min =     0x40,
+       .period_bytes_max =     0x1800,
+       .periods_min =          2,
+       .periods_max =          3,
+       .fifo_size =            0,
+};
+
+static struct snd_pcm_hardware snd_msnd_capture = {
+       .info =                 SNDRV_PCM_INFO_MMAP |
+                               SNDRV_PCM_INFO_INTERLEAVED |
+                               SNDRV_PCM_INFO_MMAP_VALID,
+       .formats =              SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+       .rates =                SNDRV_PCM_RATE_8000_48000,
+       .rate_min =             8000,
+       .rate_max =             48000,
+       .channels_min =         1,
+       .channels_max =         2,
+       .buffer_bytes_max =     0x3000,
+       .period_bytes_min =     0x40,
+       .period_bytes_max =     0x1800,
+       .periods_min =          2,
+       .periods_max =          3,
+       .fifo_size =            0,
+};
+
+
+static int snd_msnd_playback_open(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+       set_bit(F_AUDIO_WRITE_INUSE, &chip->flags);
+       clear_bit(F_WRITING, &chip->flags);
+       snd_msnd_enable_irq(chip);
+
+       runtime->dma_area = chip->mappedbase;
+       runtime->dma_bytes = 0x3000;
+
+       chip->playback_substream = substream;
+       runtime->hw = snd_msnd_playback;
+       return 0;
+}
+
+static int snd_msnd_playback_close(struct snd_pcm_substream *substream)
+{
+       struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+       snd_msnd_disable_irq(chip);
+       clear_bit(F_AUDIO_WRITE_INUSE, &chip->flags);
+       return 0;
+}
+
+
+static int snd_msnd_playback_hw_params(struct snd_pcm_substream *substream,
+                                       struct snd_pcm_hw_params *params)
+{
+       int     i;
+       struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+       void    *pDAQ = chip->mappedbase + DAPQ_DATA_BUFF;
+
+       chip->play_sample_size = snd_pcm_format_width(params_format(params));
+       chip->play_channels = params_channels(params);
+       chip->play_sample_rate = params_rate(params);
+
+       for (i = 0; i < 3; ++i, pDAQ += DAQDS__size) {
+               writew(chip->play_sample_size, pDAQ + DAQDS_wSampleSize);
+               writew(chip->play_channels, pDAQ + DAQDS_wChannels);
+               writew(chip->play_sample_rate, pDAQ + DAQDS_wSampleRate);
+       }
+       /* dont do this here:
+        * snd_msnd_calibrate_adc(chip->play_sample_rate);
+        */
+
+       return 0;
+}
+
+static int snd_msnd_playback_prepare(struct snd_pcm_substream *substream)
+{
+       struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+       unsigned int pcm_size = snd_pcm_lib_buffer_bytes(substream);
+       unsigned int pcm_count = snd_pcm_lib_period_bytes(substream);
+       unsigned int pcm_periods = pcm_size / pcm_count;
+
+       snd_msnd_play_reset_queue(chip, pcm_periods, pcm_count);
+       chip->playDMAPos = 0;
+       return 0;
+}
+
+static int snd_msnd_playback_trigger(struct snd_pcm_substream *substream,
+                                    int cmd)
+{
+       struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+       int     result = 0;
+
+       if (cmd == SNDRV_PCM_TRIGGER_START) {
+               snd_printdd("snd_msnd_playback_trigger(START)\n");
+               chip->banksPlayed = 0;
+               set_bit(F_WRITING, &chip->flags);
+               snd_msnd_DAPQ(chip, 1);
+       } else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+               snd_printdd("snd_msnd_playback_trigger(STop)\n");
+               /* interrupt diagnostic, comment this out later */
+               clear_bit(F_WRITING, &chip->flags);
+               snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_STOP);
+       } else {
+               snd_printd(KERN_ERR "snd_msnd_playback_trigger(?????)\n");
+               result = -EINVAL;
+       }
+
+       snd_printdd("snd_msnd_playback_trigger() ENDE\n");
+       return result;
+}
+
+static snd_pcm_uframes_t
+snd_msnd_playback_pointer(struct snd_pcm_substream *substream)
+{
+       struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+       return bytes_to_frames(substream->runtime, chip->playDMAPos);
+}
+
+
+static struct snd_pcm_ops snd_msnd_playback_ops = {
+       .open =         snd_msnd_playback_open,
+       .close =        snd_msnd_playback_close,
+       .ioctl =        snd_pcm_lib_ioctl,
+       .hw_params =    snd_msnd_playback_hw_params,
+       .prepare =      snd_msnd_playback_prepare,
+       .trigger =      snd_msnd_playback_trigger,
+       .pointer =      snd_msnd_playback_pointer,
+};
+
+static int snd_msnd_capture_open(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+       set_bit(F_AUDIO_READ_INUSE, &chip->flags);
+       snd_msnd_enable_irq(chip);
+       runtime->dma_area = chip->mappedbase + 0x3000;
+       runtime->dma_bytes = 0x3000;
+       memset(runtime->dma_area, 0, runtime->dma_bytes);
+       chip->capture_substream = substream;
+       runtime->hw = snd_msnd_capture;
+       return 0;
+}
+
+static int snd_msnd_capture_close(struct snd_pcm_substream *substream)
+{
+       struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+       snd_msnd_disable_irq(chip);
+       clear_bit(F_AUDIO_READ_INUSE, &chip->flags);
+       return 0;
+}
+
+static int snd_msnd_capture_prepare(struct snd_pcm_substream *substream)
+{
+       struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+       unsigned int pcm_size = snd_pcm_lib_buffer_bytes(substream);
+       unsigned int pcm_count = snd_pcm_lib_period_bytes(substream);
+       unsigned int pcm_periods = pcm_size / pcm_count;
+
+       snd_msnd_capture_reset_queue(chip, pcm_periods, pcm_count);
+       chip->captureDMAPos = 0;
+       return 0;
+}
+
+static int snd_msnd_capture_trigger(struct snd_pcm_substream *substream,
+                                   int cmd)
+{
+       struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+       if (cmd == SNDRV_PCM_TRIGGER_START) {
+               chip->last_recbank = -1;
+               set_bit(F_READING, &chip->flags);
+               if (snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_START) == 0)
+                       return 0;
+
+               clear_bit(F_READING, &chip->flags);
+       } else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+               clear_bit(F_READING, &chip->flags);
+               snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_STOP);
+               return 0;
+       }
+       return -EINVAL;
+}
+
+
+static snd_pcm_uframes_t
+snd_msnd_capture_pointer(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+       return bytes_to_frames(runtime, chip->captureDMAPos);
+}
+
+
+static int snd_msnd_capture_hw_params(struct snd_pcm_substream *substream,
+                                       struct snd_pcm_hw_params *params)
+{
+       int             i;
+       struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+       void            *pDAQ = chip->mappedbase + DARQ_DATA_BUFF;
+
+       chip->capture_sample_size = snd_pcm_format_width(params_format(params));
+       chip->capture_channels = params_channels(params);
+       chip->capture_sample_rate = params_rate(params);
+
+       for (i = 0; i < 3; ++i, pDAQ += DAQDS__size) {
+               writew(chip->capture_sample_size, pDAQ + DAQDS_wSampleSize);
+               writew(chip->capture_channels, pDAQ + DAQDS_wChannels);
+               writew(chip->capture_sample_rate, pDAQ + DAQDS_wSampleRate);
+       }
+       return 0;
+}
+
+
+static struct snd_pcm_ops snd_msnd_capture_ops = {
+       .open =         snd_msnd_capture_open,
+       .close =        snd_msnd_capture_close,
+       .ioctl =        snd_pcm_lib_ioctl,
+       .hw_params =    snd_msnd_capture_hw_params,
+       .prepare =      snd_msnd_capture_prepare,
+       .trigger =      snd_msnd_capture_trigger,
+       .pointer =      snd_msnd_capture_pointer,
+};
+
+
+int snd_msnd_pcm(struct snd_card *card, int device,
+                       struct snd_pcm **rpcm)
+{
+       struct snd_msnd *chip = card->private_data;
+       struct snd_pcm  *pcm;
+       int err;
+
+       err = snd_pcm_new(card, "MSNDPINNACLE", device, 1, 1, &pcm);
+       if (err < 0)
+               return err;
+
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_msnd_playback_ops);
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_msnd_capture_ops);
+
+       pcm->private_data = chip;
+       strcpy(pcm->name, "Hurricane");
+
+
+       if (rpcm)
+               *rpcm = pcm;
+       return 0;
+}
+EXPORT_SYMBOL(snd_msnd_pcm);
+