]> git.hungrycats.org Git - linux/commitdiff
[PATCH] Add Forte Media OSS driver
authorAlan Cox <alan@lxorguk.ukuu.org.uk>
Fri, 11 Jul 2003 13:50:40 +0000 (06:50 -0700)
committerSteve French <cifs.adm@hostme.bitkeeper.com>
Fri, 11 Jul 2003 13:50:40 +0000 (06:50 -0700)
sound/oss/forte.c [new file with mode: 0644]

diff --git a/sound/oss/forte.c b/sound/oss/forte.c
new file mode 100644 (file)
index 0000000..888dee5
--- /dev/null
@@ -0,0 +1,2147 @@
+/*
+ * forte.c - ForteMedia FM801 OSS Driver
+ *
+ * Written by Martin K. Petersen <mkp@mkp.net>
+ * Copyright (C) 2002 Hewlett-Packard Company
+ * Portions Copyright (C) 2003 Martin K. Petersen
+ *
+ * Latest version: http://mkp.net/forte/
+ *
+ * Based upon the ALSA FM801 driver by Jaroslav Kysela and OSS drivers
+ * by Thomas Sailer, Alan Cox, Zach Brown, and Jeff Garzik.  Thanks
+ * guys!
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/pci.h>
+
+#include <linux/delay.h>
+#include <linux/poll.h>
+#include <linux/kernel.h>
+
+#include <linux/sound.h>
+#include <linux/ac97_codec.h>
+#include <linux/interrupt.h>
+
+#include <linux/proc_fs.h>
+
+#include <asm/uaccess.h>
+#include <asm/hardirq.h>
+#include <asm/io.h>
+
+#define DRIVER_NAME    "forte"
+#define DRIVER_VERSION         "$Id: forte.c,v 1.63 2003/03/01 05:32:42 mkp Exp $"
+#define PFX            DRIVER_NAME ": "
+
+#undef M_DEBUG
+
+#ifdef M_DEBUG
+#define DPRINTK(args...) printk(KERN_WARNING args)
+#else
+#define DPRINTK(args...)
+#endif
+
+/* Card capabilities */
+#define FORTE_CAPS              (DSP_CAP_MMAP | DSP_CAP_TRIGGER)
+
+/* Supported audio formats */
+#define FORTE_FMTS             (AFMT_U8 | AFMT_S16_LE)
+
+/* Buffers */
+#define FORTE_MIN_FRAG_SIZE     256
+#define FORTE_MAX_FRAG_SIZE     PAGE_SIZE
+#define FORTE_DEF_FRAG_SIZE     256
+#define FORTE_MIN_FRAGMENTS     2
+#define FORTE_MAX_FRAGMENTS     256
+#define FORTE_DEF_FRAGMENTS     2
+#define FORTE_MIN_BUF_MSECS     500
+#define FORTE_MAX_BUF_MSECS     1000
+
+/* PCI BARs */
+#define FORTE_PCM_VOL           0x00    /* PCM Output Volume */
+#define FORTE_FM_VOL            0x02    /* FM Output Volume */
+#define FORTE_I2S_VOL           0x04    /* I2S Volume */
+#define FORTE_REC_SRC           0x06    /* Record Source */
+#define FORTE_PLY_CTRL          0x08    /* Playback Control */
+#define FORTE_PLY_COUNT         0x0a    /* Playback Count */
+#define FORTE_PLY_BUF1          0x0c    /* Playback Buffer I */
+#define FORTE_PLY_BUF2          0x10    /* Playback Buffer II */
+#define FORTE_CAP_CTRL          0x14    /* Capture Control */
+#define FORTE_CAP_COUNT         0x16    /* Capture Count */
+#define FORTE_CAP_BUF1          0x18    /* Capture Buffer I */
+#define FORTE_CAP_BUF2          0x1c    /* Capture Buffer II */
+#define FORTE_CODEC_CTRL        0x22    /* Codec Control */
+#define FORTE_I2S_MODE          0x24    /* I2S Mode Control */
+#define FORTE_VOLUME            0x26    /* Volume Up/Down/Mute Status */
+#define FORTE_I2C_CTRL          0x29    /* I2C Control */
+#define FORTE_AC97_CMD          0x2a    /* AC'97 Command */
+#define FORTE_AC97_DATA         0x2c    /* AC'97 Data */
+#define FORTE_MPU401_DATA       0x30    /* MPU401 Data */
+#define FORTE_MPU401_CMD        0x31    /* MPU401 Command */
+#define FORTE_GPIO_CTRL         0x52    /* General Purpose I/O Control */
+#define FORTE_GEN_CTRL          0x54    /* General Control */
+#define FORTE_IRQ_MASK          0x56    /* Interrupt Mask */
+#define FORTE_IRQ_STATUS        0x5a    /* Interrupt Status */
+#define FORTE_OPL3_BANK0        0x68    /* OPL3 Status Read / Bank 0 Write */
+#define FORTE_OPL3_DATA0        0x69    /* OPL3 Data 0 Write */
+#define FORTE_OPL3_BANK1        0x6a    /* OPL3 Bank 1 Write */
+#define FORTE_OPL3_DATA1        0x6b    /* OPL3 Bank 1 Write */
+#define FORTE_POWERDOWN         0x70    /* Blocks Power Down Control */
+
+#define FORTE_CAP_OFFSET        FORTE_CAP_CTRL - FORTE_PLY_CTRL
+
+#define FORTE_AC97_ADDR_SHIFT   10
+
+/* Playback and record control register bits */
+#define FORTE_BUF1_LAST         (1<<1)
+#define FORTE_BUF2_LAST         (1<<2)
+#define FORTE_START             (1<<5)
+#define FORTE_PAUSE             (1<<6)
+#define FORTE_IMMED_STOP        (1<<7)
+#define FORTE_RATE_SHIFT        8
+#define FORTE_RATE_MASK         (15 << FORTE_RATE_SHIFT)
+#define FORTE_CHANNELS_4        (1<<12) /* Playback only */
+#define FORTE_CHANNELS_6        (2<<12) /* Playback only */
+#define FORTE_CHANNELS_6MS      (3<<12) /* Playback only */
+#define FORTE_CHANNELS_MASK     (3<<12)
+#define FORTE_16BIT             (1<<14)
+#define FORTE_STEREO            (1<<15)
+
+/* IRQ status bits */
+#define FORTE_IRQ_PLAYBACK      (1<<8)
+#define FORTE_IRQ_CAPTURE       (1<<9)
+#define FORTE_IRQ_VOLUME        (1<<14)
+#define FORTE_IRQ_MPU           (1<<15)
+
+/* CODEC control */
+#define FORTE_CC_CODEC_RESET    (1<<5)
+#define FORTE_CC_AC97_RESET     (1<<6)
+
+/* AC97 cmd */
+#define FORTE_AC97_WRITE        (0<<7)
+#define FORTE_AC97_READ         (1<<7)
+#define FORTE_AC97_DP_INVALID   (0<<8)
+#define FORTE_AC97_DP_VALID     (1<<8)
+#define FORTE_AC97_PORT_RDY     (0<<9)
+#define FORTE_AC97_PORT_BSY     (1<<9)
+
+
+struct forte_channel {
+        const char             *name;
+
+       unsigned short          ctrl;           /* Ctrl BAR contents */
+       unsigned long           iobase;         /* Ctrl BAR address */
+
+       wait_queue_head_t       wait;
+
+       void                    *buf;           /* Buffer */
+       dma_addr_t              buf_handle;     /* Buffer handle */
+
+        unsigned int           record;
+       unsigned int            format;
+        unsigned int           rate;
+       unsigned int            stereo;
+
+       unsigned int            frag_sz;        /* Current fragment size */
+       unsigned int            frag_num;       /* Current # of fragments */
+       unsigned int            frag_msecs;     /* Milliseconds per frag */
+       unsigned int            buf_sz;         /* Current buffer size */
+
+       unsigned int            hwptr;          /* Tail */
+       unsigned int            swptr;          /* Head */
+       unsigned int            filled_frags;   /* Fragments currently full */
+       unsigned int            next_buf;       /* Index of next buffer */
+
+       unsigned int            active;         /* Channel currently in use */
+       unsigned int            mapped;         /* mmap */
+
+       unsigned int            buf_pages;      /* Real size of buffer */
+       unsigned int            nr_irqs;        /* Number of interrupts */
+       unsigned int            bytes;          /* Total bytes */
+       unsigned int            residue;        /* Partial fragment */
+};
+
+
+struct forte_chip {
+       struct pci_dev          *pci_dev;
+       unsigned long           iobase;
+       int                     irq;
+
+       struct semaphore        open_sem;       /* Device access */
+       spinlock_t              lock;           /* State */
+
+       spinlock_t              ac97_lock;
+       struct ac97_codec       *ac97;
+
+       int                     multichannel;
+       int                     dsp;            /* OSS handle */
+       int                     trigger;        /* mmap I/O trigger */
+
+       struct forte_channel    play;
+       struct forte_channel    rec;
+};
+
+
+static int channels[] = { 2, 4, 6, };
+static int rates[]    = { 5500, 8000, 9600, 11025, 16000, 19200, 
+                         22050, 32000, 38400, 44100, 48000, };
+
+static struct forte_chip *forte;
+static int found;
+
+
+/* AC97 Codec -------------------------------------------------------------- */
+
+
+/** 
+ * forte_ac97_wait:
+ * @chip:      fm801 instance whose AC97 codec to wait on
+ *
+ * FIXME:
+ *             Stop busy-waiting
+ */
+
+static inline int
+forte_ac97_wait (struct forte_chip *chip)
+{
+       int i = 10000;
+
+       while ( (inw (chip->iobase + FORTE_AC97_CMD) & FORTE_AC97_PORT_BSY) 
+               && i-- )
+               cpu_relax();
+
+       return i == 0;
+}
+
+
+/**
+ * forte_ac97_read:
+ * @codec:     AC97 codec to read from
+ * @reg:       register to read
+ */
+
+u16
+forte_ac97_read (struct ac97_codec *codec, u8 reg)
+{
+       u16 ret = 0;
+       struct forte_chip *chip = codec->private_data;
+
+       spin_lock (&chip->ac97_lock);
+
+       /* Knock, knock */
+       if (forte_ac97_wait (chip)) {
+               printk (KERN_ERR PFX "ac97_read: Serial bus busy\n");
+               goto out;
+       }
+
+       /* Send read command */
+       outw (reg | (1<<7), chip->iobase + FORTE_AC97_CMD);
+
+       if (forte_ac97_wait (chip)) {
+               printk (KERN_ERR PFX "ac97_read: Bus busy reading reg 0x%x\n",
+                       reg);
+               goto out;
+       }
+       
+       /* Sanity checking */
+       if (inw (chip->iobase + FORTE_AC97_CMD) & FORTE_AC97_DP_INVALID) {
+               printk (KERN_ERR PFX "ac97_read: Invalid data port");
+               goto out;
+       }
+
+       /* Fetch result */
+       ret = inw (chip->iobase + FORTE_AC97_DATA);
+
+ out:
+       spin_unlock (&chip->ac97_lock);
+       return ret;
+}
+
+
+/**
+ * forte_ac97_write:
+ * @codec:     AC97 codec to send command to
+ * @reg:       register to write
+ * @val:       value to write
+ */
+
+void
+forte_ac97_write (struct ac97_codec *codec, u8 reg, u16 val)
+{
+       struct forte_chip *chip = codec->private_data;
+
+       spin_lock (&chip->ac97_lock);
+
+       /* Knock, knock */
+       if (forte_ac97_wait (chip)) {
+               printk (KERN_ERR PFX "ac97_write: Serial bus busy\n");
+               goto out;
+       }
+
+       outw (val, chip->iobase + FORTE_AC97_DATA);
+       outb (reg | FORTE_AC97_WRITE, chip->iobase + FORTE_AC97_CMD);
+
+       /* Wait for completion */
+       if (forte_ac97_wait (chip)) {
+               printk (KERN_ERR PFX "ac97_write: Bus busy after write\n");
+               goto out;
+       }
+
+ out:
+       spin_unlock (&chip->ac97_lock);
+}
+
+
+/* Mixer ------------------------------------------------------------------- */
+
+
+/**
+ * forte_mixer_open:
+ * @inode:             
+ * @file:              
+ */
+
+static int
+forte_mixer_open (struct inode *inode, struct file *file)
+{
+       struct forte_chip *chip = forte;
+       file->private_data = chip->ac97;
+       return 0;
+}
+
+
+/**
+ * forte_mixer_release:
+ * @inode:             
+ * @file:              
+ */
+
+static int
+forte_mixer_release (struct inode *inode, struct file *file)
+{
+       /* We will welease Wodewick */
+       return 0;
+}
+
+
+/**
+ * forte_mixer_ioctl:
+ * @inode:             
+ * @file:              
+ */
+
+static int
+forte_mixer_ioctl (struct inode *inode, struct file *file, 
+                  unsigned int cmd, unsigned long arg)
+{
+       struct ac97_codec *codec = (struct ac97_codec *) file->private_data;
+
+       return codec->mixer_ioctl (codec, cmd, arg);
+}
+
+
+static struct file_operations forte_mixer_fops = {
+       owner:                  THIS_MODULE,
+       llseek:                 no_llseek,
+       ioctl:                  forte_mixer_ioctl,
+       open:                   forte_mixer_open,
+       release:                forte_mixer_release,
+};
+
+
+/* Channel ----------------------------------------------------------------- */
+
+/** 
+ * forte_channel_reset:
+ * @channel:   Channel to reset
+ * 
+ * Locking:    Must be called with lock held.
+ */
+
+static void
+forte_channel_reset (struct forte_channel *channel)
+{
+       if (!channel || !channel->iobase)
+               return;
+
+       DPRINTK ("%s: channel = %s\n", __FUNCTION__, channel->name);
+
+       channel->ctrl &= ~FORTE_START;
+       outw (channel->ctrl, channel->iobase + FORTE_PLY_CTRL);
+       
+       /* We always play at least two fragments, hence these defaults */
+       channel->hwptr = channel->frag_sz;
+       channel->next_buf = 1;
+       channel->swptr = 0;
+       channel->filled_frags = 0;
+       channel->active = 0;
+       channel->bytes = 0;
+       channel->nr_irqs = 0;
+       channel->mapped = 0;
+       channel->residue = 0;
+}
+
+
+/** 
+ * forte_channel_start:
+ * @channel:   Channel to start (record/playback)
+ *
+ * Locking:    Must be called with lock held.
+ */
+
+static void inline
+forte_channel_start (struct forte_channel *channel)
+{
+       if (!channel || !channel->iobase || channel->active) 
+               return;
+
+       channel->ctrl &= ~(FORTE_PAUSE | FORTE_BUF1_LAST | FORTE_BUF2_LAST
+                          | FORTE_IMMED_STOP);
+       channel->ctrl |= FORTE_START;
+       channel->active = 1;
+       outw (channel->ctrl, channel->iobase + FORTE_PLY_CTRL);
+}
+
+
+/** 
+ * forte_channel_stop:
+ * @channel:   Channel to stop
+ *
+ * Locking:    Must be called with lock held.
+ */
+
+static void inline
+forte_channel_stop (struct forte_channel *channel)
+{
+       if (!channel || !channel->iobase) 
+               return;
+
+       channel->ctrl &= ~(FORTE_START | FORTE_PAUSE);  
+       channel->ctrl |= FORTE_IMMED_STOP;
+
+       channel->active = 0;
+       outw (channel->ctrl, channel->iobase + FORTE_PLY_CTRL);
+}
+
+
+/** 
+ * forte_channel_pause:
+ * @channel:   Channel to pause
+ *
+ * Locking:    Must be called with lock held.
+ */
+
+static void inline
+forte_channel_pause (struct forte_channel *channel)
+{
+       if (!channel || !channel->iobase) 
+               return;
+
+       channel->ctrl |= FORTE_PAUSE;
+
+       channel->active = 0;
+       outw (channel->ctrl, channel->iobase + FORTE_PLY_CTRL);
+}
+
+
+/** 
+ * forte_channel_rate:
+ * @channel:   Channel whose rate to set.  Playback and record are
+ *             independent.
+ * @rate:      Channel rate in Hz
+ *
+ * Locking:    Must be called with lock held.
+ */
+
+static int
+forte_channel_rate (struct forte_channel *channel, unsigned int rate)
+{
+       int new_rate;
+
+       if (!channel || !channel->iobase) 
+               return -EINVAL;
+
+       /* The FM801 only supports a handful of fixed frequencies.
+        * We find the value closest to what userland requested.
+        */
+       if      (rate <= 6250)  { rate = 5500;  new_rate =  0; }
+       else if (rate <= 8800)  { rate = 8000;  new_rate =  1; }
+       else if (rate <= 10312) { rate = 9600;  new_rate =  2; }
+       else if (rate <= 13512) { rate = 11025; new_rate =  3; }
+       else if (rate <= 17600) { rate = 16000; new_rate =  4; }
+       else if (rate <= 20625) { rate = 19200; new_rate =  5; }
+       else if (rate <= 27025) { rate = 22050; new_rate =  6; }
+       else if (rate <= 35200) { rate = 32000; new_rate =  7; }
+       else if (rate <= 41250) { rate = 38400; new_rate =  8; }
+       else if (rate <= 46050) { rate = 44100; new_rate =  9; }
+       else                    { rate = 48000; new_rate = 10; }
+
+       channel->ctrl &= ~FORTE_RATE_MASK;
+       channel->ctrl |= new_rate << FORTE_RATE_SHIFT;
+       channel->rate = rate;
+
+       DPRINTK ("%s: %s rate = %d\n", __FUNCTION__, channel->name, rate);
+
+       return rate;
+}
+
+
+/** 
+ * forte_channel_format:
+ * @channel:   Channel whose audio format to set
+ * @format:    OSS format ID
+ *
+ * Locking:    Must be called with lock held.
+ */
+
+static int
+forte_channel_format (struct forte_channel *channel, int format)
+{
+       if (!channel || !channel->iobase) 
+               return -EINVAL;
+
+       switch (format) {
+
+       case AFMT_QUERY:
+               break;
+       
+       case AFMT_U8:
+               channel->ctrl &= ~FORTE_16BIT;
+               channel->format = AFMT_U8;
+               break;
+
+       case AFMT_S16_LE:
+       default:
+               channel->ctrl |= FORTE_16BIT;
+               channel->format = AFMT_S16_LE;
+               break;
+       }
+
+       DPRINTK ("%s: %s want %d format, got %d\n", __FUNCTION__, channel->name, 
+                format, channel->format);
+
+       return channel->format;
+}
+
+
+/** 
+ * forte_channel_stereo:
+ * @channel:   Channel to toggle
+ * @stereo:    0 for Mono, 1 for Stereo
+ *
+ * Locking:    Must be called with lock held.
+ */
+
+static int
+forte_channel_stereo (struct forte_channel *channel, unsigned int stereo)
+{
+       int ret;
+
+       if (!channel || !channel->iobase)
+               return -EINVAL;
+
+       DPRINTK ("%s: %s stereo = %d\n", __FUNCTION__, channel->name, stereo);
+
+       switch (stereo) {
+
+       case 0:
+               channel->ctrl &= ~(FORTE_STEREO | FORTE_CHANNELS_MASK);
+               channel-> stereo = stereo;
+               ret = stereo;
+               break;
+
+       case 1:
+               channel->ctrl &= ~FORTE_CHANNELS_MASK;
+               channel->ctrl |= FORTE_STEREO;
+               channel-> stereo = stereo;
+               ret = stereo;
+               break;
+
+       default:
+               DPRINTK ("Unsupported channel format");
+               ret = -EINVAL;
+               break;
+       }
+
+       return ret;
+}
+
+
+/** 
+ * forte_channel_buffer:
+ * @channel:   Channel whose buffer to set up
+ *
+ * Locking:    Must be called with lock held.
+ */
+
+static void
+forte_channel_buffer (struct forte_channel *channel, int sz, int num)
+{
+       unsigned int msecs, shift;
+
+       /* Go away, I'm busy */
+       if (channel->filled_frags || channel->bytes)
+               return;
+
+       /* Fragment size must be a power of 2 */
+       shift = 0; sz++;
+       while (sz >>= 1)
+               shift++;
+       channel->frag_sz = 1 << shift;
+
+       /* Round fragment size to something reasonable */
+       if (channel->frag_sz < FORTE_MIN_FRAG_SIZE)
+               channel->frag_sz = FORTE_MIN_FRAG_SIZE;
+
+       if (channel->frag_sz > FORTE_MAX_FRAG_SIZE)
+               channel->frag_sz = FORTE_MAX_FRAG_SIZE;
+
+       /* Find fragment length in milliseconds */
+       msecs = channel->frag_sz /
+               (channel->format == AFMT_S16_LE ? 2 : 1) /
+               (channel->stereo ? 2 : 1) /
+               (channel->rate / 1000);
+
+       channel->frag_msecs = msecs;
+
+       /* Pick a suitable number of fragments */
+       if (msecs * num < FORTE_MIN_BUF_MSECS)
+            num = FORTE_MIN_BUF_MSECS / msecs;
+
+       if (msecs * num > FORTE_MAX_BUF_MSECS)
+            num = FORTE_MAX_BUF_MSECS / msecs;
+
+       /* Fragment number must be a power of 2 */
+       shift = 0;      
+       while (num >>= 1)
+               shift++;
+       channel->frag_num = 1 << (shift + 1);
+
+       /* Round fragment number to something reasonable */
+       if (channel->frag_num < FORTE_MIN_FRAGMENTS)
+               channel->frag_num = FORTE_MIN_FRAGMENTS;
+
+       if (channel->frag_num > FORTE_MAX_FRAGMENTS)
+               channel->frag_num = FORTE_MAX_FRAGMENTS;
+
+       channel->buf_sz = channel->frag_sz * channel->frag_num;
+
+       DPRINTK ("%s: %s frag_sz = %d, frag_num = %d, buf_sz = %d\n",
+                __FUNCTION__, channel->name, channel->frag_sz, 
+                channel->frag_num, channel->buf_sz);
+}
+
+
+/** 
+ * forte_channel_prep:
+ * @channel:   Channel whose buffer to prepare
+ *
+ * Locking:    Lock held.
+ */
+
+static void
+forte_channel_prep (struct forte_channel *channel)
+{
+       struct page *page;
+       int i;
+       
+       if (channel->buf)
+               return;
+
+       forte_channel_buffer (channel, channel->frag_sz, channel->frag_num);
+       channel->buf_pages = channel->buf_sz >> PAGE_SHIFT;
+
+       if (channel->buf_sz % PAGE_SIZE)
+               channel->buf_pages++;
+
+       DPRINTK ("%s: %s frag_sz = %d, frag_num = %d, buf_sz = %d, pg = %d\n", 
+                __FUNCTION__, channel->name, channel->frag_sz, 
+                channel->frag_num, channel->buf_sz, channel->buf_pages);
+
+       /* DMA buffer */
+       channel->buf = pci_alloc_consistent (forte->pci_dev, 
+                                            channel->buf_pages * PAGE_SIZE,
+                                            &channel->buf_handle);
+
+       if (!channel->buf || !channel->buf_handle)
+               BUG();
+
+       page = virt_to_page (channel->buf);
+       
+       /* FIXME: can this go away ? */
+       for (i = 0 ; i < channel->buf_pages ; i++)
+               SetPageReserved(page++);
+
+       /* Prep buffer registers */
+       outw (channel->frag_sz - 1, channel->iobase + FORTE_PLY_COUNT);
+       outl (channel->buf_handle, channel->iobase + FORTE_PLY_BUF1);
+       outl (channel->buf_handle + channel->frag_sz, 
+             channel->iobase + FORTE_PLY_BUF2);
+
+       /* Reset hwptr */
+       channel->hwptr = channel->frag_sz;
+       channel->next_buf = 1;
+
+       DPRINTK ("%s: %s buffer @ %p (%p)\n", __FUNCTION__, channel->name, 
+                channel->buf, channel->buf_handle);
+}
+
+
+/** 
+ * forte_channel_drain:
+ * @chip:      
+ * @channel:   
+ *
+ * Locking:    Don't hold the lock.
+ */
+
+static inline int
+forte_channel_drain (struct forte_channel *channel)
+{
+       DECLARE_WAITQUEUE (wait, current);
+       unsigned long flags;
+
+       DPRINTK ("%s\n", __FUNCTION__);
+
+       if (channel->mapped) {
+               spin_lock_irqsave (&forte->lock, flags);
+               forte_channel_stop (channel);
+               spin_unlock_irqrestore (&forte->lock, flags);
+               return 0;
+       }
+
+       spin_lock_irqsave (&forte->lock, flags);
+       add_wait_queue (&channel->wait, &wait);
+
+       for (;;) {
+               if (channel->active == 0 || channel->filled_frags == 1)
+                       break;
+
+               spin_unlock_irqrestore (&forte->lock, flags);
+
+               __set_current_state (TASK_INTERRUPTIBLE);
+               schedule();
+
+               spin_lock_irqsave (&forte->lock, flags);
+       }
+
+       forte_channel_stop (channel);
+       forte_channel_reset (channel);
+       set_current_state (TASK_RUNNING);
+       remove_wait_queue (&channel->wait, &wait);
+       spin_unlock_irqrestore (&forte->lock, flags);
+
+       return 0;
+}
+
+
+/** 
+ * forte_channel_init:
+ * @chip:      Forte chip instance the channel hangs off
+ * @channel:   Channel to initialize
+ *
+ * Description:
+ *             Initializes a channel, sets defaults, and allocates
+ *             buffers.
+ *
+ * Locking:    No lock held.
+ */
+
+static int
+forte_channel_init (struct forte_chip *chip, struct forte_channel *channel)
+{
+       DPRINTK ("%s: chip iobase @ %p\n", __FUNCTION__, (void *)chip->iobase);
+
+       spin_lock_irq (&chip->lock);
+       memset (channel, 0x0, sizeof (*channel));
+
+       if (channel == &chip->play) {
+               channel->name = "PCM_OUT";
+               channel->iobase = chip->iobase;
+               DPRINTK ("%s: PCM-OUT iobase @ %p\n", __FUNCTION__,
+                        (void *) channel->iobase);
+       }
+       else if (channel == &chip->rec) {
+               channel->name = "PCM_IN";
+               channel->iobase = chip->iobase + FORTE_CAP_OFFSET;
+               channel->record = 1;
+               DPRINTK ("%s: PCM-IN iobase @ %p\n", __FUNCTION__, 
+                        (void *) channel->iobase);
+       }
+       else
+               BUG();
+
+       init_waitqueue_head (&channel->wait);
+
+       /* Defaults: 48kHz, 16-bit, stereo */
+       channel->ctrl = inw (channel->iobase + FORTE_PLY_CTRL);
+       forte_channel_reset (channel);
+       forte_channel_stereo (channel, 1);
+       forte_channel_format (channel, AFMT_S16_LE);
+       forte_channel_rate (channel, 48000);
+       channel->frag_sz = FORTE_DEF_FRAG_SIZE;
+       channel->frag_num = FORTE_DEF_FRAGMENTS;
+
+       chip->trigger = 0;
+       spin_unlock_irq (&chip->lock);
+
+       return 0;
+}
+
+
+/** 
+ * forte_channel_free:
+ * @chip:      Chip this channel hangs off
+ * @channel:   Channel to nuke 
+ *
+ * Description:
+ *             Resets channel and frees buffers.
+ *
+ * Locking:    Hold your horses.
+ */
+
+static void
+forte_channel_free (struct forte_chip *chip, struct forte_channel *channel)
+{
+       DPRINTK ("%s: %s\n", __FUNCTION__, channel->name);
+
+       if (!channel->buf_handle)
+               return;
+
+       pci_free_consistent (chip->pci_dev, channel->buf_pages * PAGE_SIZE, 
+                            channel->buf, channel->buf_handle);
+       
+       memset (channel, 0x0, sizeof (*channel));
+}
+
+
+/* DSP --------------------------------------------------------------------- */
+
+
+/**
+ * forte_dsp_ioctl:
+ */
+
+static int
+forte_dsp_ioctl (struct inode *inode, struct file *file, unsigned int cmd,
+                unsigned long arg)
+{
+       int ival=0, ret, rval=0, rd, wr, count;
+       struct forte_chip *chip;
+       struct audio_buf_info abi;
+       struct count_info cinfo;
+
+       chip = file->private_data;
+       
+       if (file->f_mode & FMODE_WRITE)
+               wr = 1;
+       else 
+               wr = 0;
+
+       if (file->f_mode & FMODE_READ)
+               rd = 1;
+       else
+               rd = 0;
+
+       switch (cmd) {
+
+       case OSS_GETVERSION:
+               return put_user (SOUND_VERSION, (int *) arg);
+
+       case SNDCTL_DSP_GETCAPS:
+               DPRINTK ("%s: GETCAPS\n", __FUNCTION__);
+
+               ival = FORTE_CAPS; /* DUPLEX */
+               return put_user (ival, (int *) arg);
+
+       case SNDCTL_DSP_GETFMTS:
+               DPRINTK ("%s: GETFMTS\n", __FUNCTION__);
+
+               ival = FORTE_FMTS; /* U8, 16LE */
+               return put_user (ival, (int *) arg);
+
+       case SNDCTL_DSP_SETFMT: /* U8, 16LE */
+               DPRINTK ("%s: SETFMT\n", __FUNCTION__);
+
+               if (get_user (ival, (int *) arg))
+                       return -EFAULT;
+
+               spin_lock_irq (&chip->lock);
+
+               if (rd) {
+                       forte_channel_stop (&chip->rec);
+                       rval = forte_channel_format (&chip->rec, ival);
+               }
+
+               if (wr) {
+                       forte_channel_stop (&chip->rec);
+                       rval = forte_channel_format (&chip->play, ival);
+               }
+
+               spin_unlock_irq (&chip->lock);
+       
+               return put_user (rval, (int *) arg);
+
+       case SNDCTL_DSP_STEREO: /* 0 - mono, 1 - stereo */
+               DPRINTK ("%s: STEREO\n", __FUNCTION__);
+
+               if (get_user (ival, (int *) arg))
+                       return -EFAULT;
+
+               spin_lock_irq (&chip->lock);
+
+               if (rd) {
+                       forte_channel_stop (&chip->rec);
+                       rval = forte_channel_stereo (&chip->rec, ival);
+               }
+
+               if (wr) {
+                       forte_channel_stop (&chip->rec);
+                       rval = forte_channel_stereo (&chip->play, ival);
+               }
+
+               spin_unlock_irq (&chip->lock);
+
+                return put_user (rval, (int *) arg);
+
+       case SNDCTL_DSP_CHANNELS: /* 1 - mono, 2 - stereo */
+               DPRINTK ("%s: CHANNELS\n", __FUNCTION__);
+
+               if (get_user (ival, (int *) arg))
+                       return -EFAULT;
+
+               spin_lock_irq (&chip->lock);
+
+               if (rd) {
+                       forte_channel_stop (&chip->rec);
+                       rval = forte_channel_stereo (&chip->rec, ival-1) + 1;
+               }
+
+               if (wr) {
+                       forte_channel_stop (&chip->play);
+                       rval = forte_channel_stereo (&chip->play, ival-1) + 1;
+               }
+
+               spin_unlock_irq (&chip->lock);
+
+                return put_user (rval, (int *) arg);
+
+       case SNDCTL_DSP_SPEED:
+               DPRINTK ("%s: SPEED\n", __FUNCTION__);
+
+               if (get_user (ival, (int *) arg))
+                        return -EFAULT;
+
+               spin_lock_irq (&chip->lock);
+
+               if (rd) {
+                       forte_channel_stop (&chip->rec);
+                       rval = forte_channel_rate (&chip->rec, ival);
+               }
+
+               if (wr) {
+                       forte_channel_stop (&chip->play);
+                       rval = forte_channel_rate (&chip->play, ival);
+               }
+
+               spin_unlock_irq (&chip->lock);
+
+                return put_user(rval, (int*) arg);
+
+       case SNDCTL_DSP_GETBLKSIZE:
+               DPRINTK ("%s: GETBLKSIZE\n", __FUNCTION__);
+
+               spin_lock_irq (&chip->lock);
+
+               if (rd)
+                       ival = chip->rec.frag_sz;
+
+               if (wr)
+                       ival = chip->play.frag_sz;
+
+               spin_unlock_irq (&chip->lock);
+
+                return put_user (ival, (int *) arg);
+
+       case SNDCTL_DSP_RESET:
+               DPRINTK ("%s: RESET\n", __FUNCTION__);
+
+               spin_lock_irq (&chip->lock);
+
+               if (rd)
+                       forte_channel_reset (&chip->rec);
+
+               if (wr)
+                       forte_channel_reset (&chip->play);
+
+               spin_unlock_irq (&chip->lock);
+
+                return 0;
+
+       case SNDCTL_DSP_SYNC:
+               DPRINTK ("%s: SYNC\n", __FUNCTION__);
+
+               if (wr)
+                       ret = forte_channel_drain (&chip->play);
+
+               return 0;
+
+       case SNDCTL_DSP_POST:
+               DPRINTK ("%s: POST\n", __FUNCTION__);
+
+               if (wr) {
+                       spin_lock_irq (&chip->lock);
+
+                       if (chip->play.filled_frags)
+                               forte_channel_start (&chip->play);
+
+                       spin_unlock_irq (&chip->lock);
+               }
+
+                return 0;
+
+       case SNDCTL_DSP_SETFRAGMENT:
+               DPRINTK ("%s: SETFRAGMENT\n", __FUNCTION__);
+
+               if (get_user (ival, (int *) arg))
+                       return -EFAULT;
+
+               spin_lock_irq (&chip->lock);
+
+               if (rd) {
+                       forte_channel_buffer (&chip->rec, ival & 0xffff, 
+                                             (ival >> 16) & 0xffff);
+                       ival = (chip->rec.frag_num << 16) + chip->rec.frag_sz;
+               }
+
+               if (wr) {
+                       forte_channel_buffer (&chip->play, ival & 0xffff, 
+                                             (ival >> 16) & 0xffff);
+                       ival = (chip->play.frag_num << 16) +chip->play.frag_sz;
+               }
+
+               spin_unlock_irq (&chip->lock);
+
+               return put_user (ival, (int *) arg);
+                
+        case SNDCTL_DSP_GETISPACE:
+               DPRINTK ("%s: GETISPACE\n", __FUNCTION__);
+
+               if (!rd)
+                       return -EINVAL;
+
+               spin_lock_irq (&chip->lock);
+
+               abi.fragstotal = chip->rec.frag_num;
+               abi.fragsize = chip->rec.frag_sz;
+                       
+               if (chip->rec.mapped) {
+                       abi.fragments = chip->rec.frag_num - 2;
+                       abi.bytes = abi.fragments * abi.fragsize;
+               }
+               else {
+                       abi.fragments = chip->rec.filled_frags;
+                       abi.bytes = abi.fragments * abi.fragsize;
+               }
+
+               spin_unlock_irq (&chip->lock);
+
+               return copy_to_user ((void *) arg, &abi, sizeof (abi));
+
+       case SNDCTL_DSP_GETIPTR:
+               DPRINTK ("%s: GETIPTR\n", __FUNCTION__);
+
+               if (!rd)
+                       return -EINVAL;
+
+               spin_lock_irq (&chip->lock);
+
+               if (chip->rec.active) 
+                       cinfo.ptr = chip->rec.hwptr;
+               else
+                       cinfo.ptr = 0;
+
+               cinfo.bytes = chip->rec.bytes;
+               cinfo.blocks = chip->rec.nr_irqs;
+               chip->rec.nr_irqs = 0;
+
+               spin_unlock_irq (&chip->lock);
+
+               return copy_to_user ((void *) arg, &cinfo, sizeof (cinfo));
+
+        case SNDCTL_DSP_GETOSPACE:
+               if (!wr)
+                       return -EINVAL;
+               
+               spin_lock_irq (&chip->lock);
+
+               abi.fragstotal = chip->play.frag_num;
+               abi.fragsize = chip->play.frag_sz;
+
+               if (chip->play.mapped) {
+                       abi.fragments = chip->play.frag_num - 2;
+                       abi.bytes = chip->play.buf_sz;
+               }
+               else {
+                       abi.fragments = chip->play.frag_num - 
+                               chip->play.filled_frags;
+
+                       if (chip->play.residue)
+                               abi.fragments--;
+
+                       abi.bytes = abi.fragments * abi.fragsize +
+                               chip->play.residue;
+               }
+
+               spin_unlock_irq (&chip->lock);
+               
+               return copy_to_user ((void *) arg, &abi, sizeof (abi));
+
+       case SNDCTL_DSP_GETOPTR:
+               if (!wr)
+                       return -EINVAL;
+
+               spin_lock_irq (&chip->lock);
+
+               if (chip->play.active) 
+                       cinfo.ptr = chip->play.hwptr;
+               else
+                       cinfo.ptr = 0;
+
+               cinfo.bytes = chip->play.bytes;
+               cinfo.blocks = chip->play.nr_irqs;
+               chip->play.nr_irqs = 0;
+
+               spin_unlock_irq (&chip->lock);
+
+               return copy_to_user ((void *) arg, &cinfo, sizeof (cinfo));
+
+       case SNDCTL_DSP_GETODELAY:
+               if (!wr)
+                       return -EINVAL;
+
+               spin_lock_irq (&chip->lock);
+
+               if (!chip->play.active) {
+                       ival = 0;
+               }
+               else if (chip->play.mapped) {
+                       count = inw (chip->play.iobase + FORTE_PLY_COUNT) + 1;
+                       ival = chip->play.frag_sz - count;
+               }
+               else {
+                       ival = chip->play.filled_frags * chip->play.frag_sz;
+
+                       if (chip->play.residue)
+                               ival += chip->play.frag_sz - chip->play.residue;
+               }
+
+               spin_unlock_irq (&chip->lock);
+
+               return put_user (ival, (int *) arg);
+
+       case SNDCTL_DSP_SETDUPLEX:
+               DPRINTK ("%s: SETDUPLEX\n", __FUNCTION__);
+
+               return -EINVAL;
+
+       case SNDCTL_DSP_GETTRIGGER:
+               DPRINTK ("%s: GETTRIGGER\n", __FUNCTION__);
+               
+               return put_user (chip->trigger, (int *) arg);
+               
+       case SNDCTL_DSP_SETTRIGGER:
+
+               if (get_user (ival, (int *) arg))
+                       return -EFAULT;
+
+               DPRINTK ("%s: SETTRIGGER %d\n", __FUNCTION__, ival);
+
+               if (wr) {
+                       spin_lock_irq (&chip->lock);
+
+                       if (ival & PCM_ENABLE_OUTPUT)
+                               forte_channel_start (&chip->play);
+                       else {          
+                               chip->trigger = 1;
+                               forte_channel_prep (&chip->play);
+                               forte_channel_stop (&chip->play);
+                       }
+
+                       spin_unlock_irq (&chip->lock);
+               }
+               else if (rd) {
+                       spin_lock_irq (&chip->lock);
+
+                       if (ival & PCM_ENABLE_INPUT)
+                               forte_channel_start (&chip->rec);
+                       else {          
+                               chip->trigger = 1;
+                               forte_channel_prep (&chip->rec);
+                               forte_channel_stop (&chip->rec);
+                       }
+
+                       spin_unlock_irq (&chip->lock);
+               }
+
+               return 0;
+               
+       case SOUND_PCM_READ_RATE:
+               DPRINTK ("%s: PCM_READ_RATE\n", __FUNCTION__);          
+               return put_user (chip->play.rate, (int *) arg);
+
+       case SOUND_PCM_READ_CHANNELS:
+               DPRINTK ("%s: PCM_READ_CHANNELS\n", __FUNCTION__);
+               return put_user (chip->play.stereo, (int *) arg);
+
+       case SOUND_PCM_READ_BITS:
+               DPRINTK ("%s: PCM_READ_BITS\n", __FUNCTION__);          
+               return put_user (chip->play.format, (int *) arg);
+
+       case SNDCTL_DSP_NONBLOCK:
+               DPRINTK ("%s: DSP_NONBLOCK\n", __FUNCTION__);           
+                file->f_flags |= O_NONBLOCK;
+               return 0;
+
+       default:
+               DPRINTK ("Unsupported ioctl: %x (%p)\n", cmd, (void *) arg);
+               break;
+       }
+
+       return -EINVAL;
+}
+
+
+/**
+ * forte_dsp_open:
+ */
+
+static int 
+forte_dsp_open (struct inode *inode, struct file *file)
+{
+       struct forte_chip *chip = forte; /* FIXME: HACK FROM HELL! */
+
+       if (file->f_flags & O_NONBLOCK) {
+               if (down_trylock (&chip->open_sem)) {
+                       DPRINTK ("%s: returning -EAGAIN\n", __FUNCTION__);
+                       return -EAGAIN;
+               }
+       }
+       else {
+               if (down_interruptible (&chip->open_sem)) {
+                       DPRINTK ("%s: returning -ERESTARTSYS\n", __FUNCTION__);
+                       return -ERESTARTSYS;
+               }
+       }
+
+       file->private_data = forte;
+
+       DPRINTK ("%s: dsp opened by %d\n", __FUNCTION__, current->pid);
+
+       if (file->f_mode & FMODE_WRITE)
+               forte_channel_init (forte, &forte->play);
+
+       if (file->f_mode & FMODE_READ)
+               forte_channel_init (forte, &forte->rec);
+
+       return 0;
+}
+
+
+/**
+ * forte_dsp_release:
+ */
+
+static int 
+forte_dsp_release (struct inode *inode, struct file *file)
+{
+       struct forte_chip *chip = file->private_data;
+       int ret = 0;
+
+       DPRINTK ("%s: chip @ %p\n", __FUNCTION__, chip);
+
+       if (file->f_mode & FMODE_WRITE) {
+               forte_channel_drain (&chip->play);
+
+               spin_lock_irq (&chip->lock);
+
+               forte_channel_free (chip, &chip->play);
+
+               spin_unlock_irq (&chip->lock);
+        }
+
+       if (file->f_mode & FMODE_READ) {
+               while (chip->rec.filled_frags > 0)
+                       interruptible_sleep_on (&chip->rec.wait);
+
+               spin_lock_irq (&chip->lock);
+
+               forte_channel_stop (&chip->rec);
+               forte_channel_free (chip, &chip->rec);
+
+               spin_unlock_irq (&chip->lock);
+       }
+
+       up (&chip->open_sem);
+
+       return ret;
+}
+
+
+/**
+ * forte_dsp_poll:
+ *
+ */
+
+static unsigned int 
+forte_dsp_poll (struct file *file, struct poll_table_struct *wait)
+{
+       struct forte_chip *chip;
+       struct forte_channel *channel;
+       unsigned int mask = 0;
+
+       chip = file->private_data;
+
+       if (file->f_mode & FMODE_WRITE) {
+               channel = &chip->play;
+
+               if (channel->active)
+                       poll_wait (file, &channel->wait, wait);
+
+               spin_lock_irq (&chip->lock);
+
+               if (channel->frag_num - channel->filled_frags > 0)
+                       mask |= POLLOUT | POLLWRNORM;
+
+               spin_unlock_irq (&chip->lock);
+       }
+
+       if (file->f_mode & FMODE_READ) {
+               channel = &chip->rec;
+
+               if (channel->active)
+                       poll_wait (file, &channel->wait, wait);
+
+               spin_lock_irq (&chip->lock);
+
+               if (channel->filled_frags > 0)
+                       mask |= POLLIN | POLLRDNORM;
+
+               spin_unlock_irq (&chip->lock);
+       }
+
+       return mask;
+}
+
+
+/**
+ * forte_dsp_mmap:
+ */
+
+static int
+forte_dsp_mmap (struct file *file, struct vm_area_struct *vma)
+{
+       struct forte_chip *chip;
+       struct forte_channel *channel;
+       unsigned long size;
+       int ret;
+
+       chip = file->private_data;
+
+       DPRINTK ("%s: start %lXh, size %ld, pgoff %ld\n", __FUNCTION__,
+                 vma->vm_start, vma->vm_end - vma->vm_start, vma->vm_pgoff);
+
+       spin_lock_irq (&chip->lock);
+
+       if (vma->vm_flags & VM_WRITE && chip->play.active) {
+               ret = -EBUSY;
+               goto out;
+       }
+
+        if (vma->vm_flags & VM_READ && chip->rec.active) {
+               ret = -EBUSY;
+               goto out;
+        }
+
+       if (file->f_mode & FMODE_WRITE)
+               channel = &chip->play;
+       else if (file->f_mode & FMODE_READ)
+               channel = &chip->rec;
+       else {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       forte_channel_prep (channel);
+       channel->mapped = 1;
+
+        if (vma->vm_pgoff != 0) {
+               ret = -EINVAL;
+                goto out;
+       }
+
+        size = vma->vm_end - vma->vm_start;
+
+        if (size > channel->buf_pages * PAGE_SIZE) {
+               DPRINTK ("%s: size (%ld) > buf_sz (%d) \n", __FUNCTION__,
+                        size, channel->buf_sz);
+               ret = -EINVAL;
+                goto out;
+       }
+
+        if (remap_page_range (vma, vma->vm_start, virt_to_phys (channel->buf),
+                             size, vma->vm_page_prot)) {
+               DPRINTK ("%s: remap el a no worko\n", __FUNCTION__);
+               ret = -EAGAIN;
+                goto out;
+       }
+
+        ret = 0;
+
+ out:
+       spin_unlock_irq (&chip->lock);
+        return ret;
+}
+
+
+/**
+ * forte_dsp_write:
+ */
+
+static ssize_t 
+forte_dsp_write (struct file *file, const char *buffer, size_t bytes, 
+                loff_t *ppos)
+{
+       struct forte_chip *chip;
+       struct forte_channel *channel;
+       unsigned int i = bytes, sz = 0;
+       unsigned long flags;
+
+       if (ppos != &file->f_pos)
+               return -ESPIPE;
+
+       if (!access_ok (VERIFY_READ, buffer, bytes))
+               return -EFAULT;
+
+       chip = (struct forte_chip *) file->private_data;
+
+       if (!chip)
+               BUG();
+
+       channel = &chip->play;
+
+       if (!channel)
+               BUG();
+
+       spin_lock_irqsave (&chip->lock, flags);
+
+       /* Set up buffers with the right fragment size */
+       forte_channel_prep (channel);
+
+       while (i) {
+               /* All fragment buffers in use -> wait */
+               if (channel->frag_num - channel->filled_frags == 0) {
+                       DECLARE_WAITQUEUE (wait, current);
+
+                       /* For trigger or non-blocking operation, get out */
+                       if (chip->trigger || file->f_flags & O_NONBLOCK) {
+                               spin_unlock_irqrestore (&chip->lock, flags);
+                               return -EAGAIN;
+                       }
+
+                       /* Otherwise wait for buffers */
+                       add_wait_queue (&channel->wait, &wait);
+
+                       for (;;) {
+                               spin_unlock_irqrestore (&chip->lock, flags);
+
+                               set_current_state (TASK_INTERRUPTIBLE);
+                               schedule();
+
+                               spin_lock_irqsave (&chip->lock, flags);
+
+                               if (channel->frag_num - channel->filled_frags)
+                                       break;
+                       }
+
+                       remove_wait_queue (&channel->wait, &wait);
+                       set_current_state (TASK_RUNNING);
+
+                       if (signal_pending (current)) {
+                               spin_unlock_irqrestore (&chip->lock, flags);
+                               return -ERESTARTSYS;
+                       }
+               }
+
+               if (channel->residue)
+                       sz = channel->residue;
+               else if (i > channel->frag_sz)
+                       sz = channel->frag_sz;
+               else
+                       sz = i;
+
+               spin_unlock_irqrestore (&chip->lock, flags);
+
+               if (copy_from_user ((void *) channel->buf + channel->swptr, buffer, sz))
+                       return -EFAULT;
+
+               spin_lock_irqsave (&chip->lock, flags);
+
+               /* Advance software pointer */
+               buffer += sz;
+               channel->swptr += sz;
+               channel->swptr %= channel->buf_sz;
+               i -= sz;
+
+               /* Only bump filled_frags if a full fragment has been written */
+               if (channel->swptr % channel->frag_sz == 0) {
+                       channel->filled_frags++;
+                       channel->residue = 0;
+               }
+               else
+                       channel->residue = channel->frag_sz - sz;
+
+               /* If playback isn't active, start it */
+               if (channel->active == 0 && chip->trigger == 0)
+                       forte_channel_start (channel);
+       }
+
+       spin_unlock_irqrestore (&chip->lock, flags);
+
+       return bytes - i;
+}
+
+
+/**
+ * forte_dsp_read:
+ */
+
+static ssize_t 
+forte_dsp_read (struct file *file, char *buffer, size_t bytes, 
+               loff_t *ppos)
+{
+       struct forte_chip *chip;
+       struct forte_channel *channel;
+       unsigned int i = bytes, sz;
+       unsigned long flags;
+
+       if (ppos != &file->f_pos)
+               return -ESPIPE;
+
+       if (!access_ok (VERIFY_WRITE, buffer, bytes))
+               return -EFAULT;
+
+       chip = (struct forte_chip *) file->private_data;
+
+       if (!chip)
+               BUG();
+
+       channel = &chip->rec;
+
+       if (!channel)
+               BUG();
+
+       spin_lock_irqsave (&chip->lock, flags);
+
+       /* Set up buffers with the right fragment size */
+       forte_channel_prep (channel);
+
+       /* Start recording */
+       if (!chip->trigger)
+               forte_channel_start (channel);
+
+       while (i) {
+               /* No fragment buffers in use -> wait */
+               if (channel->filled_frags == 0) {
+                       DECLARE_WAITQUEUE (wait, current);
+
+                       /* For trigger mode operation, get out */
+                       if (chip->trigger) {
+                               spin_unlock_irqrestore (&chip->lock, flags);
+                               return -EAGAIN;
+                       }
+
+                       add_wait_queue (&channel->wait, &wait);
+
+                       for (;;) {
+                               if (channel->active == 0)
+                                       break;
+
+                               if (channel->filled_frags)
+                                       break;
+                                               
+                               spin_unlock_irqrestore (&chip->lock, flags);
+
+                               set_current_state (TASK_INTERRUPTIBLE);
+                               schedule();
+
+                               spin_lock_irqsave (&chip->lock, flags);
+                       }
+
+                       set_current_state (TASK_RUNNING);
+                       remove_wait_queue (&channel->wait, &wait);
+               }
+
+               if (i > channel->frag_sz)
+                       sz = channel->frag_sz;
+               else
+                       sz = i;
+
+               spin_unlock_irqrestore (&chip->lock, flags);
+
+               if (copy_to_user (buffer, (void *)channel->buf+channel->swptr, sz)) {
+                       DPRINTK ("%s: copy_to_user failed\n", __FUNCTION__);
+                       return -EFAULT;
+               }
+
+               spin_lock_irqsave (&chip->lock, flags);
+
+               /* Advance software pointer */
+               buffer += sz;
+               if (channel->filled_frags > 0)
+                       channel->filled_frags--;
+               channel->swptr += channel->frag_sz;
+               channel->swptr %= channel->buf_sz;
+               i -= sz;
+       }
+
+       spin_unlock_irqrestore (&chip->lock, flags);
+
+       return bytes - i;
+}
+
+
+static struct file_operations forte_dsp_fops = {
+       owner:                  THIS_MODULE,
+       llseek:                 &no_llseek,
+       read:                   &forte_dsp_read,
+       write:                  &forte_dsp_write,
+       poll:                   &forte_dsp_poll,
+       ioctl:                  &forte_dsp_ioctl,
+       open:                   &forte_dsp_open,
+       release:                &forte_dsp_release,
+       mmap:                   &forte_dsp_mmap,
+};
+
+
+/* Common ------------------------------------------------------------------ */
+
+
+/**
+ * forte_interrupt:
+ */
+
+static irqreturn_t
+forte_interrupt (int irq, void *dev_id, struct pt_regs *regs)
+{
+       struct forte_chip *chip = dev_id;
+       struct forte_channel *channel = NULL;
+       u16 status, count; 
+
+       status = inw (chip->iobase + FORTE_IRQ_STATUS);
+
+       /* If this is not for us, get outta here ASAP */
+       if ((status & (FORTE_IRQ_PLAYBACK | FORTE_IRQ_CAPTURE)) == 0)
+               return IRQ_NONE;
+       
+       if (status & FORTE_IRQ_PLAYBACK) {
+               channel = &chip->play;
+
+               spin_lock (&chip->lock);
+
+               if (channel->frag_sz == 0)
+                       goto pack;
+
+               /* Declare a fragment done */
+               if (channel->filled_frags > 0)
+                       channel->filled_frags--;
+               channel->bytes += channel->frag_sz;
+               channel->nr_irqs++;
+               
+               /* Flip-flop between buffer I and II */
+               channel->next_buf ^= 1;
+
+               /* Advance hardware pointer by fragment size and wrap around */
+               channel->hwptr += channel->frag_sz;
+               channel->hwptr %= channel->buf_sz;
+
+               /* Buffer I or buffer II BAR */
+                outl (channel->buf_handle + channel->hwptr, 
+                     channel->next_buf == 0 ?
+                     channel->iobase + FORTE_PLY_BUF1 :
+                     channel->iobase + FORTE_PLY_BUF2);
+
+               /* If the currently playing fragment is last, schedule pause */
+               if (channel->filled_frags == 1) 
+                       forte_channel_pause (channel);
+
+       pack:
+               /* Acknowledge interrupt */
+                outw (FORTE_IRQ_PLAYBACK, chip->iobase + FORTE_IRQ_STATUS);
+
+               if (waitqueue_active (&channel->wait)) 
+                       wake_up_all (&channel->wait);
+
+               spin_unlock (&chip->lock);
+       }
+
+       if (status & FORTE_IRQ_CAPTURE) {
+               channel = &chip->rec;
+               spin_lock (&chip->lock);
+
+               /* One fragment filled */
+               channel->filled_frags++;
+
+               /* Get # of completed bytes */
+               count = inw (channel->iobase + FORTE_PLY_COUNT) + 1;
+
+               if (count == 0) {
+                       DPRINTK ("%s: last, filled_frags = %d\n", __FUNCTION__,
+                                channel->filled_frags);
+                       channel->filled_frags = 0;
+                       goto rack;
+               }
+
+               /* Buffer I or buffer II BAR */
+                outl (channel->buf_handle + channel->hwptr, 
+                     channel->next_buf == 0 ?
+                     channel->iobase + FORTE_PLY_BUF1 :
+                     channel->iobase + FORTE_PLY_BUF2);
+
+               /* Flip-flop between buffer I and II */
+               channel->next_buf ^= 1;
+
+               /* Advance hardware pointer by fragment size and wrap around */
+               channel->hwptr += channel->frag_sz;
+               channel->hwptr %= channel->buf_sz;
+
+               /* Out of buffers */
+               if (channel->filled_frags == channel->frag_num - 1)
+                       forte_channel_stop (channel);
+       rack:
+               /* Acknowledge interrupt */
+                outw (FORTE_IRQ_CAPTURE, chip->iobase + FORTE_IRQ_STATUS);
+
+               spin_unlock (&chip->lock);
+
+               if (waitqueue_active (&channel->wait))
+                       wake_up_all (&channel->wait);           
+       }
+
+       return IRQ_HANDLED;
+}
+
+
+/**
+ * forte_proc_read:
+ */
+
+static int
+forte_proc_read (char *page, char **start, off_t off, int count, 
+                int *eof, void *data)
+{
+       int i = 0, p_rate, p_chan, r_rate;
+       unsigned short p_reg, r_reg;
+
+       i += sprintf (page, "ForteMedia FM801 OSS Lite driver\n%s\n \n", 
+                     DRIVER_VERSION);
+
+       if (!forte->iobase)
+               return i;
+
+       p_rate = p_chan = -1;
+       p_reg  = inw (forte->iobase + FORTE_PLY_CTRL);
+       p_rate = (p_reg >> 8) & 15;
+       p_chan = (p_reg >> 12) & 3;
+
+       if (p_rate >= 0 || p_rate <= 10)
+               p_rate = rates[p_rate];
+
+       if (p_chan >= 0 || p_chan <= 2)
+               p_chan = channels[p_chan];
+
+       r_rate = -1;
+       r_reg  = inw (forte->iobase + FORTE_CAP_CTRL);
+       r_rate = (r_reg >> 8) & 15;
+
+       if (r_rate >= 0 || r_rate <= 10)
+               r_rate = rates[r_rate]; 
+
+       i += sprintf (page + i,
+                     "             Playback  Capture\n"
+                     "FIFO empty : %-3s       %-3s\n"
+                     "Buf1 Last  : %-3s       %-3s\n"
+                     "Buf2 Last  : %-3s       %-3s\n"
+                     "Started    : %-3s       %-3s\n"
+                     "Paused     : %-3s       %-3s\n"
+                     "Immed Stop : %-3s       %-3s\n"
+                     "Rate       : %-5d     %-5d\n"
+                     "Channels   : %-5d     -\n"
+                     "16-bit     : %-3s       %-3s\n"
+                     "Stereo     : %-3s       %-3s\n"
+                     " \n"
+                     "Buffer Sz  : %-6d    %-6d\n"
+                     "Frag Sz    : %-6d    %-6d\n"
+                     "Frag Num   : %-6d    %-6d\n"
+                     "Frag msecs : %-6d    %-6d\n"
+                     "Used Frags : %-6d    %-6d\n"
+                     "Mapped     : %-3s       %-3s\n",
+                     p_reg & 1<<0  ? "yes" : "no",
+                     r_reg & 1<<0  ? "yes" : "no",
+                     p_reg & 1<<1  ? "yes" : "no",
+                     r_reg & 1<<1  ? "yes" : "no",
+                     p_reg & 1<<2  ? "yes" : "no",
+                     r_reg & 1<<2  ? "yes" : "no",
+                     p_reg & 1<<5  ? "yes" : "no",
+                     r_reg & 1<<5  ? "yes" : "no",
+                     p_reg & 1<<6  ? "yes" : "no",
+                     r_reg & 1<<6  ? "yes" : "no",
+                     p_reg & 1<<7  ? "yes" : "no",
+                     r_reg & 1<<7  ? "yes" : "no",
+                     p_rate, r_rate,
+                     p_chan,
+                     p_reg & 1<<14 ? "yes" : "no",
+                     r_reg & 1<<14 ? "yes" : "no",
+                     p_reg & 1<<15 ? "yes" : "no",
+                     r_reg & 1<<15 ? "yes" : "no",
+                     forte->play.buf_sz,       forte->rec.buf_sz,
+                     forte->play.frag_sz,      forte->rec.frag_sz,
+                     forte->play.frag_num,     forte->rec.frag_num,
+                     forte->play.frag_msecs,   forte->rec.frag_msecs,
+                     forte->play.filled_frags, forte->rec.filled_frags,
+                     forte->play.mapped ? "yes" : "no",
+                     forte->rec.mapped ? "yes" : "no"
+               );
+
+       return i;
+}
+
+
+/**
+ * forte_proc_init:
+ *
+ * Creates driver info entries in /proc
+ */
+
+static int __init 
+forte_proc_init (void)
+{
+       if (!proc_mkdir ("driver/forte", 0))
+               return -EIO;
+
+       if (!create_proc_read_entry ("driver/forte/chip", 0, 0, forte_proc_read, forte)) {
+               remove_proc_entry ("driver/forte", NULL);
+               return -EIO;
+       }
+
+       if (!create_proc_read_entry("driver/forte/ac97", 0, 0, ac97_read_proc, forte->ac97)) {
+               remove_proc_entry ("driver/forte/chip", NULL);
+               remove_proc_entry ("driver/forte", NULL);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+
+/**
+ * forte_proc_remove:
+ *
+ * Removes driver info entries in /proc
+ */
+
+static void
+forte_proc_remove (void)
+{
+       remove_proc_entry ("driver/forte/ac97", NULL);
+       remove_proc_entry ("driver/forte/chip", NULL);
+       remove_proc_entry ("driver/forte", NULL);       
+}
+
+
+/**
+ * forte_chip_init:
+ * @chip:      Chip instance to initialize
+ *
+ * Description:
+ *             Resets chip, configures codec and registers the driver with
+ *             the sound subsystem.
+ *
+ *             Press and hold Start for 8 secs, then switch on Run
+ *             and hold for 4 seconds.  Let go of Start.  Numbers
+ *             assume a properly oiled TWG.
+ */
+
+static int __devinit
+forte_chip_init (struct forte_chip *chip)
+{
+       u8 revision;
+       u16 cmdw;
+       struct ac97_codec *codec;
+
+       pci_read_config_byte (chip->pci_dev, PCI_REVISION_ID, &revision);
+
+       if (revision >= 0xB1) {
+               chip->multichannel = 1;
+               printk (KERN_INFO PFX "Multi-channel device detected.\n");
+       }
+
+       /* Reset chip */
+       outw (FORTE_CC_CODEC_RESET | FORTE_CC_AC97_RESET, 
+             chip->iobase + FORTE_CODEC_CTRL);
+       udelay(100);
+       outw (0, chip->iobase + FORTE_CODEC_CTRL);
+
+       /* Request read from AC97 */
+       outw (FORTE_AC97_READ | (0 << FORTE_AC97_ADDR_SHIFT), 
+             chip->iobase + FORTE_AC97_CMD);
+       mdelay(750);
+
+       if ((inw (chip->iobase + FORTE_AC97_CMD) & (3<<8)) != (1<<8)) {
+               printk (KERN_INFO PFX "AC97 codec not responding");
+               return -EIO;
+       }
+
+       /* Init volume */
+       outw (0x0808, chip->iobase + FORTE_PCM_VOL);
+       outw (0x9f1f, chip->iobase + FORTE_FM_VOL);
+       outw (0x8808, chip->iobase + FORTE_I2S_VOL);
+
+       /* I2S control - I2S mode */
+       outw (0x0003, chip->iobase + FORTE_I2S_MODE);
+
+       /* Interrupt setup - unmask PLAYBACK & CAPTURE */
+       cmdw = inw (chip->iobase + FORTE_IRQ_MASK);
+       cmdw &= ~0x0003;
+       outw (cmdw, chip->iobase + FORTE_IRQ_MASK);
+
+       /* Interrupt clear */
+       outw (FORTE_IRQ_PLAYBACK|FORTE_IRQ_CAPTURE, 
+             chip->iobase + FORTE_IRQ_STATUS);
+
+       /* Set up the AC97 codec */
+       if ((codec = ac97_alloc_codec()) == NULL)
+               return -ENOMEM;
+       codec->private_data = chip;
+       codec->codec_read = forte_ac97_read;
+       codec->codec_write = forte_ac97_write;
+       codec->id = 0;
+
+       if (ac97_probe_codec (codec) == 0) {
+               printk (KERN_ERR PFX "codec probe failed\n");
+               ac97_release_codec(codec);
+               return -1;
+       }
+
+       /* Register mixer */
+       if ((codec->dev_mixer = 
+            register_sound_mixer (&forte_mixer_fops, -1)) < 0) {
+               printk (KERN_ERR PFX "couldn't register mixer!\n");
+               ac97_release_codec(codec);
+               return -1;
+       }
+
+       chip->ac97 = codec;
+
+       /* Register DSP */
+       if ((chip->dsp = register_sound_dsp (&forte_dsp_fops, -1) ) < 0) {
+               printk (KERN_ERR PFX "couldn't register dsp!\n");
+               return -1;
+       }
+
+       /* Register with /proc */
+       if (forte_proc_init()) {
+               printk (KERN_ERR PFX "couldn't add entries to /proc!\n");
+               return -1;
+       }
+
+       return 0;
+}
+
+
+/**
+ * forte_probe:
+ * @pci_dev:   PCI struct for probed device
+ * @pci_id:    
+ *
+ * Description:
+ *             Allocates chip instance, I/O region, and IRQ
+ */
+static int __init 
+forte_probe (struct pci_dev *pci_dev, const struct pci_device_id *pci_id)
+{
+       struct forte_chip *chip;
+       int ret = 0;
+
+       /* FIXME: Support more than one chip */
+       if (found++)
+               return -EIO;
+
+       /* Ignition */
+       if (pci_enable_device (pci_dev))
+               return -EIO;
+
+       pci_set_master (pci_dev);
+
+       /* Allocate chip instance and configure */
+       forte = (struct forte_chip *) 
+               kmalloc (sizeof (struct forte_chip), GFP_KERNEL);
+       chip = forte;
+
+       if (chip == NULL) {
+               printk (KERN_WARNING PFX "Out of memory");
+               return -ENOMEM;
+       }
+
+       memset (chip, 0, sizeof (struct forte_chip));
+       chip->pci_dev = pci_dev;
+
+       init_MUTEX(&chip->open_sem);
+       spin_lock_init (&chip->lock);
+       spin_lock_init (&chip->ac97_lock);
+
+       if (! request_region (pci_resource_start (pci_dev, 0),
+                             pci_resource_len (pci_dev, 0), DRIVER_NAME)) {
+               printk (KERN_WARNING PFX "Unable to reserve I/O space");
+               ret = -ENOMEM;
+               goto error;
+       }
+
+       chip->iobase = pci_resource_start (pci_dev, 0);
+       chip->irq = pci_dev->irq;
+
+       if (request_irq (chip->irq, forte_interrupt, SA_SHIRQ, DRIVER_NAME,
+                        chip)) {
+               printk (KERN_WARNING PFX "Unable to reserve IRQ");
+               ret = -EIO;
+               goto error;
+       }               
+       
+       pci_set_drvdata (pci_dev, chip);
+
+       printk (KERN_INFO PFX "FM801 chip found at 0x%04lX-0x%04lX IRQ %u\n", 
+               chip->iobase, pci_resource_end (pci_dev, 0), chip->irq);
+
+       /* Power it up */
+       if ((ret = forte_chip_init (chip)) == 0)
+               return 0;
+
+ error:
+       if (chip->irq)
+               free_irq (chip->irq, chip);
+
+       if (chip->iobase) 
+               release_region (pci_resource_start (pci_dev, 0),
+                               pci_resource_len (pci_dev, 0));
+               
+       kfree (chip);
+
+       return ret;
+}
+
+
+/**
+ * forte_remove:
+ * @pci_dev:   PCI device to unclaim
+ *
+ */
+
+static void 
+forte_remove (struct pci_dev *pci_dev)
+{
+       struct forte_chip *chip = pci_get_drvdata (pci_dev);
+
+       if (chip == NULL)
+               return;
+
+       /* Turn volume down to avoid popping */
+       outw (0x1f1f, chip->iobase + FORTE_PCM_VOL);
+       outw (0x1f1f, chip->iobase + FORTE_FM_VOL);
+       outw (0x1f1f, chip->iobase + FORTE_I2S_VOL);
+
+       forte_proc_remove();
+       free_irq (chip->irq, chip);
+       release_region (chip->iobase, pci_resource_len (pci_dev, 0));
+
+       unregister_sound_dsp (chip->dsp);
+       unregister_sound_mixer (chip->ac97->dev_mixer);
+       ac97_release_codec(chip->ac97);
+       kfree (chip);
+
+       printk (KERN_INFO PFX "driver released\n");
+}
+
+
+static struct pci_device_id forte_pci_ids[] __devinitdata = {
+       { 0x1319, 0x0801, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, },
+       { 0, }
+};
+
+
+static struct pci_driver forte_pci_driver = {
+       name:                   DRIVER_NAME,
+       id_table:               forte_pci_ids,
+       probe:                  forte_probe,
+       remove:                 forte_remove,
+
+};
+
+
+/**
+ * forte_init_module:
+ *
+ */
+
+static int __init
+forte_init_module (void)
+{
+       printk (KERN_INFO PFX DRIVER_VERSION "\n");
+
+       if (!pci_register_driver (&forte_pci_driver)) {
+               pci_unregister_driver (&forte_pci_driver);
+               return -ENODEV;
+       }
+
+       return 0;
+}
+
+
+/**
+ * forte_cleanup_module:
+ *
+ */
+
+static void __exit 
+forte_cleanup_module (void)
+{
+       pci_unregister_driver (&forte_pci_driver);
+}
+
+
+module_init(forte_init_module);
+module_exit(forte_cleanup_module);
+
+MODULE_AUTHOR("Martin K. Petersen <mkp@mkp.net>");
+MODULE_DESCRIPTION("ForteMedia FM801 OSS Driver");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE (pci, forte_pci_ids);