patch-2.4.20 linux-2.4.20/drivers/sound/forte.c

Next file: linux-2.4.20/drivers/sound/harmony.c
Previous file: linux-2.4.20/drivers/sound/es1371.c
Back to the patch index
Back to the overall index

diff -urN linux-2.4.19/drivers/sound/forte.c linux-2.4.20/drivers/sound/forte.c
@@ -0,0 +1,2084 @@
+/*
+ * forte.c - ForteMedia FM801 OSS Driver
+ *
+ * Written by Martin K. Petersen <mkp@mkp.net>
+ * Copyright (C) 2002 Hewlett-Packard Company
+ *
+ * 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
+ *
+ */
+ 
+/*
+ * TODO:
+ *	MMIO
+ *	Multichannelize
+ *	Multichipify
+ *	MPU401
+ *	M^Gameport
+ */
+
+#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/wrapper.h>
+
+#include <linux/proc_fs.h>
+
+#include <asm/uaccess.h>
+#include <asm/hardirq.h>
+
+#define DRIVER_NAME	"forte"
+#define DRIVER_VERSION 	"$Id: forte.c,v 1.55 2002/10/02 00:01: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     16
+#define FORTE_MAX_FRAGMENTS     256
+#define FORTE_DEF_FRAGMENTS     16
+#define FORTE_MIN_BUF           16386
+
+/* 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		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		blocked;	/* Blocked on I/O */
+	unsigned int		drain;		/* Drain queued buffers */
+	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 */
+};
+
+
+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;
+
+	MOD_INC_USE_COUNT;
+
+	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 */
+	MOD_DEC_USE_COUNT;
+
+	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->blocked = 0;
+	channel->drain = 0;
+	channel->active = 0;
+	channel->bytes = 0;
+	channel->nr_irqs = 0;
+	channel->mapped = 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) 
+		return;
+
+	DPRINTK ("%s: channel = %s\n", __FUNCTION__, channel->name);
+
+	channel->ctrl &= ~(FORTE_PAUSE | FORTE_BUF1_LAST | FORTE_BUF2_LAST);
+	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;
+
+	DPRINTK ("%s: channel = %s\n", __FUNCTION__, channel->name);
+
+	channel->ctrl &= ~FORTE_START;
+	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.
+ *
+ * FIXME:	Buffer scaling dependent on rate/channels/bits
+ */
+
+static void
+forte_channel_buffer (struct forte_channel *channel, int sz, int num)
+{
+	/* Go away, I'm busy */
+	if (channel->filled_frags || channel->bytes)
+		return;
+
+	channel->frag_sz = sz;
+	channel->frag_num = num;
+
+	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;
+
+	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;
+
+	if (channel->frag_sz * channel->frag_num < FORTE_MIN_BUF)
+		channel->frag_num = FORTE_MIN_BUF / channel->frag_sz;
+
+	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;
+
+	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);
+	
+	for (i = 0 ; i < channel->buf_pages ; i++)
+		mem_map_reserve (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;
+
+	if (!channel->active)
+		return 0;
+
+	if (channel->mapped) {
+		spin_lock_irqsave (&forte->lock, flags);
+		forte_channel_stop (channel);
+		spin_unlock_irqrestore (&forte->lock, flags);
+		return 0;
+	}
+
+	channel->drain = 1;
+	add_wait_queue (&channel->wait, &wait);
+
+	for (;;) {
+		spin_lock_irqsave (&forte->lock, flags);
+
+		if (channel->active == 0 || channel->filled_frags < 1)
+			break;
+
+		spin_unlock_irqrestore (&forte->lock, flags);
+		__set_current_state (TASK_INTERRUPTIBLE);
+		schedule();
+	}
+
+	channel->drain = 0;
+	spin_unlock_irqrestore (&forte->lock, flags);
+	set_current_state (TASK_RUNNING);
+	remove_wait_queue (&channel->wait, &wait);
+
+	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);
+	forte_channel_buffer (channel, FORTE_DEF_FRAG_SIZE, 
+			      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);
+			spin_lock_irq (&chip->lock);
+			forte_channel_reset (&chip->play);
+			spin_unlock_irq (&chip->lock);
+		}
+
+		return 0;
+
+	case SNDCTL_DSP_POST:
+		DPRINTK ("%s: POST\n", __FUNCTION__);
+
+		if (wr) {
+			spin_lock_irq (&chip->lock);
+			forte_channel_reset (&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;
+			abi.bytes = abi.fragments * abi.fragsize;
+		}
+
+		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 (!chip->play.active)
+			return 0;
+
+		if (!wr)
+			return -EINVAL;
+
+		spin_lock_irq (&chip->lock);
+
+		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;
+		}
+
+		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);
+
+	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 (down_interruptible (&chip->open_sem)) {
+		DPRINTK ("%s: returning -ERESTARTSYS\n", __FUNCTION__);
+		return -ERESTARTSYS;
+	}
+
+	file->private_data = forte;
+
+	DPRINTK ("%s: chip @ %p\n", __FUNCTION__, file->private_data);
+
+	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);
+
+		chip->play.ctrl |= FORTE_IMMED_STOP;
+		forte_channel_stop (&chip->play);
+		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);
+
+		chip->play.ctrl |= FORTE_IMMED_STOP;
+		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:
+ *
+ * FIXME:	Racy
+ */
+
+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);
+
+		if (channel->filled_frags)
+			mask |= POLLOUT | POLLWRNORM;
+	}
+
+	if (file->f_mode & FMODE_READ) {
+		channel = &chip->rec;
+
+		if (channel->active)
+			poll_wait (file, &channel->wait, wait);
+
+		if (channel->filled_frags > 0)
+			mask |= POLLIN | POLLRDNORM;
+	}
+
+	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->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 mode operation, get out */
+			if (chip->trigger) {
+				spin_unlock_irqrestore (&chip->lock, flags);
+				return -EAGAIN;
+			}
+
+			/* Otherwise wait for buffers */
+			channel->blocked = 1;
+			add_wait_queue (&channel->wait, &wait);
+
+			for (;;) {
+				if (channel->active == 0)
+					break;
+
+				if (channel->frag_num - 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);
+			channel->blocked = 0;
+		}
+
+		if (i > channel->frag_sz)
+			sz = channel->frag_sz;
+		else
+			sz = i;
+
+		spin_unlock_irqrestore (&chip->lock, flags);
+
+		/* Clear the fragment so we don't get noise when copying
+		 * smaller buffers
+		 */
+		memset ((void *) channel->buf + channel->swptr, 0x0, sz);
+
+		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->filled_frags++;
+		channel->swptr += channel->frag_sz;
+		channel->swptr %= channel->buf_sz;
+		i -= 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;
+			}
+
+			channel->blocked = 1;
+			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);
+			channel->blocked = 0;
+		}
+
+		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;
+		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 void
+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;
+	
+	if (status & FORTE_IRQ_PLAYBACK) {
+		channel = &chip->play;
+		spin_lock (&chip->lock);
+
+		/* Declare a fragment done */
+		channel->filled_frags--;
+
+		/* Get # of completed bytes */
+		count = inw (channel->iobase + FORTE_PLY_COUNT) + 1;
+		channel->bytes += count;
+
+		if (count == 0) {
+			DPRINTK ("%s: last, filled_frags = %d\n", __FUNCTION__,
+				 channel->filled_frags);
+			channel->filled_frags = 0;
+			forte_channel_stop (channel);
+			goto pack;
+		}
+
+		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 stop */
+		if (channel->filled_frags == 1)
+			forte_channel_stop (channel);
+	pack:
+		/* Acknowledge interrupt */
+                outw (FORTE_IRQ_PLAYBACK, chip->iobase + FORTE_IRQ_STATUS);
+
+		spin_unlock (&chip->lock);
+
+		if (channel->blocked || channel->drain)
+			wake_up_interruptible (&channel->wait);
+	}
+
+	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 (channel->blocked)
+			wake_up_all (&channel->wait);		
+	}
+
+	return;
+}
+
+
+/**
+ * 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",
+		      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");
+
+	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 = kmalloc (sizeof (struct ac97_codec), GFP_KERNEL)) == NULL)
+		return -ENOMEM;
+	memset (codec, 0, sizeof (struct ac97_codec));
+
+	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");
+		kfree (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");
+		kfree (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);
+
+	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)
+{
+	if (!pci_present())
+		return -ENODEV;
+
+	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);

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)