patch-2.3.30 linux/drivers/sound/trident.c

Next file: linux/drivers/sound/trident.h
Previous file: linux/drivers/sound/sonicvibes.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.29/linux/drivers/sound/trident.c linux/drivers/sound/trident.c
@@ -0,0 +1,3042 @@
+/*****************************************************************************
+ *
+ *      Trident 4D-Wave OSS driver for Linux 2.2.x
+ *
+ *	Driver: Alan Cox <alan@redhat.com>
+ *
+ *  Built from:
+ *  	Low level code: <audio@tridentmicro.com> from ALSA
+ *  	Framework: Thomas Sailer <sailer@ife.ee.ethz.ch>
+ *  	Extended by: Zach Brown <zab@redhat.com>  
+ *
+ *	Hacked up by:
+ *	  Aaron Holtzman <aholtzma@ess.engr.uvic.ca>
+ *
+ *
+ *      This program is free software; you can redistribute it and/or modify
+ *      it under the terms of the GNU General Public License as published by
+ *      the Free Software Foundation; either version 2 of the License, or
+ *      (at your option) any later version.
+ *
+ *      This program is distributed in the hope that it will be useful,
+ *      but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *      GNU General Public License for more details.
+ *
+ *      You should have received a copy of the GNU General Public License
+ *      along with this program; if not, write to the Free Software
+ *      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+/*****************************************************************************/
+
+      
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <linux/ioport.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/sound.h>
+#include <linux/malloc.h>
+#include <linux/soundcard.h>
+#include <linux/pci.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/spinlock.h>
+#include <asm/uaccess.h>
+#include <asm/hardirq.h>
+
+#ifdef CONFIG_APM
+#include <linux/apm_bios.h>
+#endif
+
+#include "trident.h"
+#include "ac97.h"
+
+/* --------------------------------------------------------------------- */
+
+#define M_DEBUG 1
+
+#ifdef M_DEBUG
+static int debug=0;
+#define M_printk(args...) {if (debug) printk(args);}
+#else
+#define M_printk(x)
+#endif
+
+/* --------------------------------------------------------------------- */
+#define DRIVER_VERSION "0.01"
+
+#define TRIDENT_FMT_STEREO	0x01
+#define TRIDENT_FMT_16BIT	0x02
+#define TRIDENT_FMT_MASK	0x03
+#define TRIDENT_DAC_SHIFT	0   
+#define TRIDENT_ADC_SHIFT	4
+
+#define TRIDENT_ENABLE_PE	1
+#define TRIDENT_ENABLE_RE	2
+
+
+#define TRIDENT_CARD_MAGIC	0x5072696E
+#define TRIDENT_STATE_MAGIC	0x63657373
+
+#define DAC_RUNNING		1
+#define ADC_RUNNING		2
+
+#define NR_DSPS		8
+
+#define SND_DEV_DSP16   5 
+
+static const unsigned sample_size[] = { 1, 2, 2, 4 };
+static const unsigned sample_shift[] = { 0, 1, 1, 2 };
+
+enum card_types_t {
+	TYPE_4DWAVE_DX,
+	TYPE_4DWAVE_NX
+};
+
+static const char *card_names[]={
+	[TYPE_4DWAVE_DX] = "Trident 4DWave DX",
+	[TYPE_4DWAVE_NX] = "Trident 4DWave NX",
+};
+
+
+typedef struct tChannelControl
+{
+    // register data
+    unsigned int *  lpChStart;
+    unsigned int *  lpChStop;
+    unsigned int *  lpChAint;
+    unsigned int *  lpChAinten;
+
+    // register addresses
+    unsigned int *  lpAChStart;
+    unsigned int *  lpAChStop;
+    unsigned int *  lpAChAint;
+    unsigned int *  lpAChAinten;
+    
+    unsigned int data[16];
+
+} CHANNELCONTROL;
+
+/* --------------------------------------------------------------------- */
+
+struct trident_state {
+	unsigned int magic;
+	int channel;
+	struct trident_card *card;	/* Card info */
+	/* wave stuff */
+	unsigned int rateadc, ratedac;
+	unsigned char fmt, enable;
+
+	struct semaphore open_sem;
+	mode_t open_mode;
+	wait_queue_head_t open_wait;
+
+	/* soundcore stuff */
+	int dev_audio;
+
+	struct dmabuf {
+		void *rawbuf;
+		unsigned buforder;
+		unsigned numfrag;
+		unsigned fragshift;
+		int chan[2];	/* Hardware channel */
+		/* XXX zab - swptr only in here so that it can be referenced by
+			clear_advance, as far as I can tell :( */
+		unsigned hwptr, swptr;
+		unsigned total_bytes;
+		int count;
+		unsigned error; /* over/underrun */
+		wait_queue_head_t wait;
+		/* redundant, but makes calculations easier */
+		unsigned fragsize;
+		unsigned dmasize;
+		unsigned fragsamples;
+		/* OSS stuff */
+		unsigned mapped:1;
+		unsigned ready:1;
+		unsigned endcleared:1;
+		unsigned ossfragshift;
+		int ossmaxfrags;
+		unsigned subdivision;
+		u16 base;		/* Offset for ptr */
+	} dma_dac, dma_adc;
+	
+	u8 bDMAStart;
+
+};
+	
+struct trident_card {
+	unsigned int magic;
+
+	/* We keep trident cards in a linked list */
+	struct trident_card *next;
+
+	/* The trident has a certain amount of cross channel interaction
+	   so we use a single per card lock */
+	   
+	spinlock_t lock;
+
+	int dev_mixer;
+
+	int card_type;
+
+	/* as most of this is static,
+		perhaps it should be a pointer to a global struct */
+	struct mixer_goo {
+		int modcnt;
+		int supported_mixers;
+		int stereo_mixers;
+		int record_sources;
+		/* the caller must guarantee arg sanity before calling these */
+/*		int (*read_mixer)(struct trident_card *card, int index);*/
+		void (*write_mixer)(struct trident_card *card,int mixer, unsigned int left,unsigned int right);
+		int (*recmask_io)(struct trident_card *card,int rw,int mask);
+		unsigned int mixer_state[SOUND_MIXER_NRDEVICES];
+	} mix;
+	
+	struct trident_state channels[NR_DSPS];
+
+	/* hardware resources */
+	unsigned long iobase;
+	u32 irq;
+
+	u32 ChanMap[2];
+	int ChanPCMcnt;
+	int ChanPCM;
+	CHANNELCONTROL ChRegs;
+	int ChanDwordCount;
+};
+
+static struct timer_list debug_timer;
+
+#define IWriteAinten( x ) \
+        {int i; \
+         for( i= 0; i < ChanDwordCount; i++) \
+		outl((x)->lpChAinten[i], TRID_REG(trident, (x)->lpAChAinten[i]));}
+
+#define IReadAinten( x ) \
+        {int i; \
+         for( i= 0; i < ChanDwordCount; i++) \
+         (x)->lpChAinten[i] = inl(TRID_REG(trident, (x)->lpAChAinten[i]));}
+
+#define ReadAint( x ) \
+        IReadAint( x ) 
+
+#define WriteAint( x ) \
+        IWriteAint( x ) 
+
+#define IWriteAint( x ) \
+        {int i; \
+         for( i= 0; i < ChanDwordCount; i++) \
+         outl((x)->lpChAint[i], TRID_REG(trident, (x)->lpAChAint[i]));}
+
+#define IReadAint( x ) \
+        {int i; \
+         for( i= 0; i < ChanDwordCount; i++) \
+         (x)->lpChAint[i] = inl(TRID_REG(trident, (x)->lpAChAint[i]));}
+
+
+/*
+ *	Trident support library routines
+ */
+ 
+/*---------------------------------------------------------------------------
+   void ResetAinten( struct trident_state *trident, int ChannelNum)
+  
+   Description: This routine will disable interrupts and ack any 
+                existing interrupts for specified channel.
+  
+   Parameters:  trident - pointer to target device class for 4DWave.
+                ChannelNum - channel number 
+  
+   returns:     TRUE if everything went ok, else FALSE.
+  
+  ---------------------------------------------------------------------------*/
+
+static void ResetAinten(struct trident_card * trident, int ChannelNum)
+{
+	unsigned int dwMask;
+	unsigned int x = ChannelNum >> 5;
+	unsigned int ChanDwordCount = trident->ChanDwordCount;
+
+	IReadAinten(&trident->ChRegs);
+	dwMask = 1 << (ChannelNum & 0x1f);
+	trident->ChRegs.lpChAinten[x] &= ~dwMask;
+	IWriteAinten(&trident->ChRegs);
+	// Ack the channel in case the interrupt was set before we disable it.
+	outl(dwMask, TRID_REG(trident, trident->ChRegs.lpAChAint[x]));
+}
+
+/*---------------------------------------------------------------------------
+   void EnableEndInterrupts( struct trident_card *trident)
+  
+   Description: This routine will enable end of loop interrupts.
+                End of loop interrupts will occur when a running
+                channel reaches ESO.
+  
+   Parameters:  trident - pointer to target device class for 4DWave.
+  
+   returns:     TRUE if everything went ok, else FALSE.
+  
+  ---------------------------------------------------------------------------*/
+
+static int EnableEndInterrupts(struct trident_card * trident)
+{
+	unsigned int GlobalControl;
+
+	GlobalControl = inb(TRID_REG(trident, T4D_LFO_GC_CIR + 1));
+	GlobalControl |= 0x10;
+	outb(GlobalControl, TRID_REG(trident, T4D_LFO_GC_CIR + 1));
+
+	M_printk("(trident) globctl=%02X\n", GlobalControl);
+	M_printk("(trident) enabled end interrupts\n");
+	return (TRUE);
+}
+
+/*---------------------------------------------------------------------------
+e   void DisableEndInterrupts( struct trident_card *trident)
+  
+   Description: This routine will disable end of loop interrupts.
+                End of loop interrupts will occur when a running
+                channel reaches ESO.
+  
+   Parameters:  
+                trident - pointer to target device class for 4DWave.
+  
+   returns:     TRUE if everything went ok, else FALSE.
+  
+  ---------------------------------------------------------------------------*/
+
+static int DisableEndInterrupts(struct trident_card * trident)
+{
+	unsigned int GlobalControl;
+
+	GlobalControl = inb(TRID_REG(trident, T4D_LFO_GC_CIR + 1));
+	GlobalControl &= ~0x10;
+	outb(GlobalControl, TRID_REG(trident, T4D_LFO_GC_CIR + 1));
+
+	M_printk("(trident) disabled end interrupts\n");
+	M_printk("(trident) globctl=%02X\n", GlobalControl);
+	return (TRUE);
+}
+
+/*---------------------------------------------------------------------------
+   void trident_enable_voice_irq( unsigned int HwChannel )
+  
+    Description: Enable an interrupt channel, any channel 0 thru n.
+                 This routine automatically handles the fact that there are
+                 more than 32 channels available.
+  
+    Parameters : HwChannel - Channel number 0 thru n.
+                 trident - pointer to target device class for 4DWave.
+  
+    Return Value: None.
+  
+  ---------------------------------------------------------------------------*/
+void trident_enable_voice_irq(struct trident_card * trident, unsigned int HwChannel)
+{
+	unsigned int x, Data, ChanDwordCount;
+
+	x = HwChannel >> 5;
+	Data = 1 << (HwChannel & 0x1f);
+	ChanDwordCount = trident->ChanDwordCount;
+	IReadAinten(&trident->ChRegs);
+	trident->ChRegs.lpChAinten[x] |= Data;
+	IWriteAinten(&trident->ChRegs);
+	M_printk("(trident) enabled voice IRQ %d\n", HwChannel);
+}
+
+/*---------------------------------------------------------------------------
+   void trident_disable_voice_irq( unsigned int HwChannel )
+  
+    Description: Disable an interrupt channel, any channel 0 thru n.
+                 This routine automatically handles the fact that there are
+                 more than 32 channels available.
+  
+    Parameters : HwChannel - Channel number 0 thru n.
+                 trident - pointer to target device class for 4DWave.
+  
+    Return Value: None.
+  
+  ---------------------------------------------------------------------------*/
+void trident_disable_voice_irq(struct trident_card * trident, unsigned int HwChannel)
+{
+	unsigned int x, Data, ChanDwordCount;
+
+	x = HwChannel >> 5;
+	Data = 1 << (HwChannel & 0x1f);
+	ChanDwordCount = trident->ChanDwordCount;
+	IReadAinten(&trident->ChRegs);
+	trident->ChRegs.lpChAinten[x] &= ~Data;
+	IWriteAinten(&trident->ChRegs);
+	M_printk("(trident) disabled voice IRQ %d\n", HwChannel);
+}
+
+/*---------------------------------------------------------------------------
+   unsigned int AllocateChannelPCM( void )
+  
+    Description: Allocate hardware channel by reverse order (63-0).
+  
+    Parameters :  trident - pointer to target device class for 4DWave.
+  
+    Return Value: hardware channel - 0-63 or -1 when no channel is available
+  
+  ---------------------------------------------------------------------------*/
+
+static int AllocateChannelPCM(struct trident_card *trident)
+{
+	int idx;
+
+	if (trident->ChanPCMcnt >= trident->ChanPCM)
+	{
+		M_printk(KERN_DEBUG "(trident) no channels available.\n");
+		return -1;
+	}
+	for (idx = 31; idx >= 0; idx--) {
+		if (!(trident->ChanMap[1] & (1 << idx))) {
+			trident->ChanMap[1] |= 1 << idx;
+			trident->ChanPCMcnt++;
+			return idx + 32;
+		}
+	}
+	for (idx = 31; idx >= 0; idx--) {
+		if (!(trident->ChanMap[0] & (1 << idx))) {
+			trident->ChanMap[0] |= 1 << idx;
+			trident->ChanPCMcnt++;
+			return idx;
+		}
+	}
+	return -1;
+}
+
+/*---------------------------------------------------------------------------
+   void FreeChannelPCM( int channel )
+  
+    Description: Free hardware channel.
+  
+    Parameters :  trident - pointer to target device class for 4DWave.
+	          channel - hardware channel number 0-63
+  
+    Return Value: none
+  
+  ---------------------------------------------------------------------------*/
+
+static void FreeChannelPCM(struct trident_card *trident, int channel)
+{
+	if (channel < 0 || channel > 63)
+		return;
+	if (trident->ChanMap[channel>>5] & (1 << (channel & 0x1f))) {
+		trident->ChanMap[channel>>5] &= ~(1 << (channel & 0x1f));
+		trident->ChanPCMcnt--;
+	}
+}
+
+/*---------------------------------------------------------------------------
+   void trident_start_voice( ULONG HwChannel )
+  
+    Description: Start a channel, any channel 0 thru n.
+                 This routine automatically handles the fact that there are
+                 more than 32 channels available.
+  
+    Parameters : HwChannel - Channel number 0 thru n.
+                 trident - pointer to target device class for 4DWave.
+  
+    Return Value: None.
+  
+  ---------------------------------------------------------------------------*/
+void trident_start_voice(struct trident_card * trident, unsigned int HwChannel)
+{
+	unsigned int x = HwChannel >> 5;
+	unsigned int Data = 1 << (HwChannel & 0x1f);
+
+	outl(Data, TRID_REG(trident, trident->ChRegs.lpAChStart[x]));
+	M_printk("(trident) start voice %d\n", HwChannel);
+}
+
+/*---------------------------------------------------------------------------
+   void trident_stop_voice( ULONG HwChannel )
+  
+    Description: Stop a channel, any channel 0 thru n.
+                 This routine automatically handles the fact that there are
+                 more than 32 channels available.
+  
+    Parameters : HwChannel - Channel number 0 thru n.
+                 trident - pointer to target device class for 4DWave.
+  
+    Return Value: None.
+  
+  ---------------------------------------------------------------------------*/
+void trident_stop_voice(struct trident_card * trident, unsigned int HwChannel)
+{
+	unsigned int x = HwChannel >> 5;
+	unsigned int Data = 1 << (HwChannel & 0x1f);
+
+	outl(Data, TRID_REG(trident, trident->ChRegs.lpAChStop[x]));
+  M_printk("(trident) stop voice %d\n", HwChannel);
+}
+
+/*---------------------------------------------------------------------------
+   int DidChannelInterrupt( )
+  
+   Description:  Check if interrupt channel occurred.
+  
+   Parameters :  trident - pointer to target device class for 4DWave.
+  
+   Return Value: TRUE if interrupt occurred, else FALSE.
+  
+  ---------------------------------------------------------------------------*/
+static int DidChannelInterrupt(struct trident_card * trident, int channel)
+{
+	unsigned int ChanDwordCount = trident->ChanDwordCount;
+	unsigned int x = channel >> 5;
+	unsigned int dwMask = 1 << (channel & 0x1f);
+
+	ReadAint(&trident->ChRegs);
+	return (trident->ChRegs.lpChAint[x] & dwMask) ? TRUE : FALSE;
+}
+
+/*---------------------------------------------------------------------------
+   void AckChannelInterrupt( )
+  
+   Description:  Acknowledge the interrupt bit for channel intrs.
+  
+   Parameters :  trident - pointer to target device class for 4DWave.
+  
+   Return Value: None
+  
+  ---------------------------------------------------------------------------*/
+static void AckChannelInterrupt(struct trident_card * trident, int channel)
+{
+	unsigned int ChanDwordCount = trident->ChanDwordCount;
+	unsigned int x = channel >> 5;
+	unsigned int dwMask = 1 << (channel & 0x1f);
+
+	ReadAint(&trident->ChRegs);
+	trident->ChRegs.lpChAint[x] &= dwMask;
+	IWriteAint(&trident->ChRegs);
+}
+
+/*---------------------------------------------------------------------------
+   int LoadDeltaHw( unsigned int HwChannel, unsigned int Delta )
+  
+   Description: This routine writes Delta to the hardware.
+  
+   Parameters:  Delta - data to write (2 Bytes only)
+                HwChannel - Hardware channel to write to.
+   		trident - pointer to target device class for 4DWave.
+  
+   Returns:     TRUE if all goes well, else FALSE. 
+  
+  ---------------------------------------------------------------------------*/
+static int LoadDeltaHw(struct trident_card * trident, unsigned int HwChannel, unsigned int Delta)
+{
+
+	outb(HwChannel, TRID_REG(trident, T4D_LFO_GC_CIR));
+
+	if (trident->card_type != TYPE_4DWAVE_NX) {
+		outw((unsigned short) Delta, TRID_REG(trident, CH_DX_ESO_DELTA));
+	} 
+	else			// ID_4DWAVE_NX
+	{
+		outb((unsigned char) Delta, TRID_REG(trident, CH_NX_DELTA_CSO + 3));
+		outb((unsigned char) (Delta >> 8), TRID_REG(trident, CH_NX_DELTA_ESO + 3));
+	}
+
+	return TRUE;
+}
+
+/*---------------------------------------------------------------------------
+   int LoadVirtualChannel( ULONG *Data, ULONG HwChannel)
+  
+   Description: This routine writes all required channel registers to hardware.
+  
+   Parameters:  *Data - a pointer to the data to write (5 ULONGS always).
+                HwChannel - Hardware channel to write to.
+   		trident - pointer to target device class for 4DWave.
+  
+   Returns:     TRUE if all goes well, else FALSE. 
+  
+  ---------------------------------------------------------------------------*/
+static int LoadVirtualChannel(struct trident_card * trident, unsigned int *Data, unsigned int HwChannel)
+{
+	unsigned int ChanData[CHANNEL_REGS];
+	unsigned int ULONGSToDo = CHANNEL_REGS;
+	unsigned int i;
+	unsigned int Address = CHANNEL_START;
+
+	/* Copy the data first... Hack... Before mucking with Volume! */
+	memcpy((unsigned char *) ChanData, (unsigned char *) Data, ULONGSToDo * 4);
+
+	outb((unsigned char) HwChannel, TRID_REG(trident, T4D_LFO_GC_CIR));
+
+	for (i = 0; i < ULONGSToDo; i++, Address += 4)
+		outl(ChanData[i], TRID_REG(trident, Address));
+
+	M_printk("(trident) load virtual channel %d\n", HwChannel);
+	return TRUE;
+}
+
+/*---------------------------------------------------------------------------
+   trident_write_voice_regs
+  
+   Description: This routine will write the 5 hardware channel registers
+                to hardware.
+  
+   Paramters:   trident - pointer to target device class for 4DWave.
+                Channel - Real or Virtual channel number.
+                Each register field.
+  
+   Returns:     TRUE if all goes well, else FALSE. 
+  
+  ---------------------------------------------------------------------------*/
+int trident_write_voice_regs(struct trident_card * trident,
+			 unsigned int Channel,
+			 unsigned int LBA,
+			 unsigned int CSO,
+			 unsigned int ESO,
+			 unsigned int DELTA,
+			 unsigned int ALPHA_FMS,
+			 unsigned int FMC_RVOL_CVOL,
+			 unsigned int GVSEL,
+			 unsigned int PAN,
+			 unsigned int VOL,
+			 unsigned int CTRL,
+			 unsigned int EC)
+{
+	unsigned int ChanData[CHANNEL_REGS + 1], FmcRvolCvol;
+
+	ChanData[1] = LBA;
+	ChanData[4] = (GVSEL << 31) |
+	    ((PAN & 0x0000007f) << 24) |
+	    ((VOL & 0x000000ff) << 16) |
+	    ((CTRL & 0x0000000f) << 12) |
+	    (EC & 0x00000fff);
+
+	FmcRvolCvol = FMC_RVOL_CVOL & 0x0000ffff;
+
+	if (trident->card_type != TYPE_4DWAVE_NX) 
+	{
+		ChanData[0] = (CSO << 16) | (ALPHA_FMS & 0x0000ffff);
+		ChanData[2] = (ESO << 16) | (DELTA & 0x0ffff);
+		ChanData[3] = FmcRvolCvol;
+	}
+	else			// ID_4DWAVE_NX
+	{
+		ChanData[0] = (DELTA << 24) | (CSO & 0x00ffffff);
+		ChanData[2] = ((DELTA << 16) & 0xff000000) | (ESO & 0x00ffffff);
+		ChanData[3] = (ALPHA_FMS << 16) | FmcRvolCvol;
+	}
+
+	LoadVirtualChannel(trident, ChanData, Channel);
+
+	return TRUE;
+}
+
+static int compute_rate(u32 rate)
+{
+	int delta;
+	// We special case 44100 and 8000 since rounding with the equation
+	// does not give us an accurate enough value. For 11025 and 22050
+	// the equation gives us the best answer. All other frequencies will
+	// also use the equation. JDW
+	if (rate == 44100)
+		delta = 0xeb3;
+	else if (rate == 8000)
+		delta = 0x2ab;
+	else if (rate == 48000)
+		delta = 0x1000;
+	else
+		delta = (((rate << 12) + rate) / 48000) & 0x0000ffff;
+	return delta;
+}
+
+/*---------------------------------------------------------------------------
+   trident_set_dac_rate
+  
+   Description: This routine will set the sample rate for playback.
+  
+   Paramters:   trident - pointer to target device class for 4DWave.
+                rate    - desired sample rate
+                set     - actually write hardware if set is true.
+  
+   Returns:     The rate allowed by device.
+  
+  ---------------------------------------------------------------------------*/
+
+static unsigned int trident_set_dac_rate(struct trident_state * trident,
+					     unsigned int rate, int set)
+{
+	unsigned int delta;
+
+
+	if (rate > 48000)
+		rate = 48000;
+	if (rate < 4000)
+		rate = 4000;
+
+	delta = compute_rate(rate);
+
+	if(set)
+	{
+		//Select channel window
+		outb(trident->dma_dac.chan[1], TRID_REG(trident->card, T4D_LFO_GC_CIR));
+
+		if (trident->card->card_type != TYPE_4DWAVE_NX) 
+			outw(delta,TRID_REG(trident->card,CH_DX_ESO_DELTA+2));
+		else			// ID_4DWAVE_NX
+		{
+			outb( delta     & 0xff,TRID_REG(trident->card,CH_NX_DELTA_CSO));
+			outb((delta>>8) & 0xff,TRID_REG(trident->card,CH_NX_DELTA_ESO));
+		}
+	}
+	 
+	M_printk("(trident) called trident_set_dac_rate : rate = %d, set = %d delta = %4x\n", 
+			rate, set,delta);
+
+	trident->ratedac = rate;
+	return rate;
+}
+
+/*---------------------------------------------------------------------------
+   trident_set_adc_rate
+  
+   Description: This routine will set the sample rate for capture.
+  
+   Paramters:   trident - pointer to target device class for 4DWave.
+                rate    - desired sample rate
+                set     - actually write hardware if set is true.
+  
+   Returns:     The rate allowed by device.
+  
+  ---------------------------------------------------------------------------*/
+
+static unsigned int trident_set_adc_rate(struct trident_state * trident,
+					     unsigned int rate,
+					     int set)
+{
+	//snd_printk("trid: called trident_set_adc_rate\n");
+	if (rate > 48000)
+		rate = 48000;
+	if (rate < 4000)
+		rate = 4000;
+
+	trident->rateadc = rate;
+	/*
+	 *	FIXME: hit the hardware
+	 */
+
+	return rate;
+}
+ 
+extern __inline__ unsigned ld2(unsigned int x)
+{
+	unsigned r = 0;
+	
+	if (x >= 0x10000) {
+		x >>= 16;
+		r += 16;
+	}
+	if (x >= 0x100) {
+		x >>= 8;
+		r += 8;
+	}
+	if (x >= 0x10) {
+		x >>= 4;
+		r += 4;
+	}
+	if (x >= 4) {
+		x >>= 2;
+		r += 2;
+	}
+	if (x >= 2)
+		r++;
+	return r;
+}
+
+
+/* --------------------------------------------------------------------- */
+
+static struct trident_card *devs = NULL;
+
+/* --------------------------------------------------------------------- */
+
+
+/*
+ *	Trident AC97 codec programming interface.
+ */
+	 
+static void trident_ac97_set(struct trident_card *trident, u8 cmd, u16 val)
+{
+	unsigned int    address, data;
+	unsigned short  count  = 0xffff;
+
+	data = ((unsigned long)val) << 16;
+
+	if (trident->card_type != TYPE_4DWAVE_NX)
+	{
+		address = DX_ACR0_AC97_W;
+		/* read AC-97 write register status */
+		do
+		{
+			if ((inw(TRID_REG(trident,address))&0x8000) == 0)
+			break;
+		}
+		while(count--);
+
+		data |= (0x8000 | (cmd & 0x000000ff));
+	}
+	else  // ID_4DWAVE_NX
+	{
+		address = NX_ACR1_AC97_W;
+		/* read AC-97 write register status */
+		do
+		{
+			if ((inw(TRID_REG(trident, address )) & 0x0800) == 0)
+				break;
+		}
+		while (count--);
+		data |= (0x0800 | (cmd & 0x000000ff));
+	}
+	if (count == 0) 
+	{
+		printk(KERN_ERR "trident: AC97 CODEC write timed out.\n");
+		return;
+	}
+
+	outl(data, TRID_REG(trident, address));
+}
+
+static u16 trident_ac97_get(struct trident_card *trident, u8 cmd)
+{
+	unsigned int data = 0;
+	unsigned short count = 0xffff;
+
+	if (trident->card_type != TYPE_4DWAVE_NX)
+	{
+		data = (0x00008000L | (cmd & 0x000000ff));
+		outl(data, TRID_REG(trident, DX_ACR1_AC97_R));
+		do
+		{
+			data = inl(TRID_REG(trident, DX_ACR1_AC97_R));
+			if ( ( data & 0x0008000 ) == 0 )
+				break;
+		}
+		while(count--);
+	}
+	else  // ID_4DWAVE_NX
+	{
+		data = (0x00000800L | (cmd & 0x000000ff));
+		outl(data, TRID_REG(trident, NX_ACR2_AC97_R_PRIMARY));
+		do
+		{
+			data = inl(TRID_REG(trident, NX_ACR2_AC97_R_PRIMARY));
+			if ( ( data & 0x00000C00 ) == 0 )
+				break;
+		}
+		while(count--);
+	}
+	if ( count == 0 ) 
+	{
+		printk("trident: AC97 CODEC read timed out.\n");
+		data = 0;
+	}
+	return ((unsigned short)(data >> 16));
+}
+
+/* OSS interface to the ac97s.. */
+
+#define AC97_STEREO_MASK (SOUND_MASK_VOLUME|\
+	SOUND_MASK_PCM|SOUND_MASK_LINE|SOUND_MASK_CD|\
+	SOUND_MASK_VIDEO|SOUND_MASK_LINE1|SOUND_MASK_IGAIN)
+
+#define AC97_SUPPORTED_MASK (AC97_STEREO_MASK | \
+	SOUND_MASK_BASS|SOUND_MASK_TREBLE|SOUND_MASK_MIC|\
+	SOUND_MASK_SPEAKER)
+
+#define AC97_RECORD_MASK (SOUND_MASK_MIC|\
+	SOUND_MASK_CD| SOUND_MASK_VIDEO| SOUND_MASK_LINE1| SOUND_MASK_LINE|\
+	SOUND_MASK_PHONEIN)
+
+#define supported_mixer(CARD,FOO) ( CARD->mix.supported_mixers & (1<<FOO) )
+
+/* this table has default mixer values for all OSS mixers.
+	be sure to fill it in if you add oss mixers
+	to anyone's supported mixer defines */
+
+/* possible __init */
+static struct mixer_defaults {
+	int mixer;
+	unsigned int value;
+} mixer_defaults[SOUND_MIXER_NRDEVICES] = {
+		/* all values 0 -> 100 in bytes */
+	{SOUND_MIXER_VOLUME,	0x3232},
+	{SOUND_MIXER_BASS,	0x3232},
+	{SOUND_MIXER_TREBLE,	0x3232},
+	{SOUND_MIXER_SPEAKER,	0x3232},
+	{SOUND_MIXER_MIC,	0x3232},
+	{SOUND_MIXER_LINE,	0x3232},
+	{SOUND_MIXER_CD,	0x3232},
+	{SOUND_MIXER_VIDEO,	0x3232},
+	{SOUND_MIXER_LINE1,	0x3232},
+	{SOUND_MIXER_PCM,	0x3232},
+	{SOUND_MIXER_IGAIN,	0x3232},
+	{-1,0}
+};
+	
+static struct ac97_mixer_hw {
+	unsigned char offset;
+	int scale;
+} ac97_hw[SOUND_MIXER_NRDEVICES]= {
+	[SOUND_MIXER_VOLUME]	=	{0x02,63},
+	[SOUND_MIXER_BASS]	=	{0x08,15},
+	[SOUND_MIXER_TREBLE]	=	{0x08,15},
+	[SOUND_MIXER_SPEAKER]	=	{0x0a,15},
+	[SOUND_MIXER_MIC]	=	{0x0e,31},
+	[SOUND_MIXER_LINE]	=	{0x10,31},
+	[SOUND_MIXER_CD]	=	{0x12,31},
+	[SOUND_MIXER_VIDEO]	=	{0x14,31},
+	[SOUND_MIXER_LINE1]	=	{0x16,31},
+	[SOUND_MIXER_PCM]	=	{0x18,31},
+	[SOUND_MIXER_IGAIN]	=	{0x1c,31}
+};
+
+#if 0 /* *shrug* removed simply because we never used it.
+		feel free to implement again if needed */
+
+/* reads the given OSS mixer from the ac97
+	the caller must have insured that the ac97 knows
+	about that given mixer, and should be holding a
+	spinlock for the card */
+static int ac97_read_mixer(struct trident_card *card, int mixer) 
+{
+	u16 val;
+	int ret=0;
+	struct ac97_mixer_hw *mh = &ac97_hw[mixer];
+
+	val = trident_ac97_get(card , mh->offset);
+
+	if(AC97_STEREO_MASK & (1<<mixer)) {
+		/* nice stereo mixers .. */
+		int left,right;
+
+		left = (val >> 8)  & 0x7f;
+		right = val  & 0x7f;
+
+		if (mixer == SOUND_MIXER_IGAIN) {
+			right = (right * 100) / mh->scale;
+			left = (left * 100) / mh->scale;
+		else {
+			right = 100 - ((right * 100) / mh->scale);
+			left = 100 - ((left * 100) / mh->scale);
+		}
+
+		ret = left | (right << 8);
+	} else if (mixer == SOUND_MIXER_SPEAKER) {
+		ret = 100 - ((((val & 0x1e)>>1) * 100) / mh->scale);
+	} else if (mixer == SOUND_MIXER_MIC) {
+		ret = 100 - (((val & 0x1f) * 100) / mh->scale);
+	/*  the low bit is optional in the tone sliders and masking
+		it lets is avoid the 0xf 'bypass'.. */
+	} else if (mixer == SOUND_MIXER_BASS) {
+		ret = 100 - ((((val >> 8) & 0xe) * 100) / mh->scale);
+	} else if (mixer == SOUND_MIXER_TREBLE) {
+		ret = 100 - (((val & 0xe) * 100) / mh->scale);
+	}
+
+	printk("read mixer %d (0x%x) %x -> %x\n",mixer,mh->offset,val,ret);
+
+	return ret;
+}
+#endif
+
+/* write the OSS encoded volume to the given OSS encoded mixer,
+	again caller's job to make sure all is well in arg land,
+	call with spinlock held */
+static void ac97_write_mixer(struct trident_card *card, int mixer, unsigned int left, unsigned int right)
+{
+	u16 val=0;
+	struct ac97_mixer_hw *mh = &ac97_hw[mixer];
+
+	printk("(trident) wrote ac97 mixer %d (0x%x) %d,%d",mixer,mh->offset,left,right);
+
+	if(AC97_STEREO_MASK & (1<<mixer)) {
+		/* stereo mixers */
+
+
+		if (mixer == SOUND_MIXER_IGAIN) {
+			right = (right * mh->scale) / 100;
+			left = (left * mh->scale) / 100;
+		} else {
+			right = ((100 - right) * mh->scale) / 100;
+			left = ((100 - left) * mh->scale) / 100;
+		}
+
+		val = (left << 8) | right;
+
+	} else if (mixer == SOUND_MIXER_SPEAKER) {
+		val = (((100 - left) * mh->scale) / 100) << 1;
+	} else if (mixer == SOUND_MIXER_MIC) {
+		val = trident_ac97_get(card , mh->offset) & ~0x801f;
+		val |= (((100 - left) * mh->scale) / 100);
+	/*  the low bit is optional in the tone sliders and masking
+		it lets us avoid the 0xf 'bypass'.. */
+	} else if (mixer == SOUND_MIXER_BASS) {
+		val = trident_ac97_get(card , mh->offset) & ~0x0f00;
+		val |= ((((100 - left) * mh->scale) / 100) << 8) & 0x0e00;
+	} else if (mixer == SOUND_MIXER_TREBLE)  {
+		val = trident_ac97_get(card , mh->offset) & ~0x000f;
+		val |= (((100 - left) * mh->scale) / 100) & 0x000e;
+	}
+
+	trident_ac97_set(card, mh->offset, val);
+	
+	printk(" -> %x\n",val);
+}
+
+/* the following tables allow us to go from 
+	OSS <-> ac97 quickly. */
+
+enum ac97_recsettings {
+	AC97_REC_MIC=0,
+	AC97_REC_CD,
+	AC97_REC_VIDEO,
+	AC97_REC_AUX,
+	AC97_REC_LINE,
+	AC97_REC_STEREO, /* combination of all enabled outputs..  */
+	AC97_REC_MONO,        /*.. or the mono equivalent */
+	AC97_REC_PHONE        
+};
+
+static unsigned int ac97_rm2oss[] = {
+	[AC97_REC_MIC] = SOUND_MIXER_MIC, 
+	[AC97_REC_CD] = SOUND_MIXER_CD, 
+	[AC97_REC_VIDEO] = SOUND_MIXER_VIDEO, 
+	[AC97_REC_AUX] = SOUND_MIXER_LINE1, 
+	[AC97_REC_LINE] = SOUND_MIXER_LINE, 
+	[AC97_REC_PHONE] = SOUND_MIXER_PHONEIN
+};
+
+/* indexed by bit position */
+static unsigned int ac97_oss_rm[] = {
+	[SOUND_MIXER_MIC] = AC97_REC_MIC,
+	[SOUND_MIXER_CD] = AC97_REC_CD,
+	[SOUND_MIXER_VIDEO] = AC97_REC_VIDEO,
+	[SOUND_MIXER_LINE1] = AC97_REC_AUX,
+	[SOUND_MIXER_LINE] = AC97_REC_LINE,
+	[SOUND_MIXER_PHONEIN] = AC97_REC_PHONE
+};
+	
+/* read or write the recmask 
+	the ac97 can really have left and right recording
+	inputs independantly set, but OSS doesn't seem to 
+	want us to express that to the user. 
+	the caller guarantees that we have a supported bit set,
+	and they must be holding the card's spinlock */
+	
+static int ac97_recmask_io(struct trident_card *card, int rw, int mask) 
+{
+	unsigned int val;
+
+	if (rw) {
+		/* read it from the card */
+		val = trident_ac97_get(card, 0x1a) & 0x7;
+		return ac97_rm2oss[val];
+	}
+
+	/* else, write the first set in the mask as the
+		output */	
+
+	val = ffs(mask); 
+	val = ac97_oss_rm[val-1];
+	val |= val << 8;  /* set both channels */
+
+	printk("trident: setting ac97 recmask to 0x%x\n",val);
+
+	trident_ac97_set(card,0x1a,val);
+
+	return 0;
+};
+
+/*
+ *	Generic AC97 codec initialisation. Need to check there are no
+ *	quirks for the Trident cards.
+ */
+ 
+static u16 trident_ac97_init(struct trident_card *trident)
+{
+	trident->mix.supported_mixers = AC97_SUPPORTED_MASK;
+	trident->mix.stereo_mixers = AC97_STEREO_MASK;
+	trident->mix.record_sources = AC97_RECORD_MASK;
+/*	trident->mix.read_mixer = ac97_read_mixer;*/
+	trident->mix.write_mixer = ac97_write_mixer;
+	trident->mix.recmask_io = ac97_recmask_io;
+
+	if(trident->card_type == TYPE_4DWAVE_NX)
+	{
+		unsigned short VendorID1, VendorID2;
+
+		outl(0x02, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+
+		// 4 Speaker Codec initialization
+		VendorID1 = trident_ac97_get(trident, AC97_VENDOR_ID1);
+		VendorID2 = trident_ac97_get(trident, AC97_VENDOR_ID2);
+
+		if( (VendorID1 == 0x8384) && (VendorID2 == 0x7608) )
+		{
+			// Sigmatel 9708.
+            
+                	unsigned short TestReg;
+                	unsigned int DTemp;
+
+                	trident_ac97_set(trident, AC97_SIGMATEL_CIC1, 0xABBAL);  
+                	trident_ac97_set(trident, AC97_SIGMATEL_CIC2, 0x1000L);
+
+			TestReg = trident_ac97_get(trident, AC97_SIGMATEL_BIAS2);
+
+			if( TestReg != 0x8000 ) // Errata Notice.
+			{
+				trident_ac97_set(trident, AC97_SIGMATEL_BIAS1, 0xABBAL );
+				trident_ac97_set(trident, AC97_SIGMATEL_BIAS2, 0x0007L );
+			}
+			else // Newer version
+			{
+				trident_ac97_set(trident, AC97_SIGMATEL_CIC2, 0x1001L );  // recommended
+				trident_ac97_set(trident, AC97_SIGMATEL_DAC2INVERT, 0x0008L );
+			}
+
+			trident_ac97_set( trident, AC97_SURROUND_MASTER, 0x0000L );
+			trident_ac97_set( trident, AC97_HEADPHONE_VOL,   0x8000L );
+
+			DTemp = (unsigned int)trident_ac97_get( trident, AC97_GENERAL_PURPOSE );
+			trident_ac97_set( trident, AC97_GENERAL_PURPOSE, DTemp & 0x0000FDFFL );  // bit9 = 0.
+
+			DTemp = (unsigned int)trident_ac97_get( trident, AC97_MIC_VOL );
+			trident_ac97_set( trident, AC97_MIC_VOL, DTemp | 0x00008000L );  // bit15 = 1.
+
+			DTemp = inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+			outl(DTemp | 0x0010, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+
+		}
+		else if((VendorID1 == 0x5452) && (VendorID2 == 0x4108) )
+		{   // TriTech TR28028
+			trident_ac97_set( trident, AC97_SURROUND_MASTER, 0x0000L );
+	                trident_ac97_set( trident, AC97_EXTENDED_STATUS, 0x0000L );
+		}
+		else if((VendorID1 == 0x574D) && 
+                	(VendorID2 >= 0x4C00) && (VendorID2 <= 0x4C0f))
+		{   // Wolfson WM9704
+			trident_ac97_set( trident, AC97_SURROUND_MASTER, 0x0000L );
+		}
+		else
+		{
+#if 0
+			printk("trident: No four Speaker Support with on board CODEC\n") ;
+#endif
+		}
+
+		// S/PDIF C Channel bits 0-31 : 48khz, SCMS disabled
+		outl(0x200004, TRID_REG(trident, NX_SPCSTATUS));
+		// Enable S/PDIF out, 48khz only from ac97 fifo
+		outb(0x28, TRID_REG(trident, NX_SPCTRL_SPCSO+3));
+	}
+        else
+        {
+		outl(0x02, TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
+        }
+	return 0;
+}
+
+
+/* this only fixes the output apu mode to be later set by start_dac and
+	company.  output apu modes are set in trident_rec_setup */
+static void set_fmt(struct trident_state *s, unsigned char mask, unsigned char data)
+{
+	s->fmt = (s->fmt & mask) | data;
+	/* Set the chip ? */
+}
+
+
+/*
+ *	Native play back driver 
+ */
+
+/* the mode passed should be already shifted and masked */
+
+static void trident_play_setup(struct trident_state *trident, int mode, u32 rate, void *buffer, int size)
+{
+	unsigned int LBA;
+	unsigned int Delta;
+	unsigned int ESO;
+	unsigned int CTRL;
+	unsigned int FMC_RVOL_CVOL;
+	unsigned int GVSEL;
+	unsigned int PAN;
+	unsigned int VOL;
+	unsigned int EC;
+
+
+
+	/* set Loop Back Address */
+	LBA = virt_to_bus(buffer);
+
+	Delta = compute_rate(rate);
+
+	M_printk("(trident) rate, delta = %d %d\n", rate, Delta);
+
+	/* set ESO */
+	ESO = size;
+	if (mode & TRIDENT_FMT_16BIT)
+		ESO /= 2;
+	if (mode & TRIDENT_FMT_STEREO)
+		ESO /= 2;
+
+	ESO = ESO - 1;
+	//snd_printk("trid: ESO = %d\n", ESO);
+
+	/* set ctrl mode
+	   CTRL default: 8-bit (unsigned) mono, loop mode enabled
+	 */
+	CTRL = 0x00000001;
+	if (mode & TRIDENT_FMT_16BIT)
+	{
+		CTRL |= 0x00000008;	// 16-bit data
+		CTRL |= 0x00000002;	// signed data
+	}
+	if (mode&TRIDENT_FMT_STEREO)
+		CTRL |= 0x00000004;	// stereo data
+
+	//FMC_RVOL_CVOL = 0x0000c000;
+	FMC_RVOL_CVOL = 0x0000ffff;
+	GVSEL = 1;
+	PAN = 0;
+	VOL = 0;
+	EC = 0;
+
+	trident_write_voice_regs(trident->card,
+			     trident->dma_dac.chan[1],
+			     LBA,
+			     0,	/* cso */
+			     ESO,
+			     Delta,
+			     0,	/* alpha */
+			     FMC_RVOL_CVOL,
+			     GVSEL,
+			     PAN,
+			     VOL,
+			     CTRL,
+			     EC);
+
+}
+
+/*
+ *	Native record driver 
+ */
+
+/* again, passed mode is alrady shifted/masked */
+
+static void trident_rec_setup(struct trident_state *trident, int mode, u32 rate, void *buffer, int size)
+{
+	unsigned int LBA;
+	unsigned int Delta;
+	unsigned int ESO;
+	unsigned int CTRL;
+	unsigned int FMC_RVOL_CVOL;
+	unsigned int GVSEL;
+	unsigned int PAN;
+	unsigned int VOL;
+	unsigned int EC;
+	unsigned char bValue;
+	unsigned short wValue;
+	unsigned int dwValue;
+	unsigned short wRecCODECSamples;
+	unsigned int dwChanFlags;
+	struct trident_card *card = trident->card;
+
+	// Enable AC-97 ADC (capture), disable capture interrupt
+	if (trident->card->card_type != TYPE_4DWAVE_NX) 
+	{
+		bValue = inb(TRID_REG(card, DX_ACR2_AC97_COM_STAT));
+		outb(bValue | 0x48, TRID_REG(card, DX_ACR2_AC97_COM_STAT));
+	}
+	else 
+	{
+		wValue = inw(TRID_REG(card, T4D_MISCINT));
+		outw(wValue | 0x1000, TRID_REG(card, T4D_MISCINT));
+	}
+
+	// Initilize the channel and set channel Mode
+	outb(0, TRID_REG(card, LEGACY_DMAR15));
+
+	// Set DMA channel operation mode register
+	bValue = inb(TRID_REG(card, LEGACY_DMAR11)) & 0x03;
+	outb(bValue | 0x54, TRID_REG(card, LEGACY_DMAR11));
+
+	// Set channel buffer Address
+	LBA = virt_to_bus(buffer);
+	outl(LBA, TRID_REG(card, LEGACY_DMAR0));
+
+	/* set ESO */
+	ESO = size;
+
+	dwValue = inl(TRID_REG(card, LEGACY_DMAR4)) & 0xff000000;
+	dwValue |= (ESO - 1) & 0x0000ffff;
+	outl(dwValue, TRID_REG(card, LEGACY_DMAR4));
+
+	// Set channel sample rate , 4.12 format
+	dwValue = (((unsigned int) 48000L << 12) / (unsigned long) (rate));
+	outw((unsigned short) dwValue, TRID_REG(card, T4D_SBDELTA_DELTA_R));
+
+	// Set channel interrupt blk length
+	if (mode & TRIDENT_FMT_16BIT) {
+		wRecCODECSamples = (unsigned short) ((ESO >> 1) - 1);
+		dwChanFlags = 0xffffb000;
+	} else {
+		wRecCODECSamples = (unsigned short) (ESO - 1);
+		dwChanFlags = 0xffff1000;
+	}
+
+	dwValue = ((unsigned int) wRecCODECSamples) << 16;
+	dwValue |= (unsigned int) (wRecCODECSamples) & 0x0000ffff;
+	outl(dwValue, TRID_REG(card, T4D_SBBL_SBCL));
+
+	// Right now, set format and start to run capturing, 
+	// continuous run loop enable.
+	trident->bDMAStart = 0x19;	// 0001 1001b
+
+	if (mode & TRIDENT_FMT_16BIT)
+		trident->bDMAStart |= 0xa0;
+	if (mode & TRIDENT_FMT_STEREO)
+		trident->bDMAStart |= 0x40;
+
+	// Prepare capture intr channel
+
+	Delta = ((((unsigned int) rate) << 12) / ((unsigned long) (48000L)));
+
+	/* set Loop Back Address */
+	LBA = virt_to_bus(buffer);
+
+	/* set ESO */
+	ESO = size;
+	if (mode & TRIDENT_FMT_16BIT)
+		ESO /= 2;
+	if (mode & TRIDENT_FMT_STEREO)
+		ESO /= 2;
+
+	ESO = ESO - 1;
+	//snd_printk("trid: ESO = %d\n", ESO);
+
+	/* set ctrl mode
+	   CTRL default: 8-bit (unsigned) mono, loop mode enabled
+	 */
+	CTRL = 0x00000001;
+	if (mode & TRIDENT_FMT_16BIT)
+		CTRL |= 0x00000008;	// 16-bit data
+	/* XXX DO UNSIGNED XXX */
+	//if (!(mode & SND_PCM1_MODE_U))
+	//	CTRL |= 0x00000002;	// signed data
+	if (mode& TRIDENT_FMT_STEREO)
+		CTRL |= 0x00000004;	// stereo data
+
+	FMC_RVOL_CVOL = 0x0000ffff;
+	GVSEL = 1;
+	PAN = 0xff;
+	VOL = 0xff;
+	EC = 0;
+
+	trident_write_voice_regs(card,
+			     trident->dma_adc.chan[0],
+			     LBA,
+			     0,	/* cso */
+			     ESO,
+			     Delta,
+			     0,	/* alpha */
+			     FMC_RVOL_CVOL,
+			     GVSEL,
+			     PAN,
+			     VOL,
+			     CTRL,
+			     EC);
+
+}
+
+/* Playback pointer */
+__inline__ unsigned int get_dmaa(struct trident_state *trident)
+{
+	unsigned int cso;
+	unsigned int eso;
+
+#if 0
+	if (!(trident->enable & ADC_RUNNING))
+		return 0;
+#endif
+
+	outb(trident->dma_dac.chan[1], TRID_REG(trident->card, T4D_LFO_GC_CIR));
+
+	if (trident->card->card_type != TYPE_4DWAVE_NX) 
+	{
+		cso = inw(TRID_REG(trident->card, CH_DX_CSO_ALPHA_FMS + 2));
+		eso = inw(TRID_REG(trident->card, CH_DX_ESO_DELTA + 2));
+	}
+	else			// ID_4DWAVE_NX
+	{
+		cso = (unsigned int) inl(TRID_REG(trident->card, CH_NX_DELTA_CSO)) & 0x00ffffff;
+		eso = (unsigned int) inl(TRID_REG(trident->card, CH_NX_DELTA_ESO)) & 0x00ffffff;
+	}
+	M_printk("(trident) get_dmaa: chip reported %d.%d\n", cso, eso);
+	cso++;
+
+	if (cso > eso)
+		cso = eso;
+
+	if (trident->fmt & TRIDENT_FMT_16BIT)
+		cso *= 2;
+	if (trident->fmt & TRIDENT_FMT_STEREO)
+		cso *= 2;
+	return cso;
+}
+
+/* Record pointer */
+extern __inline__ unsigned get_dmac(struct trident_state *trident)
+{
+	unsigned int cso;
+
+	if (!(trident->enable&DAC_RUNNING))
+		return 0;
+
+	outb(trident->dma_adc.chan[0], TRID_REG(trident->card, T4D_LFO_GC_CIR));
+
+	if (trident->card->card_type != TYPE_4DWAVE_NX) {
+		cso = inw(TRID_REG(trident->card, CH_DX_CSO_ALPHA_FMS + 2));
+	} else {		// ID_4DWAVE_NX 
+		cso = (unsigned int) inl(TRID_REG(trident->card, CH_NX_DELTA_CSO)) & 0x00ffffff;
+	}
+
+	printk("(trident) get_dmac: chip reported %d\n", cso);
+	
+	cso++;
+
+	if (trident->fmt & TRIDENT_FMT_16BIT)
+		cso *= 2;
+	if (trident->fmt & TRIDENT_FMT_STEREO)
+		cso *= 2;
+	return cso;
+}
+
+static void trident_interrupt(int irq, void *dev_id, struct pt_regs *regs);
+
+static void trident_kick(unsigned long plop)
+{
+	trident_interrupt(5, (void *)plop, NULL);
+	debug_timer.expires=jiffies+1;
+	add_timer(&debug_timer);
+}
+
+/* Stop recording (lock held) */
+
+extern inline void __stop_adc(struct trident_state *s)
+{
+	struct trident_card *trident = s->card;
+
+	M_printk("(trident) stopping ADC\n");
+	
+
+	s->enable &= ~ADC_RUNNING;
+	trident_disable_voice_irq(trident, s->dma_adc.chan[0]);
+	outb(0x00, TRID_REG(trident, T4D_SBCTRL_SBE2R_SBDD));
+	trident_disable_voice_irq(trident, s->dma_adc.chan[0]);
+	trident_stop_voice(trident, s->dma_adc.chan[0]);
+	ResetAinten(trident, s->dma_adc.chan[0]);
+}	
+
+extern inline void stop_adc(struct trident_state *s)
+{
+	unsigned long flags;
+	struct trident_card *trident = s->card;
+
+	spin_lock_irqsave(&trident->lock, flags);
+	__stop_adc(s);
+	spin_unlock_irqrestore(&trident->lock, flags);
+}	
+
+/* stop playback (lock held) */
+
+extern inline void __stop_dac(struct trident_state *s)
+{
+	struct trident_card *trident = s->card;
+
+	M_printk("(trident) stopping DAC\n");
+	
+	//trident_stop_voice(trident, s->dma_dac.chan[0]);
+	//trident_disable_voice_irq(trident, s->dma_dac.chan[0]);
+	trident_stop_voice(trident, s->dma_dac.chan[1]);
+	trident_disable_voice_irq(trident, s->dma_dac.chan[1]);
+	s->enable &= ~DAC_RUNNING;
+}	
+
+extern inline void stop_dac(struct trident_state *s)
+{
+	struct trident_card *trident = s->card;
+	unsigned long flags;
+
+	spin_lock_irqsave(&trident->lock, flags);
+	__stop_dac(s);
+	spin_unlock_irqrestore(&trident->lock, flags);
+}	
+
+static void start_dac(struct trident_state *s)
+{
+	unsigned long flags;
+	struct trident_card *trident = s->card;
+
+	spin_lock_irqsave(&s->card->lock, flags);
+	if ((s->dma_dac.mapped || s->dma_dac.count > 0) && s->dma_dac.ready) 
+	{
+		s->enable |= DAC_RUNNING;
+		trident_enable_voice_irq(trident, s->dma_dac.chan[1]);
+		trident_start_voice(trident, s->dma_dac.chan[1]);
+		//trident_start_voice(trident, s->dma_dac.chan[0]);
+		M_printk("(trident) starting DAC\n");
+	
+	}
+	spin_unlock_irqrestore(&s->card->lock, flags);
+}	
+
+static void start_adc(struct trident_state *s)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&s->card->lock, flags);
+	if ((s->dma_adc.mapped || s->dma_adc.count < (signed)(s->dma_adc.dmasize - 2*s->dma_adc.fragsize)) 
+	    && s->dma_adc.ready) {
+		s->enable |= ADC_RUNNING;
+		trident_enable_voice_irq(s->card, s->dma_adc.chan[0]);
+		outb(s->bDMAStart, TRID_REG(s->card, T4D_SBCTRL_SBE2R_SBDD));
+		trident_start_voice(s->card, s->dma_adc.chan[0]);
+		M_printk("(trident) starting ADC\n");
+	
+	}
+	spin_unlock_irqrestore(&s->card->lock, flags);
+}	
+
+/* --------------------------------------------------------------------- */
+
+/* we allocate both buffers at once */
+#define DMABUF_DEFAULTORDER (15-PAGE_SHIFT)
+#define DMABUF_MINORDER 2
+
+static void dealloc_dmabuf(struct dmabuf *db)
+{
+	unsigned long map, mapend;
+
+	if (db->rawbuf) 
+	{
+		M_printk("(trident) freeing %p\n",db->rawbuf);
+		/* undo marking the pages as reserved */
+		mapend = MAP_NR(db->rawbuf + (PAGE_SIZE << db->buforder) - 1);
+		for (map = MAP_NR(db->rawbuf); map <= mapend; map++)
+			clear_bit(PG_reserved, &mem_map[map].flags);	
+		free_pages((unsigned long)db->rawbuf, db->buforder);
+	}
+	db->rawbuf = NULL;
+	db->mapped = db->ready = 0;
+}
+
+static int prog_dmabuf(struct trident_state *s, unsigned rec)
+{
+	struct dmabuf *db = rec ? &s->dma_adc : &s->dma_dac;
+	unsigned rate = rec ? s->rateadc : s->ratedac;
+	int order;
+	unsigned bytepersec;
+	unsigned bufs;
+	unsigned long map, mapend;
+	unsigned char fmt;
+	unsigned long flags;
+
+	spin_lock_irqsave(&s->card->lock, flags);
+	fmt = s->fmt;
+	if (rec) {
+		s->enable &= ~TRIDENT_ENABLE_RE;
+		fmt >>= TRIDENT_ADC_SHIFT;
+	} else {
+		s->enable &= ~TRIDENT_ENABLE_PE;
+		fmt >>= TRIDENT_DAC_SHIFT;
+	}
+	spin_unlock_irqrestore(&s->card->lock, flags);
+	fmt &= TRIDENT_FMT_MASK;
+
+	db->hwptr = db->swptr = db->total_bytes = db->count = db->error = db->endcleared = 0;
+
+	if (!db->rawbuf) {
+		void *rawbuf;
+		/* haha, this thing is hacked to hell and back.
+			this is so ugly. */
+		s->dma_dac.ready = s->dma_dac.mapped = 0;
+		s->dma_adc.ready = s->dma_adc.mapped = 0;
+
+		/* alloc as big a chunk as we can */
+		for (order = DMABUF_DEFAULTORDER; order >= DMABUF_MINORDER; order--)
+			if((rawbuf = (void *)__get_free_pages(GFP_KERNEL|GFP_DMA, order)))
+
+				break;
+
+		if (!rawbuf)
+			return -ENOMEM;
+
+
+		/* we allocated both buffers */
+		s->dma_adc.rawbuf = rawbuf;
+		s->dma_dac.rawbuf = rawbuf + ( PAGE_SIZE << (order - 1) );
+
+		M_printk("(trident) allocated %ld bytes at %p\n",PAGE_SIZE<<order, db->rawbuf);
+
+		s->dma_adc.buforder = s->dma_dac.buforder = order - 1;
+
+		/* XXX these checks are silly now */
+#if 0
+		if ((virt_to_bus(db->rawbuf) ^ (virt_to_bus(db->rawbuf) + (PAGE_SIZE << order) - 1)) & ~0xffff)
+			printk(KERN_DEBUG "trident: DMA buffer crosses 64k boundary: busaddr 0x%lx  size %ld\n", 
+			       virt_to_bus(db->rawbuf), PAGE_SIZE << order);
+
+#endif
+		if ((virt_to_bus(db->rawbuf) + (PAGE_SIZE << order) - 1) & ~0xffffff)
+			M_printk(KERN_DEBUG "(trident) DMA buffer beyond 16MB: busaddr 0x%lx  size %ld\n", 
+			       virt_to_bus(db->rawbuf), PAGE_SIZE << order);
+
+		/* now mark the pages as reserved; otherwise remap_page_range doesn't do what we want */
+		mapend = MAP_NR(db->rawbuf + (PAGE_SIZE << order) - 1);
+		for (map = MAP_NR(db->rawbuf); map <= mapend; map++)
+			set_bit(PG_reserved, &mem_map[map].flags);
+	}
+	bytepersec = rate << sample_shift[fmt];
+	bufs = PAGE_SIZE << db->buforder;
+	if (db->ossfragshift) {
+		if ((1000 << db->ossfragshift) < bytepersec)
+			db->fragshift = ld2(bytepersec/1000);
+		else
+			db->fragshift = db->ossfragshift;
+	} else {
+	/*	lets hand out reasonable big ass buffers by default */
+		db->fragshift = (db->buforder + PAGE_SHIFT -2);
+#if 0
+		db->fragshift = ld2(bytepersec/100/(db->subdivision ? db->subdivision : 1));
+		if (db->fragshift < 3)
+			db->fragshift = 3; 
+#endif
+	}
+	db->numfrag = bufs >> db->fragshift;
+	while (db->numfrag < 4 && db->fragshift > 3) {
+		db->fragshift--;
+		db->numfrag = bufs >> db->fragshift;
+	}
+	db->fragsize = 1 << db->fragshift;
+	if (db->ossmaxfrags >= 4 && db->ossmaxfrags < db->numfrag)
+		db->numfrag = db->ossmaxfrags;
+	db->fragsamples = db->fragsize >> sample_shift[fmt];
+	db->dmasize = db->numfrag << db->fragshift;
+
+	memset(db->rawbuf, (fmt & TRIDENT_FMT_16BIT) ? 0 : 0x80, db->dmasize);
+
+	spin_lock_irqsave(&s->card->lock, flags);
+	if (rec) {
+		trident_rec_setup(s, fmt, s->rateadc, 
+			db->rawbuf, db->numfrag << db->fragshift);
+	} else {
+		trident_play_setup(s, fmt, s->ratedac, 
+			db->rawbuf, db->numfrag << db->fragshift);
+	}
+	spin_unlock_irqrestore(&s->card->lock, flags);
+	db->ready = 1;
+
+	return 0;
+}
+
+/* only called by trident_write */
+
+extern __inline__ void clear_advance(struct trident_state *s)
+{
+	unsigned char c = ((s->fmt >> TRIDENT_DAC_SHIFT) & TRIDENT_FMT_16BIT) ? 0 : 0x80;
+	unsigned char *buf = s->dma_dac.rawbuf;
+	unsigned bsize = s->dma_dac.dmasize;
+	unsigned bptr = s->dma_dac.swptr;
+	unsigned len = s->dma_dac.fragsize;
+
+	if (bptr + len > bsize) {
+		unsigned x = bsize - bptr;
+		memset(buf + bptr, c, x);
+		/* account for wrapping? */
+		bptr = 0;
+		len -= x;
+	}
+	memset(buf + bptr, c, len);
+}
+
+/* call with spinlock held! */
+static void trident_update_ptr(struct trident_state *s)
+{
+	unsigned hwptr;
+	int diff;
+
+	/* update ADC pointer */
+	if (s->dma_adc.ready) {
+		hwptr = get_dmac(s) % s->dma_adc.dmasize;
+		diff = (s->dma_adc.dmasize + hwptr - s->dma_adc.hwptr) % s->dma_adc.dmasize;
+		s->dma_adc.hwptr = hwptr;
+		s->dma_adc.total_bytes += diff;
+		s->dma_adc.count += diff;
+		if (s->dma_adc.count >= (signed)s->dma_adc.fragsize) 
+			wake_up(&s->dma_adc.wait);
+		if (!s->dma_adc.mapped) {
+			if (s->dma_adc.count > (signed)(s->dma_adc.dmasize - ((3 * s->dma_adc.fragsize) >> 1))) {
+				s->enable &= ~TRIDENT_ENABLE_RE;
+				__stop_adc(s); 
+				s->dma_adc.error++;
+			}
+		}
+	}
+	/* update DAC pointer */
+	if (s->dma_dac.ready) 
+	{
+		/* this is so gross.  */
+		hwptr = (/*s->dma_dac.dmasize -*/ get_dmaa(s)) % s->dma_dac.dmasize; 
+		diff = (s->dma_dac.dmasize + hwptr - s->dma_dac.hwptr) % s->dma_dac.dmasize;
+		M_printk("(trident) updating dac: hwptr: %d diff: %d\n",hwptr,diff);
+		s->dma_dac.hwptr = hwptr;
+		s->dma_dac.total_bytes += diff;
+		if (s->dma_dac.mapped) 
+		{
+			s->dma_dac.count += diff;
+			if (s->dma_dac.count >= (signed)s->dma_dac.fragsize) 
+				wake_up(&s->dma_dac.wait);
+		} 
+		else 
+		{
+			s->dma_dac.count -= diff;
+			M_printk("(trident) trident_update_ptr: diff: %d, count: %d\n", diff, s->dma_dac.count); 
+			if (s->dma_dac.count <= 0) 
+			{
+				s->enable &= ~TRIDENT_ENABLE_PE;
+				/* Lock already held */
+				__stop_dac(s);
+				/* brute force everyone back in sync, sigh */
+				s->dma_dac.count = 0; 
+				s->dma_dac.swptr = 0; 
+				s->dma_dac.hwptr = 0; 
+				s->dma_dac.error++;
+			} 
+			else if (s->dma_dac.count <= (signed)s->dma_dac.fragsize && 
+					!s->dma_dac.endcleared) 
+			{
+				clear_advance(s);
+				s->dma_dac.endcleared = 1;
+			}
+
+			if (s->dma_dac.count + (signed)s->dma_dac.fragsize <= (signed)s->dma_dac.dmasize) 
+				wake_up(&s->dma_dac.wait);
+		}
+	}
+}
+
+/*
+ *	Trident interrupt handlers.
+ */
+ 
+static void trident_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+        struct trident_state *s;
+        struct trident_card *c = (struct trident_card *)dev_id;
+	int i;
+	u32 event;
+
+	spin_lock(&c->lock);
+	
+	event = inl(TRID_REG(c, T4D_MISCINT));
+	
+//	if(event & 0x28)
+//		printk("IRQ %04X (%08lX, %08lX)\n", event, c->iobase, TRID_REG(c, T4D_MISCINT));
+	
+	if(event & 8)
+	{
+		/* Midi - TODO */
+	}	
+	
+	if(event & 0x20)
+	{
+		/*
+		 *	Update the pointers for all channels we are running.
+		 */
+	 
+		for(i=0;i<NR_DSPS;i++)
+		{
+			s=&c->channels[i];
+			if(DidChannelInterrupt(c, i))
+			{
+				AckChannelInterrupt(c,i);
+				if(s->dev_audio != -1)
+					trident_update_ptr(s);
+				else
+				{
+					/* Spurious ? */
+					M_printk("(trident) spurious channel irq %d.\n", i);
+					trident_stop_voice(c, i);
+					trident_disable_voice_irq(c,i);
+				}
+			}
+		}
+		
+	}	
+	spin_unlock(&c->lock);
+}
+
+
+/* --------------------------------------------------------------------- */
+
+static const char invalid_magic[] = KERN_CRIT "trident: invalid magic value in %s\n";
+
+#define VALIDATE_MAGIC(FOO,MAG)                         \
+({                                                \
+	if (!(FOO) || (FOO)->magic != MAG) { \
+		printk(invalid_magic,__FUNCTION__);            \
+		return -ENXIO;                    \
+	}                                         \
+})
+
+#define VALIDATE_STATE(a) VALIDATE_MAGIC(a,TRIDENT_STATE_MAGIC)
+#define VALIDATE_CARD(a) VALIDATE_MAGIC(a,TRIDENT_CARD_MAGIC)
+
+static void set_mixer(struct trident_card *card,unsigned int mixer, unsigned int val ) 
+{
+	unsigned int left,right;
+	/* cleanse input a little */
+	right = ((val >> 8)  & 0xff) ;
+	left = (val  & 0xff) ;
+
+	if(right > 100) right = 100;
+	if(left > 100) left = 100;
+
+	card->mix.mixer_state[mixer]=(right << 8) | left;
+	card->mix.write_mixer(card,mixer,left,right);
+}
+
+static int mixer_ioctl(struct trident_card *card, unsigned int cmd, unsigned long arg)
+{
+	unsigned long flags;
+	int i, val=0;
+
+	VALIDATE_CARD(card);
+        if (cmd == SOUND_MIXER_INFO) {
+		mixer_info info;
+		strncpy(info.id, card_names[card->card_type], sizeof(info.id));
+		strncpy(info.name,card_names[card->card_type],sizeof(info.name));
+		info.modify_counter = card->mix.modcnt;
+		if (copy_to_user((void *)arg, &info, sizeof(info)))
+			return -EFAULT;
+		return 0;
+	}
+	if (cmd == SOUND_OLD_MIXER_INFO) {
+		_old_mixer_info info;
+		strncpy(info.id, card_names[card->card_type], sizeof(info.id));
+		strncpy(info.name,card_names[card->card_type],sizeof(info.name));
+		if (copy_to_user((void *)arg, &info, sizeof(info)))
+			return -EFAULT;
+		return 0;
+	}
+	if (cmd == OSS_GETVERSION)
+		return put_user(SOUND_VERSION, (int *)arg);
+
+	if (_IOC_TYPE(cmd) != 'M' || _IOC_SIZE(cmd) != sizeof(int))
+                return -EINVAL;
+
+        if (_IOC_DIR(cmd) == _IOC_READ) {
+                switch (_IOC_NR(cmd)) {
+                case SOUND_MIXER_RECSRC: /* give them the current record source */
+
+			if(!card->mix.recmask_io) {
+				val = 0;
+			} else {
+				spin_lock_irqsave(&card->lock, flags);
+				val = card->mix.recmask_io(card,1,0);
+				spin_unlock_irqrestore(&card->lock, flags);
+			}
+			break;
+			
+                case SOUND_MIXER_DEVMASK: /* give them the supported mixers */
+			val = card->mix.supported_mixers;
+			break;
+
+                case SOUND_MIXER_RECMASK: /* Arg contains a bit for each supported recording source */
+			val = card->mix.record_sources;
+			break;
+			
+                case SOUND_MIXER_STEREODEVS: /* Mixer channels supporting stereo */
+			val = card->mix.stereo_mixers;
+			break;
+			
+                case SOUND_MIXER_CAPS:
+			val = SOUND_CAP_EXCL_INPUT;
+			break;
+
+		default: /* read a specific mixer */
+			i = _IOC_NR(cmd);
+
+			if ( ! supported_mixer(card,i)) 
+				return -EINVAL;
+
+			/* do we ever want to touch the hardware? */
+/*			spin_lock_irqsave(&s->lock, flags);
+			val = card->mix.read_mixer(card,i);
+			spin_unlock_irqrestore(&s->lock, flags);*/
+
+			val = card->mix.mixer_state[i];
+/*			printk("returned 0x%x for mixer %d\n",val,i);*/
+
+			break;
+		}
+		return put_user(val,(int *)arg);
+	}
+	
+        if (_IOC_DIR(cmd) != (_IOC_WRITE|_IOC_READ))
+		return -EINVAL;
+	
+	card->mix.modcnt++;
+
+	get_user_ret(val, (int *)arg, -EFAULT);
+
+	switch (_IOC_NR(cmd)) {
+	case SOUND_MIXER_RECSRC: /* Arg contains a bit for each recording source */
+
+		if (!card->mix.recmask_io) return -EINVAL;
+		if(! (val &= card->mix.record_sources)) return -EINVAL;
+
+		spin_lock_irqsave(&card->lock, flags);
+		card->mix.recmask_io(card,0,val);
+		spin_unlock_irqrestore(&card->lock, flags);
+		return 0;
+
+	default:
+		i = _IOC_NR(cmd);
+
+		if ( ! supported_mixer(card,i)) 
+			return -EINVAL;
+
+		spin_lock_irqsave(&card->lock, flags);
+		set_mixer(card,i,val);
+		spin_unlock_irqrestore(&card->lock, flags);
+
+		return 0;
+	}
+}
+
+/* --------------------------------------------------------------------- */
+
+static loff_t trident_llseek(struct file *file, loff_t offset, int origin)
+{
+	return -ESPIPE;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int trident_open_mixdev(struct inode *inode, struct file *file)
+{
+	int minor = MINOR(inode->i_rdev);
+	struct trident_card *card = devs;
+
+	while (card && card->dev_mixer != minor)
+		card = card->next;
+	if (!card)
+		return -ENODEV;
+
+	file->private_data = card;
+	//FIXME put back in
+	//MOD_INC_USE_COUNT;
+	return 0;
+}
+
+static int trident_release_mixdev(struct inode *inode, struct file *file)
+{
+	struct trident_card *card = (struct trident_card *)file->private_data;
+
+	VALIDATE_CARD(card);
+	
+	//FIXME put back in
+	//MOD_DEC_USE_COUNT;
+	return 0;
+}
+
+static int trident_ioctl_mixdev(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct trident_card *card = (struct trident_card *)file->private_data;
+
+	VALIDATE_CARD(card);
+
+	return mixer_ioctl(card, cmd, arg);
+}
+
+static /*const*/ struct file_operations trident_mixer_fops = {
+	&trident_llseek,
+	NULL,  /* read */
+	NULL,  /* write */
+	NULL,  /* readdir */
+	NULL,  /* poll */
+	&trident_ioctl_mixdev,
+	NULL,  /* mmap */
+	&trident_open_mixdev,
+	NULL,	/* flush */
+	&trident_release_mixdev,
+	NULL,  /* fsync */
+	NULL,  /* fasync */
+	NULL,  /* check_media_change */
+	NULL,  /* revalidate */
+	NULL,  /* lock */
+};
+
+/* --------------------------------------------------------------------- */
+
+static int drain_dac(struct trident_state *s, int nonblock)
+{
+        DECLARE_WAITQUEUE(wait, current);
+	unsigned long flags;
+	int count;
+	signed long tmo;
+
+	if (s->dma_dac.mapped || !s->dma_dac.ready)
+		return 0;
+	current->state = TASK_INTERRUPTIBLE;
+	add_wait_queue(&s->dma_dac.wait, &wait);
+
+	for (;;) 
+	{
+		spin_lock_irqsave(&s->card->lock, flags);
+		count = s->dma_dac.count;
+		spin_unlock_irqrestore(&s->card->lock, flags);
+
+		if (count <= 0)
+			break;
+
+		if (signal_pending(current))
+			break;
+
+		if (nonblock) 
+		{
+			remove_wait_queue(&s->dma_dac.wait, &wait);
+			current->state = TASK_RUNNING;
+			return -EBUSY;
+		}
+
+		tmo = (count * HZ) / s->ratedac;
+		tmo >>= sample_shift[(s->fmt >> TRIDENT_DAC_SHIFT) & TRIDENT_FMT_MASK];
+
+		/* XXX this is just broken.  someone is waking us up alot, or schedule_timeout is broken.
+		or something.  who cares. - zach */
+		if (!schedule_timeout(tmo ? tmo : 1) && tmo)
+			printk(KERN_DEBUG "trident: dma timed out?? %ld\n",jiffies);
+	}
+	remove_wait_queue(&s->dma_dac.wait, &wait);
+	current->state = TASK_RUNNING;
+	if (signal_pending(current))
+		return -ERESTARTSYS;
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+/* in this loop, dma_adc.count signifies the amount of data thats waiting
+	to be copied to the user's buffer.  it is filled by the interrupt
+	handler and drained by this loop. */
+static ssize_t trident_read(struct file *file, char *buffer, size_t count, loff_t *ppos)
+{
+	struct trident_state *s = (struct trident_state *)file->private_data;
+	ssize_t ret;
+	unsigned long flags;
+	unsigned swptr;
+	int cnt;
+	
+	VALIDATE_STATE(s);
+	if (ppos != &file->f_pos)
+		return -ESPIPE;
+	if (s->dma_adc.mapped)
+		return -ENXIO;
+	if (!s->dma_adc.ready && (ret = prog_dmabuf(s, 1)))
+		return ret;
+	if (!access_ok(VERIFY_WRITE, buffer, count))
+		return -EFAULT;
+	ret = 0;
+
+	while (count > 0) {
+		spin_lock_irqsave(&s->card->lock, flags);
+		/* remember, all these things are expressed in bytes to be
+			sent to the user.. hence the evil / 2 down below */
+		swptr = s->dma_adc.swptr;
+		cnt = s->dma_adc.dmasize-swptr;
+		if (s->dma_adc.count < cnt)
+			cnt = s->dma_adc.count;
+		spin_unlock_irqrestore(&s->card->lock, flags);
+
+		if (cnt > count)
+			cnt = count;
+
+		if (cnt <= 0) {
+			start_adc(s);
+			if (file->f_flags & O_NONBLOCK) 
+			{
+				ret = ret ? ret : -EAGAIN;
+				return ret;
+			}
+			if (!interruptible_sleep_on_timeout(&s->dma_adc.wait, HZ)) {
+				M_printk(KERN_DEBUG "(trident) read: chip lockup? dmasz %u fragsz %u count %i hwptr %u swptr %u\n",
+				       s->dma_adc.dmasize, s->dma_adc.fragsize, s->dma_adc.count, 
+				       s->dma_adc.hwptr, s->dma_adc.swptr);
+				stop_adc(s);
+				spin_lock_irqsave(&s->card->lock, flags);
+//				set_dmac(s, virt_to_bus(s->dma_adc.rawbuf), s->dma_adc.numfrag << s->dma_adc.fragshift);
+				s->dma_adc.count = s->dma_adc.hwptr = s->dma_adc.swptr = 0;
+				spin_unlock_irqrestore(&s->card->lock, flags);
+			}
+			if (signal_pending(current)) 
+			{
+				ret = ret ? ret : -ERESTARTSYS;
+				return ret;
+			}
+			continue;
+		}
+	
+		if (copy_to_user(buffer, s->dma_adc.rawbuf + swptr, cnt)) {
+			ret = ret ? ret : -EFAULT;
+			return ret;
+		}
+
+		swptr = (swptr + cnt) % s->dma_adc.dmasize;
+		spin_lock_irqsave(&s->card->lock, flags);
+		s->dma_adc.swptr = swptr;
+		s->dma_adc.count -= cnt;
+		spin_unlock_irqrestore(&s->card->lock, flags);
+		count -= cnt;
+		buffer += cnt;
+		ret += cnt;
+		start_adc(s);
+	}
+
+	return ret;
+}
+
+static ssize_t trident_write(struct file *file, const char *buffer, size_t count, loff_t *ppos)
+{
+	struct trident_state *s = (struct trident_state *)file->private_data;
+	ssize_t ret;
+	unsigned long flags;
+	unsigned swptr;
+	int cnt;
+	int mode = (s->fmt >> TRIDENT_DAC_SHIFT) & TRIDENT_FMT_MASK;
+
+	M_printk("(trident) trident_write: count %d\n", count);
+	
+	VALIDATE_STATE(s);
+	if (ppos != &file->f_pos)
+		return -ESPIPE;
+	if (s->dma_dac.mapped)
+		return -ENXIO;
+	if (!s->dma_dac.ready && (ret = prog_dmabuf(s, 0)))
+		return ret;
+	if (!access_ok(VERIFY_READ, buffer, count))
+		return -EFAULT;
+	ret = 0;
+
+	while (count > 0) {
+		spin_lock_irqsave(&s->card->lock, flags);
+
+		if (s->dma_dac.count < 0) 
+		{
+			s->dma_dac.count = 0;
+			s->dma_dac.swptr = s->dma_dac.hwptr;
+		}
+		swptr = s->dma_dac.swptr;
+
+		cnt = s->dma_dac.dmasize-swptr;
+
+		if (s->dma_dac.count + cnt > s->dma_dac.dmasize)
+			cnt = s->dma_dac.dmasize - s->dma_dac.count;
+
+		spin_unlock_irqrestore(&s->card->lock, flags);
+
+		if (cnt > count)
+			cnt = count;
+
+		if (cnt <= 0) 
+		{
+			/* buffer is full, wait for it to be played */
+			start_dac(s);
+			if (file->f_flags & O_NONBLOCK) 
+			{
+				if(!ret) ret = -EAGAIN;
+				return ret;
+			}
+			if (!interruptible_sleep_on_timeout(&s->dma_dac.wait, HZ)) 
+			{
+				M_printk(KERN_DEBUG 
+						"trident: write: chip lockup? dmasz %u fragsz %u count %i hwptr %u swptr %u\n", 
+						s->dma_dac.dmasize, s->dma_dac.fragsize, s->dma_dac.count, s->dma_dac.hwptr, 
+						s->dma_dac.swptr);
+				stop_dac(s);
+				spin_lock_irqsave(&s->card->lock, flags);
+//			set_dmaa(s, virt_to_bus(s->dma_dac.rawbuf), s->dma_dac.numfrag << s->dma_dac.fragshift);
+				s->dma_dac.count = s->dma_dac.hwptr = s->dma_dac.swptr = 0;
+				spin_unlock_irqrestore(&s->card->lock, flags);
+			}
+			if (signal_pending(current)) 
+			{
+				if (!ret) ret = -ERESTARTSYS;
+				return ret;
+			}
+			continue;
+		}
+		if (copy_from_user(s->dma_dac.rawbuf + swptr, buffer, cnt)) 
+		{
+			if (!ret)
+				ret = -EFAULT;
+			return ret;
+		}
+
+		swptr = (swptr + cnt) % s->dma_dac.dmasize;
+
+		spin_lock_irqsave(&s->card->lock, flags);
+		s->dma_dac.swptr = swptr;
+		s->dma_dac.count += cnt;
+		s->dma_dac.endcleared = 0;
+		spin_unlock_irqrestore(&s->card->lock, flags);
+		count -= cnt;
+		buffer += cnt;
+		ret += cnt;
+		start_dac(s);
+	}
+	return ret;
+}
+
+static unsigned int trident_poll(struct file *file, struct poll_table_struct *wait)
+{
+	struct trident_state *s = (struct trident_state *)file->private_data;
+	unsigned long flags;
+	unsigned int mask = 0;
+
+	VALIDATE_STATE(s);
+	if (file->f_mode & FMODE_WRITE)
+		poll_wait(file, &s->dma_dac.wait, wait);
+	if (file->f_mode & FMODE_READ)
+		poll_wait(file, &s->dma_adc.wait, wait);
+	spin_lock_irqsave(&s->card->lock, flags);
+	trident_update_ptr(s);
+	if (file->f_mode & FMODE_READ) {
+		if (s->dma_adc.count >= (signed)s->dma_adc.fragsize)
+			mask |= POLLIN | POLLRDNORM;
+	}
+	if (file->f_mode & FMODE_WRITE) {
+		if (s->dma_dac.mapped) {
+			if (s->dma_dac.count >= (signed)s->dma_dac.fragsize) 
+				mask |= POLLOUT | POLLWRNORM;
+		} else {
+			if ((signed)s->dma_dac.dmasize >= s->dma_dac.count + (signed)s->dma_dac.fragsize)
+				mask |= POLLOUT | POLLWRNORM;
+		}
+	}
+	spin_unlock_irqrestore(&s->card->lock, flags);
+	return mask;
+}
+
+/* this needs to be fixed to deal with the dual apus/buffers */
+#if 0
+static int trident_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct trident_state *s = (struct trident_state *)file->private_data;
+	struct dmabuf *db;
+	int ret;
+	unsigned long size;
+
+	VALIDATE_STATE(s);
+	if (vma->vm_flags & VM_WRITE) {
+		if ((ret = prog_dmabuf(s, 1)) != 0)
+			return ret;
+		db = &s->dma_dac;
+	} else if (vma->vm_flags & VM_READ) {
+		if ((ret = prog_dmabuf(s, 0)) != 0)
+			return ret;
+		db = &s->dma_adc;
+	} else 
+		return -EINVAL;
+	if (vma->vm_offset != 0)
+		return -EINVAL;
+	size = vma->vm_end - vma->vm_start;
+	if (size > (PAGE_SIZE << db->buforder))
+		return -EINVAL;
+	if (remap_page_range(vma->vm_start, virt_to_phys(db->rawbuf), size, vma->vm_page_prot))
+		return -EAGAIN;
+	db->mapped = 1;
+	return 0;
+}
+#endif
+
+static int trident_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct trident_state *s = (struct trident_state *)file->private_data;
+	unsigned long flags;
+        audio_buf_info abinfo;
+        count_info cinfo;
+	int val, mapped, ret;
+	unsigned char fmtm, fmtd;
+
+/*	printk("trident: trident_ioctl: cmd %d\n", cmd);*/
+	
+	VALIDATE_STATE(s);
+  mapped = ((file->f_mode & FMODE_WRITE) && s->dma_dac.mapped) ||
+		((file->f_mode & FMODE_READ) && s->dma_adc.mapped);
+
+	switch (cmd) 
+	{
+		case OSS_GETVERSION:
+		return put_user(SOUND_VERSION, (int *)arg);
+
+		case SNDCTL_DSP_SYNC:
+			if (file->f_mode & FMODE_WRITE)
+				return drain_dac(s, file->f_flags & O_NONBLOCK);
+		return 0;
+
+		case SNDCTL_DSP_SETDUPLEX:
+		/* XXX fix */
+		return 0;
+
+		case SNDCTL_DSP_GETCAPS:
+			return put_user(0/*DSP_CAP_DUPLEX | DSP_CAP_REALTIME | DSP_CAP_TRIGGER | DSP_CAP_MMAP*/, (int *)arg);
+
+		case SNDCTL_DSP_RESET:
+			if (file->f_mode & FMODE_WRITE) 
+			{
+				stop_dac(s);
+				synchronize_irq();
+				s->dma_dac.swptr = s->dma_dac.hwptr = s->dma_dac.count = s->dma_dac.total_bytes = 0;
+			}
+			if (file->f_mode & FMODE_READ) 
+			{
+				stop_adc(s);
+				synchronize_irq();
+				s->dma_adc.swptr = s->dma_adc.hwptr = s->dma_adc.count = s->dma_adc.total_bytes = 0;
+			}
+		return 0;
+
+		case SNDCTL_DSP_SPEED:
+			get_user_ret(val, (int *)arg, -EFAULT);
+			if (val >= 0) 
+			{
+				if (file->f_mode & FMODE_READ) 
+				{
+					stop_adc(s);
+					s->dma_adc.ready = 0;
+					trident_set_adc_rate(s, val, 1);
+				}
+				if (file->f_mode & FMODE_WRITE) 
+				{
+					stop_dac(s);
+					s->dma_dac.ready = 0;
+					trident_set_dac_rate(s, val, 1);
+				}
+			}
+		return put_user((file->f_mode & FMODE_READ) ? s->rateadc : s->ratedac, (int *)arg);
+
+		case SNDCTL_DSP_STEREO:
+			get_user_ret(val, (int *)arg, -EFAULT);
+			fmtd = 0;
+			fmtm = ~0;
+			if (file->f_mode & FMODE_READ) 
+			{
+				stop_adc(s);
+				s->dma_adc.ready = 0;
+				if (val)
+					fmtd |= TRIDENT_FMT_STEREO << TRIDENT_ADC_SHIFT;
+				else
+					fmtm &= ~(TRIDENT_FMT_STEREO << TRIDENT_ADC_SHIFT);
+			}
+			if (file->f_mode & FMODE_WRITE) 
+			{
+				stop_dac(s);
+				s->dma_dac.ready = 0;
+				if (val)
+					fmtd |= TRIDENT_FMT_STEREO << TRIDENT_DAC_SHIFT;
+				else
+					fmtm &= ~(TRIDENT_FMT_STEREO << TRIDENT_DAC_SHIFT);
+			}
+			set_fmt(s, fmtm, fmtd);
+
+		return 0;
+
+		case SNDCTL_DSP_CHANNELS:
+			get_user_ret(val, (int *)arg, -EFAULT);
+			if (val != 0) 
+			{
+				fmtd = 0;
+				fmtm = ~0;
+
+				if (file->f_mode & FMODE_READ) 
+				{
+				stop_adc(s);
+				s->dma_adc.ready = 0;
+				if (val >= 2)
+					fmtd |= TRIDENT_FMT_STEREO << TRIDENT_ADC_SHIFT;
+				else
+					fmtm &= ~(TRIDENT_FMT_STEREO << TRIDENT_ADC_SHIFT);
+				}
+
+				if (file->f_mode & FMODE_WRITE) 
+				{
+					stop_dac(s);
+					s->dma_dac.ready = 0;
+					if (val >= 2)
+						fmtd |= TRIDENT_FMT_STEREO << TRIDENT_DAC_SHIFT;
+					else
+						fmtm &= ~(TRIDENT_FMT_STEREO << TRIDENT_DAC_SHIFT);
+				}
+				set_fmt(s, fmtm, fmtd);
+			}
+		return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? 
+							(TRIDENT_FMT_STEREO << TRIDENT_ADC_SHIFT) : 
+							(TRIDENT_FMT_STEREO << TRIDENT_DAC_SHIFT))) ? 2 : 1, (int *)arg);
+
+		case SNDCTL_DSP_GETFMTS: /* Returns a mask */
+		return put_user(AFMT_S8|AFMT_S16_LE, (int *)arg);
+
+		case SNDCTL_DSP_SETFMT: /* Selects ONE fmt*/
+			get_user_ret(val, (int *)arg, -EFAULT);
+			if (val != AFMT_QUERY) 
+			{
+				fmtd = 0;
+				fmtm = ~0;
+				if (file->f_mode & FMODE_READ) 
+				{
+					stop_adc(s);
+					s->dma_adc.ready = 0;
+					/* fixed at 16bit for now */
+					fmtd |= TRIDENT_FMT_16BIT << TRIDENT_ADC_SHIFT;
+#if 0
+					if (val == AFMT_S16_LE)
+					fmtd |= TRIDENT_FMT_16BIT << TRIDENT_ADC_SHIFT;
+					else
+					fmtm &= ~(TRIDENT_FMT_16BIT << TRIDENT_ADC_SHIFT);
+#endif
+				}
+				if (file->f_mode & FMODE_WRITE) 
+				{
+					stop_dac(s);
+					s->dma_dac.ready = 0;
+					if (val == AFMT_S16_LE)
+						fmtd |= TRIDENT_FMT_16BIT << TRIDENT_DAC_SHIFT;
+					else
+						fmtm &= ~(TRIDENT_FMT_16BIT << TRIDENT_DAC_SHIFT);
+				}
+				set_fmt(s, fmtm, fmtd);
+			}
+		return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? 
+			(TRIDENT_FMT_16BIT << TRIDENT_ADC_SHIFT) : 
+			(TRIDENT_FMT_16BIT << TRIDENT_DAC_SHIFT))) ? 
+			AFMT_S16_LE : AFMT_S8, (int *)arg);
+
+		case SNDCTL_DSP_POST:
+		return 0;
+
+		case SNDCTL_DSP_GETTRIGGER:
+			val = 0;
+			if (file->f_mode & FMODE_READ && s->enable & TRIDENT_ENABLE_RE) 
+				val |= PCM_ENABLE_INPUT;
+			if (file->f_mode & FMODE_WRITE && s->enable & TRIDENT_ENABLE_PE) 
+				val |= PCM_ENABLE_OUTPUT;
+		return put_user(val, (int *)arg);
+
+		case SNDCTL_DSP_SETTRIGGER:
+			get_user_ret(val, (int *)arg, -EFAULT);
+			if (file->f_mode & FMODE_READ) 
+			{
+				if (val & PCM_ENABLE_INPUT) 
+				{
+					if (!s->dma_adc.ready && (ret =  prog_dmabuf(s, 1)))
+						return ret;
+					start_adc(s);
+				} 
+				else
+					stop_adc(s);
+			}
+			if (file->f_mode & FMODE_WRITE) 
+			{
+				if (val & PCM_ENABLE_OUTPUT) 
+				{
+					if (!s->dma_dac.ready && (ret = prog_dmabuf(s, 0)))
+						return ret;
+					start_dac(s);
+				} 
+				else
+					stop_dac(s);
+			}
+		return 0;
+
+		case SNDCTL_DSP_GETOSPACE:
+			if (!(file->f_mode & FMODE_WRITE))
+				return -EINVAL;
+			if (!(s->enable & TRIDENT_ENABLE_PE) && (val = prog_dmabuf(s, 0)) != 0)
+				return val;
+			spin_lock_irqsave(&s->card->lock, flags);
+			trident_update_ptr(s);
+			abinfo.fragsize = s->dma_dac.fragsize;
+			abinfo.bytes = s->dma_dac.dmasize - s->dma_dac.count;
+			abinfo.fragstotal = s->dma_dac.numfrag;
+			abinfo.fragments = abinfo.bytes >> s->dma_dac.fragshift;      
+			spin_unlock_irqrestore(&s->card->lock, flags);
+		return copy_to_user((void *)arg, &abinfo, sizeof(abinfo)) ? -EFAULT : 0;
+
+		case SNDCTL_DSP_GETISPACE:
+			if (!(file->f_mode & FMODE_READ))
+			return -EINVAL;
+			if (!(s->enable & TRIDENT_ENABLE_RE) && (val = prog_dmabuf(s, 1)) != 0)
+			return val;
+			spin_lock_irqsave(&s->card->lock, flags);
+			trident_update_ptr(s);
+			abinfo.fragsize = s->dma_adc.fragsize;
+			abinfo.bytes = s->dma_adc.count;
+			abinfo.fragstotal = s->dma_adc.numfrag;
+			abinfo.fragments = abinfo.bytes >> s->dma_adc.fragshift;      
+			spin_unlock_irqrestore(&s->card->lock, flags);
+		return copy_to_user((void *)arg, &abinfo, sizeof(abinfo)) ? -EFAULT : 0;
+
+		case SNDCTL_DSP_NONBLOCK:
+			file->f_flags |= O_NONBLOCK;
+		return 0;
+
+		case SNDCTL_DSP_GETODELAY:
+			if (!(file->f_mode & FMODE_WRITE))
+				return -EINVAL;
+			spin_lock_irqsave(&s->card->lock, flags);
+			trident_update_ptr(s);
+			val = s->dma_dac.count;
+			spin_unlock_irqrestore(&s->card->lock, flags);
+		return put_user(val, (int *)arg);
+
+		case SNDCTL_DSP_GETIPTR:
+			if (!(file->f_mode & FMODE_READ))
+				return -EINVAL;
+			spin_lock_irqsave(&s->card->lock, flags);
+			trident_update_ptr(s);
+			cinfo.bytes = s->dma_adc.total_bytes;
+			cinfo.blocks = s->dma_adc.count >> s->dma_adc.fragshift;
+			cinfo.ptr = s->dma_adc.hwptr;
+			if (s->dma_adc.mapped)
+				s->dma_adc.count &= s->dma_adc.fragsize-1;
+			spin_unlock_irqrestore(&s->card->lock, flags);
+		return copy_to_user((void *)arg, &cinfo, sizeof(cinfo));
+
+		case SNDCTL_DSP_GETOPTR:
+			if (!(file->f_mode & FMODE_WRITE))
+				return -EINVAL;
+			spin_lock_irqsave(&s->card->lock, flags);
+			trident_update_ptr(s);
+			cinfo.bytes = s->dma_dac.total_bytes;
+			cinfo.blocks = s->dma_dac.count >> s->dma_dac.fragshift;
+			cinfo.ptr = s->dma_dac.hwptr;
+			if (s->dma_dac.mapped)
+				s->dma_dac.count &= s->dma_dac.fragsize-1;
+			spin_unlock_irqrestore(&s->card->lock, flags);
+		return copy_to_user((void *)arg, &cinfo, sizeof(cinfo));
+
+		case SNDCTL_DSP_GETBLKSIZE:
+			if (file->f_mode & FMODE_WRITE) 
+			{
+				if ((val = prog_dmabuf(s, 0)))
+					return val;
+				return put_user(s->dma_dac.fragsize, (int *)arg);
+			}
+			if ((val = prog_dmabuf(s, 1)))
+				return val;
+		return put_user(s->dma_adc.fragsize, (int *)arg);
+
+		case SNDCTL_DSP_SETFRAGMENT:
+			get_user_ret(val, (int *)arg, -EFAULT);
+			if (file->f_mode & FMODE_READ) 
+			{
+				s->dma_adc.ossfragshift = val & 0xffff;
+				s->dma_adc.ossmaxfrags = (val >> 16) & 0xffff;
+				if (s->dma_adc.ossfragshift < 4)
+					s->dma_adc.ossfragshift = 4;
+				if (s->dma_adc.ossfragshift > 15)
+					s->dma_adc.ossfragshift = 15;
+				if (s->dma_adc.ossmaxfrags < 4)
+					s->dma_adc.ossmaxfrags = 4;
+			}
+			if (file->f_mode & FMODE_WRITE) 
+			{
+				s->dma_dac.ossfragshift = val & 0xffff;
+				s->dma_dac.ossmaxfrags = (val >> 16) & 0xffff;
+				if (s->dma_dac.ossfragshift < 4)
+					s->dma_dac.ossfragshift = 4;
+				if (s->dma_dac.ossfragshift > 15)
+					s->dma_dac.ossfragshift = 15;
+				if (s->dma_dac.ossmaxfrags < 4)
+					s->dma_dac.ossmaxfrags = 4;
+			}
+		return 0;
+
+		case SNDCTL_DSP_SUBDIVIDE:
+			if ((file->f_mode & FMODE_READ && s->dma_adc.subdivision) || 
+					(file->f_mode & FMODE_WRITE && s->dma_dac.subdivision))
+				return -EINVAL;
+			get_user_ret(val, (int *)arg, -EFAULT);
+			if (val != 1 && val != 2 && val != 4)
+				return -EINVAL;
+			if (file->f_mode & FMODE_READ)
+				s->dma_adc.subdivision = val;
+			if (file->f_mode & FMODE_WRITE)
+				s->dma_dac.subdivision = val;
+		return 0;
+
+		case SOUND_PCM_READ_RATE:
+		return put_user((file->f_mode & FMODE_READ) ? s->rateadc : s->ratedac, (int *)arg);
+
+		case SOUND_PCM_READ_CHANNELS:
+		return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? 
+						(TRIDENT_FMT_STEREO << TRIDENT_ADC_SHIFT) : 
+						(TRIDENT_FMT_STEREO << TRIDENT_DAC_SHIFT))) ? 2 : 1, (int *)arg);
+
+		case SOUND_PCM_READ_BITS:
+		return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? 
+						(TRIDENT_FMT_16BIT << TRIDENT_ADC_SHIFT) : 
+						(TRIDENT_FMT_16BIT << TRIDENT_DAC_SHIFT))) ? 16 : 8, (int *)arg);
+
+		case SOUND_PCM_WRITE_FILTER:
+		case SNDCTL_DSP_SETSYNCRO:
+		case SOUND_PCM_READ_FILTER:
+		return -EINVAL;
+
+	}
+	return -EINVAL;
+}
+
+static int trident_open(struct inode *inode, struct file *file)
+{
+	int minor = MINOR(inode->i_rdev);
+	struct trident_card *c = devs;
+	struct trident_state *s = NULL, *sp;
+	int i;
+	unsigned char fmtm = ~0, fmts = 0;
+
+	/*
+	 *	Scan the cards and find the channel. We only
+	 *	do this at open time so it is ok
+	 */
+	 
+	while (c!=NULL)
+	{
+		for(i=0;i<NR_DSPS;i++)
+		{
+			sp=&c->channels[i];
+			if(sp->dev_audio < 0)
+				continue;
+			if((sp->dev_audio ^ minor) & ~0xf)
+				continue;
+			s=sp;
+		}
+		c=c->next;
+	}
+		
+	if (!s)
+		return -ENODEV;
+		
+	VALIDATE_STATE(s);
+	file->private_data = s;
+	/* wait for device to become free */
+	down(&s->open_sem);
+	while (s->open_mode & file->f_mode) 
+	{
+		if (file->f_flags & O_NONBLOCK) 
+		{
+			up(&s->open_sem);
+			return -EWOULDBLOCK;
+		}
+		up(&s->open_sem);
+		interruptible_sleep_on(&s->open_wait);
+		if (signal_pending(current))
+			return -ERESTARTSYS;
+		down(&s->open_sem);
+	}
+	if (file->f_mode & FMODE_READ) 
+	{
+/*
+		fmtm &= ~((TRIDENT_FMT_STEREO | TRIDENT_FMT_16BIT) << TRIDENT_ADC_SHIFT);
+		if ((minor & 0xf) == SND_DEV_DSP16)
+			fmts |= TRIDENT_FMT_16BIT << TRIDENT_ADC_SHIFT; */
+
+		fmtm = (TRIDENT_FMT_STEREO|TRIDENT_FMT_16BIT) << TRIDENT_ADC_SHIFT;
+
+		s->dma_adc.ossfragshift = s->dma_adc.ossmaxfrags = s->dma_adc.subdivision = 0;
+		trident_set_adc_rate(s, 8000, 0);
+	}
+	if (file->f_mode & FMODE_WRITE) 
+	{
+		fmtm &= ~((TRIDENT_FMT_STEREO | TRIDENT_FMT_16BIT) << TRIDENT_DAC_SHIFT);
+		if ((minor & 0xf) == SND_DEV_DSP16)
+			fmts |= TRIDENT_FMT_16BIT << TRIDENT_DAC_SHIFT;
+		s->dma_dac.ossfragshift = s->dma_dac.ossmaxfrags = s->dma_dac.subdivision = 0;
+		trident_set_dac_rate(s, 8000, 1);
+	}
+	set_fmt(s, fmtm, fmts);
+	s->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE);
+
+	up(&s->open_sem);
+	//FIXME put back in
+	//MOD_INC_USE_COUNT;
+	return 0;
+}
+
+static int trident_release(struct inode *inode, struct file *file)
+{
+	struct trident_state *s = (struct trident_state *)file->private_data;
+
+	VALIDATE_STATE(s);
+	if (file->f_mode & FMODE_WRITE)
+		drain_dac(s, file->f_flags & O_NONBLOCK);
+	down(&s->open_sem);
+	if (file->f_mode & FMODE_WRITE) {
+		stop_dac(s);
+	}
+	if (file->f_mode & FMODE_READ) {
+		stop_adc(s);
+	}
+
+	/* free our shared dma buffers */
+	dealloc_dmabuf(&s->dma_adc);
+	dealloc_dmabuf(&s->dma_dac);
+		
+	s->open_mode &= (~file->f_mode) & (FMODE_READ|FMODE_WRITE);
+	/* we're covered by the open_sem */
+	up(&s->open_sem);
+	wake_up(&s->open_wait);
+	//FIXME put back in
+	//MOD_DEC_USE_COUNT;
+	return 0;
+}
+
+static /*const*/ struct file_operations trident_audio_fops = {
+	&trident_llseek,
+	&trident_read,
+	&trident_write,
+	NULL,  /* readdir */
+	&trident_poll,
+	&trident_ioctl,
+	NULL,	/* XXX &trident_mmap, */
+	&trident_open,
+	NULL,	/* flush */
+	&trident_release,
+	NULL,  /* fsync */
+	NULL,  /* fasync */
+	NULL,  /* check_media_change */
+	NULL,  /* revalidate */
+	NULL,  /* lock */
+};
+
+#ifdef CONFIG_APM
+int trident_apm_callback(apm_event_t ae) {
+}
+#endif
+
+/* --------------------------------------------------------------------- */
+
+static int trident_install(struct pci_dev *pcidev, int card_type)
+{
+	u16 w;
+	u32 l;
+	unsigned long iobase;
+	int i;
+	struct trident_card *card;
+	struct trident_state *trident;
+	int num = 0;
+	u32 ChanDwordCount;
+	
+	iobase = pcidev->resource[0].start;
+	
+	if(check_region(iobase, 256))
+	{
+		M_printk(KERN_WARNING "(trident) can't allocate 256 bytes I/O at 0x%4.4lx\n", iobase);
+		return 0;
+	}
+
+	/* this was tripping up some machines */
+	if(pcidev->irq == 0) 
+	{
+		printk(KERN_WARNING "(trident) pci subsystem reports irq 0, this might not be correct.\n");
+	}
+
+	/* just to be sure */
+	pci_set_master(pcidev);
+	
+	pci_read_config_word(pcidev, PCI_COMMAND, &w);
+	if((w&(PCI_COMMAND_IO|PCI_COMMAND_MASTER)) != (PCI_COMMAND_IO|PCI_COMMAND_MASTER))
+	{
+		printk("(trident) BIOS did not enable I/O access.\n");
+		w|=PCI_COMMAND_IO|PCI_COMMAND_MASTER;
+		pci_write_config_word(pcidev, PCI_COMMAND,w);
+	}
+
+	card = kmalloc(sizeof(struct trident_card), GFP_KERNEL);
+
+	if(card == NULL)
+	{
+		printk(KERN_WARNING "(trident) out of memory\n");
+		return 0;
+	}
+	
+	memset(card, 0, sizeof(*card));
+
+#ifdef CONFIG_APM
+	printk("(trident) apm_reg_callback: %d\n",apm_register_callback(trident_apm_callback));
+#endif
+
+	card->iobase = iobase;
+	card->card_type = card_type;
+	card->irq = pcidev->irq;
+	card->next = devs;
+	card->magic = TRIDENT_CARD_MAGIC;
+	devs = card;
+
+	ChanDwordCount = card->ChanDwordCount = 2;
+	card->ChanPCM = 32;
+
+	card->ChRegs.lpChStart = card->ChRegs.data;
+	card->ChRegs.lpChStop = card->ChRegs.lpChStart + ChanDwordCount;
+	card->ChRegs.lpChAint = card->ChRegs.lpChStop + ChanDwordCount;
+	card->ChRegs.lpChAinten = card->ChRegs.lpChAint + ChanDwordCount;
+	card->ChRegs.lpAChStart = card->ChRegs.lpChAinten + ChanDwordCount;
+	card->ChRegs.lpAChStop = card->ChRegs.lpAChStart + ChanDwordCount;
+	card->ChRegs.lpAChAint = card->ChRegs.lpAChStop + ChanDwordCount;
+	card->ChRegs.lpAChAinten = card->ChRegs.lpAChAint + ChanDwordCount;
+	// Assign addresses.
+	card->ChRegs.lpAChStart[0] = T4D_START_A;
+	card->ChRegs.lpAChStop[0] = T4D_STOP_A;
+	card->ChRegs.lpAChAint[0] = T4D_AINT_A;
+	card->ChRegs.lpAChAinten[0] = T4D_AINTEN_A;
+
+
+	card->ChRegs.lpAChStart[1] = T4D_START_B;
+	card->ChRegs.lpAChStop[1] = T4D_STOP_B;
+	card->ChRegs.lpAChAint[1] = T4D_AINT_B;
+	card->ChRegs.lpAChAinten[1] = T4D_AINTEN_B;
+	
+
+	outl(0x00, TRID_REG(card, T4D_MUSICVOL_WAVEVOL));
+	trident_ac97_set(card, 0x0L  , 0L);
+	trident_ac97_set(card, 0x02L , 0L);
+	trident_ac97_set(card, 0x18L , 0L);
+
+	if(card->card_type == TYPE_4DWAVE_NX)
+	{
+	// Enable rear channels
+		outl(0x12, TRID_REG(card, NX_ACR0_AC97_COM_STAT));
+				// ...or not, since they sound ugly. :)
+				// outl(0x02, TRID_REG(card, NX_ACR0_AC97_COM_STAT));
+	// S/PDIF C Channel bits 0-31 : 48khz, SCMS disabled
+	// outl(0x200004, TRID_REG(card, NX_SPCSTATUS));
+	// Disable S/PDIF out, 48khz only from ac97 fifo
+		// outb(0x00, TRID_REG(card, NX_SPCTRL_SPCSO + 3));
+	}
+	else 
+	{
+		outl(0x02, TRID_REG(card, DX_ACR2_AC97_COM_STAT));
+	}
+     				        	        	        					 		        	                		
+	for(i=0;i<NR_DSPS;i++)
+	{
+		struct trident_state *s=&card->channels[i];
+
+		s->card = card;
+		init_waitqueue_head(&s->dma_adc.wait);
+		init_waitqueue_head(&s->dma_dac.wait);
+		init_waitqueue_head(&s->open_wait);
+		init_MUTEX(&s->open_sem);
+		s->magic = TRIDENT_STATE_MAGIC;
+		s->channel = i;		
+
+		if(s->dma_adc.ready || s->dma_dac.ready || s->dma_adc.rawbuf)
+			printk("(trident) BOTCH!\n");
+		
+		/*
+		 *	Now allocate the hardware resources
+		 */
+		
+		//s->dma_dac.chan[0] = AllocateChannelPCM(card);
+		s->dma_dac.chan[1] = AllocateChannelPCM(card);
+		//s->dma_adc.chan[0] = AllocateChannelPCM(card);
+		
+		/* register devices */
+		if ((s->dev_audio = register_sound_dsp(&trident_audio_fops, -1)) < 0)
+			break;
+	}
+	
+	num = i;
+	
+	/* clear the rest if we ran out of slots to register */
+	for(;i<NR_DSPS;i++)
+	{
+		struct trident_state *s=&card->channels[i];
+		s->dev_audio = -1;
+	}
+	
+	trident = &card->channels[0];
+
+	/*
+	 *	Ok card ready. Begin setup proper
+	 */
+
+	printk(KERN_INFO "(trident) Configuring %s found at IO 0x%04lX IRQ %d\n", 
+		card_names[card_type],card->iobase,card->irq);
+
+
+	/* stake our claim on the iospace */
+	request_region(iobase, 256, card_names[card_type]);
+	
+	/*
+	 *	Reset the CODEC
+	 */
+	 
+	trident_ac97_init(card);
+
+	if ((card->dev_mixer = register_sound_mixer(&trident_mixer_fops, -1)) < 0) 
+	{
+		printk("(trident) couldn't register mixer!\n");
+	} 
+	else 
+	{
+		int i;
+		for(i = 0 ; i < SOUND_MIXER_NRDEVICES ; i++) 
+		{
+			struct mixer_defaults *md = &mixer_defaults[i];
+
+			if(md->mixer == -1) 
+				break;
+			if(!supported_mixer(card,md->mixer)) 
+				continue;
+			set_mixer(card,md->mixer,md->value);
+		}
+	}
+	
+	if(request_irq(card->irq, trident_interrupt, SA_SHIRQ, card_names[card_type], card))
+	{
+		printk(KERN_ERR "(trident) unable to allocate irq %d,\n", card->irq);
+		unregister_sound_mixer(card->dev_mixer);
+		for(i=0;i<NR_DSPS;i++)
+		{
+			struct trident_state *s = &card->channels[i];
+			if(s->dev_audio != -1)
+				unregister_sound_dsp(s->dev_audio);
+		}
+		release_region(card->iobase, 256);		
+		kfree(card);
+		return 0;
+	}
+
+	init_timer(&debug_timer);	
+	debug_timer.function = trident_kick;
+	debug_timer.data = (unsigned long)card;
+	debug_timer.expires = jiffies+1;
+	
+//	add_timer(&debug_timer);
+
+	printk("(trident) %d channels configured.\n", num);
+
+	EnableEndInterrupts(card);
+	return 1; 
+}
+
+#ifdef MODULE
+int init_module(void)
+#else
+int __init init_trident(void)
+#endif
+{
+	struct pci_dev *pcidev = NULL;
+	int foundone = 0;
+
+	if (!pci_present())   /* No PCI bus in this machine! */
+		return -ENODEV;
+	printk(KERN_INFO "(trident) version " DRIVER_VERSION " time " __TIME__ " " __DATE__ "\n");
+
+	pcidev = NULL;
+
+	/*
+	 *	Find the 4DWave DX
+	 */
+
+	while( (pcidev = pci_find_device(PCI_VENDOR_ID_TRIDENT, PCI_DEVICE_ID_TRIDENT_4DWAVE_DX, pcidev))!=NULL
+			&&
+		( trident_install(pcidev, TYPE_4DWAVE_DX) )) {
+			foundone=1;
+	}
+
+	/*
+	 *	Find the 4DWave NX
+	 */
+
+	while((pcidev = pci_find_device(PCI_VENDOR_ID_TRIDENT, PCI_DEVICE_ID_TRIDENT_4DWAVE_NX, pcidev))!=NULL
+			&&
+		( trident_install(pcidev, TYPE_4DWAVE_NX) )) {
+			foundone=1;
+	}
+
+	if( ! foundone )
+		return -ENODEV;
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+#ifdef MODULE
+
+MODULE_AUTHOR("Alan Cox <alan@redhat.com>");
+MODULE_DESCRIPTION("Trident 4DWave Driver");
+#ifdef M_DEBUG
+MODULE_PARM(debug,"i");
+#endif
+
+void cleanup_module(void)
+{
+	struct trident_card *s;
+
+#ifdef CONFIG_APM
+	apm_unregister_callback(trident_apm_callback);
+#endif
+
+	del_timer(&debug_timer);
+	
+	while ((s = devs)) {
+		int i;
+		devs = devs->next;
+		
+		
+		/* Kill interrupts, and SP/DIF */
+		
+		DisableEndInterrupts(s);
+		if(s->card_type == TYPE_4DWAVE_NX)
+			outb(0x00, TRID_REG(s, NX_SPCTRL_SPCSO+3));
+	
+		free_irq(s->irq, s);
+		unregister_sound_mixer(s->dev_mixer);
+		for(i=0;i<NR_DSPS;i++)
+		{
+			struct trident_state *trident = &s->channels[i];
+			if(trident->dev_audio != -1)
+				unregister_sound_dsp(trident->dev_audio);
+		}
+		release_region(s->iobase, 256);
+		kfree(s);
+	}
+	printk("(trident) unloading\n");
+}
+
+#endif /* MODULE */
+
+

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