/*
 *  audio@tridentmicro.com
 *  Fri Feb 19 15:55:28 MST 1999
 *  Routines for control of Trident 4DWave (DX and NX) chip
 *
 *  BUGS:
 *
 *  TODO:
 *    ---
 *
 *   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.
 *
 */

#define __SND_OSS_COMPAT__
#define SND_MAIN_OBJECT_FILE
#include "../../include/driver.h"
#include "../../include/info.h"
#include "../../include/control.h"
#include "../../include/trident.h"


/*
 *  common I/O routines
 */


/*---------------------------------------------------------------------------
   unsigned short ReadAC97( void *private_data, unsigned short reg )
  
   Description: This routine will do all of the reading from the external
                CODEC (AC97).
  
   Parameters:  private_data - target dependent pointer to hw
                reg - CODEC register index, from AC97 Hal.
 
   returns:     16 bit value read from the AC97.
  
  ---------------------------------------------------------------------------*/
static unsigned short ReadAC97(void *private_data, unsigned short reg)
{
	unsigned int data = 0;
	unsigned short wCount = 0xffff;
	unsigned long flags;
	trident_t *trident = (trident_t *) private_data;

	spin_lock_irqsave(&trident->reg_lock, flags);
	if (!trident->isNX) {
		data = (0x00008000L | (reg & 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 (wCount--);

		if (wCount == 0) {
			snd_printk("trid: ReadAC97 ERROR ac97 register TIMEOUT!!!\n");
			data = 0;
		}
	} else			// ID_4DWAVE_NX
	 {
		data = (0x00000800L | (reg & 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 (wCount--);

		if (wCount == 0) {
			snd_printk("trid: ReadAC97 ERROR ac97 register TIMEOUT!!!\n");
			data = 0;
		}
	}

	spin_unlock_irqrestore(&trident->reg_lock, flags);
	//snd_printk("trid: called ReadAC97 reg=%x, data = %x\n", reg, (data >> 16));
	return ((unsigned short) (data >> 16));
}

/*---------------------------------------------------------------------------
   void WriteAC97( void *private_data, unsigned short reg, unsigned short data)
  
   Description: This routine will do all of the writing to the external
                CODEC (AC97).
  
   Parameters:  reg - CODEC register index, from AC97 Hal.
                data  - Lower 16 bits are the data to write to CODEC.
  
   returns:     TRUE if everything went ok, else FALSE.
  
  ---------------------------------------------------------------------------*/
static void WriteAC97(void *private_data, unsigned short reg, unsigned short data)
{
	unsigned int address, ulData;
	unsigned short wCount = 0xffff;
	unsigned long flags;
	trident_t *trident = (trident_t *) private_data;

	//snd_printk("trid: called WriteAC97, reg=%x, data=%x\n",reg,data);
	ulData = ((unsigned long) data) << 16;

	spin_lock_irqsave(&trident->reg_lock, flags);
	if (!trident->isNX) {
		address = DX_ACR0_AC97_W;
		/* read AC-97 write register status */
		do {
			if ((inw(TRID_REG(trident, address)) & 0x8000) == 0)
				break;
		}
		while (wCount--);

		if (wCount == 0) {
			snd_printk("trid: WriteAC97 ERROR Write ac97 register TIMEOUT!!!\n");
			spin_unlock_irqrestore(&trident->reg_lock, flags);
			return;
		}
		ulData |= (0x8000 | (reg & 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 (wCount--);

		if (wCount == 0) {
			snd_printk("trid: WriteAC97 ERROR Write ac97 register TIMEOUT!!!\n");
			spin_unlock_irqrestore(&trident->reg_lock, flags);
			return;
		}
		ulData |= (0x0800 | (reg & 0x000000ff));
	}

	outl(ulData, TRID_REG(trident, address));
	spin_unlock_irqrestore(&trident->reg_lock, flags);
}

/*---------------------------------------------------------------------------
   void ResetAinten( trident_t *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(trident_t * 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( trident_t *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(trident_t * 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));

	return (TRUE);
}

/*---------------------------------------------------------------------------
   void DisableEndInterrupts( trident_t *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(trident_t * 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));

	return (TRUE);
}

/*---------------------------------------------------------------------------
   void snd_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 snd_trident_enable_voice_irq(trident_t * 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);
}

/*---------------------------------------------------------------------------
   void snd_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 snd_trident_disable_voice_irq(trident_t * 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);
}

/*---------------------------------------------------------------------------
   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(trident_t * trident)
{
	int idx;

	if (trident->ChanPCMcnt >= trident->ChanPCM)
		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(trident_t *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--;
	}
}

/*---------------------------------------------------------------------------
   unsigned int AllocateChannelSynth( 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 AllocateChannelSynth(trident_t * trident)
{
	int idx;

	if (trident->synth.ChanSynthCount >= 64 - trident->ChanPCM)
		return -1;
	for (idx = 31; idx >= 0; idx--) {
		if (!(trident->ChanMap[1] & (1 << idx))) {
			trident->ChanMap[1] |= 1 << idx;
			trident->synth.ChanSynthCount++;
			return idx + 32;
		}
	}
	for (idx = 31; idx >= 0; idx--) {
		if (!(trident->ChanMap[0] & (1 << idx))) {
			trident->ChanMap[0] |= 1 << idx;
			trident->synth.ChanSynthCount++;
			return idx;
		}
	}
	return -1;
}

/*---------------------------------------------------------------------------
   void FreeChannelSynth( 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 FreeChannelSynth(trident_t *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->synth.ChanSynthCount--;
	}
}

/*---------------------------------------------------------------------------
   void snd_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 snd_trident_start_voice(trident_t * trident, unsigned int HwChannel)
{
	unsigned int x = HwChannel >> 5;
	unsigned int Data = 1 << (HwChannel & 0x1f);

	outl(Data, TRID_REG(trident, trident->ChRegs->lpAChStart[x]));
}

/*---------------------------------------------------------------------------
   void snd_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 snd_trident_stop_voice(trident_t * trident, unsigned int HwChannel)
{
	unsigned int x = HwChannel >> 5;
	unsigned int Data = 1 << (HwChannel & 0x1f);

	outl(Data, TRID_REG(trident, trident->ChRegs->lpAChStop[x]));
}

/*---------------------------------------------------------------------------
   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(trident_t * 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(trident_t * 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(trident_t * trident, unsigned int HwChannel, unsigned int Delta)
{

	outb(HwChannel, TRID_REG(trident, T4D_LFO_GC_CIR));

	if (!trident->isNX) {
		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(trident_t * 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));

	return TRUE;
}

/*---------------------------------------------------------------------------
   snd_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 snd_trident_write_voice_regs(trident_t * 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->isNX) {
		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;
}

/*---------------------------------------------------------------------------
   snd_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 snd_trident_set_dac_rate(trident_t * trident,
					     unsigned int rate,
					     int set)
{
	//snd_printk("trid: called snd_trident_set_dac_rate : rate = %d, set = %d\n", rate, set);
	if (rate > 48000)
		rate = 48000;
	if (rate < 4000)
		rate = 4000;
	return rate;
}

/*---------------------------------------------------------------------------
   snd_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 snd_trident_set_adc_rate(trident_t * trident,
					     unsigned int rate,
					     int set)
{
	//snd_printk("trid: called snd_trident_set_adc_rate\n");
	if (rate > 48000)
		rate = 48000;
	if (rate < 4000)
		rate = 4000;
	return rate;
}

/*
 *  PCM part
 */

/*---------------------------------------------------------------------------
   snd_trident_playback_ioctl
  
   Description: Device I/O control handler for playback parameters.
  
   Paramters:   subchn  - PCM subchannel class
                cmd     - what ioctl message to process
                arg     - additional message infoarg     
  
   Returns:     None
  
  ---------------------------------------------------------------------------*/

static int snd_trident_playback_ioctl(void *private_data,
				      snd_pcm_subchn_t * subchn,
				      unsigned int cmd,
				      unsigned long *arg)
{
	trident_t *trident = (trident_t *) private_data;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;

	//snd_printk("trid: called snd_trident_playback_ioctl\n");
	switch (cmd) {
	case SND_PCM1_IOCTL_RATE:
		subchn1->real_rate = snd_trident_set_dac_rate(trident, subchn1->rate, 0);
		return 0;
	}
	return -ENXIO;
}

/*---------------------------------------------------------------------------
   snd_trident_capture_ioctl
  
   Description: Device I/O control handler for capture parameters.
  
   Paramters:   subchn  - PCM subchannel class
                cmd     - what ioctl message to process
                arg     - additional message info
  
   Returns:     None
  
  ---------------------------------------------------------------------------*/
static int snd_trident_capture_ioctl(void *private_data,
				     snd_pcm_subchn_t * subchn,
				     unsigned int cmd, unsigned long *arg)
{
	trident_t *trident = (trident_t *) private_data;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;

	switch (cmd) {
	case SND_PCM1_IOCTL_RATE:
		subchn1->real_rate = snd_trident_set_adc_rate(trident, subchn1->rate, 0);
		return 0;
	}
	return -ENXIO;
}

/*---------------------------------------------------------------------------
   snd_trident_playback_trigger
  
   Description: This routine will start or stop playback.
  
   Paramters:   subchn  - PCM subchannel class
                up      - start or stop it flag.
  
   Returns:     None
  
  ---------------------------------------------------------------------------*/

static void snd_trident_playback_trigger(void *private_data,
					 snd_pcm_subchn_t * subchn,
					 int up)
{
	unsigned long flags;
	trident_t *trident = (trident_t *) private_data;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	snd_trident_pcm_t *tpcm = (snd_trident_pcm_t *)subchn1->private_data;

	//snd_printk("trid: called snd_trident_playback_trigger\n");
	spin_lock_irqsave(&trident->reg_lock, flags);
	if (up) {
		snd_trident_enable_voice_irq(trident, tpcm->channel1->number);
		snd_trident_start_voice(trident, tpcm->channel1->number);
		snd_trident_start_voice(trident, tpcm->channel0->number);
	} else {
		snd_trident_stop_voice(trident, tpcm->channel0->number);
		snd_trident_disable_voice_irq(trident, tpcm->channel1->number);
		snd_trident_disable_voice_irq(trident, tpcm->channel0->number);
		snd_trident_stop_voice(trident, tpcm->channel1->number);
	}
	tpcm->running = up;
	spin_unlock_irqrestore(&trident->reg_lock, flags);
}

/*---------------------------------------------------------------------------
   snd_trident_capture_trigger
  
   Description: This routine will start or stop capture.
  
   Paramters:   subchn  - PCM subchannel class
                up      - start or stop it flag.
  
   Returns:     None
  
  ---------------------------------------------------------------------------*/

static void snd_trident_capture_trigger(void * private_data,
					snd_pcm_subchn_t * subchn,
					int up)
{
	unsigned long flags;
	trident_t *trident = (trident_t *) private_data;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	snd_trident_pcm_t *tpcm = (snd_trident_pcm_t *)subchn1->private_data;

	spin_lock_irqsave(&trident->reg_lock, flags);
	if (up) {
		snd_trident_enable_voice_irq(trident, tpcm->channel0->number);
		outb(trident->bDMAStart, TRID_REG(trident, T4D_SBCTRL_SBE2R_SBDD));
		snd_trident_start_voice(trident, tpcm->channel0->number);
	} else {
		snd_trident_disable_voice_irq(trident, tpcm->channel0->number);
		outb(0x00, TRID_REG(trident, T4D_SBCTRL_SBE2R_SBDD));
		snd_trident_disable_voice_irq(trident, tpcm->channel0->number);
		snd_trident_stop_voice(trident, tpcm->channel0->number);
		ResetAinten(trident, tpcm->channel0->number);
	}
	tpcm->running = up;
	spin_unlock_irqrestore(&trident->reg_lock, flags);
}

/*---------------------------------------------------------------------------
   snd_trident_playback_prepare
  
   Description: This routine will set up the 4DWave hardware in 
                preparation for playback.
  
   Paramters:   subchn  - PCM subchannel class
                buffer  - DMA transfer buffer pointer
                size    - size in bytes of buffer
                offset  - NA
                count   - how many bytes to process between intrs
  
   Returns:     None
  
  ---------------------------------------------------------------------------*/

static void snd_trident_playback_prepare(void *private_data,
					 snd_pcm_subchn_t * subchn,
					 unsigned char *buffer,
					 unsigned int size,
					 unsigned int offset,
					 unsigned int count)
{
	trident_t *trident = (trident_t *) private_data;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	snd_trident_pcm_t *tpcm = (snd_trident_pcm_t *)subchn1->private_data;
	unsigned long flags;
	unsigned int LBA;
	unsigned int Delta;
	unsigned int rate;
	unsigned int ESO;
	unsigned int CTRL;
	unsigned int FMC_RVOL_CVOL;
	unsigned int GVSEL;
	unsigned int PAN;
	unsigned int VOL;
	unsigned int EC;

	/*
	   snd_printk("trid: called snd_trident_playback_prepare\n");
	   if (pcm1->playback.mode & SND_PCM1_MODE_16)
	   snd_printk("trid: 16bit and ");
	   else
	   snd_printk("trid: 8bit and ");
	   if (pcm1->playback.voices > 1)
	   snd_printk("stereo\n");
	   else
	   snd_printk("mono\n");

	   snd_printk("trid: size, count, offset = %d %d %d\n", size, count, offset);
	 */

	spin_lock_irqsave(&trident->reg_lock, flags);

	// 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
	rate = subchn1->real_rate;
	if (rate == 44100)
		Delta = 0xeb3;
	else if (rate == 8000)
		Delta = 0x2ab;
	else if (rate == 48000)
		Delta = 0x1000;
	else
		Delta = (((rate << 12) + rate) / 48000) & 0x0000ffff;

	//snd_printk("trid: rate, delta = %d %d\n", rate, Delta);

	/* set Loop Back Address */
	LBA = virt_to_bus(buffer);
#if 0
	printk("LBA = 0x%x (0x%lx), addr = 0x%lx\n", LBA, virt_to_bus(buffer), (long) buffer);
#endif

	/* set ESO */
	ESO = size;
	if (subchn1->mode & SND_PCM1_MODE_16)
		ESO /= 2;
	if (subchn1->voices > 1)
		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 (subchn1->mode & SND_PCM1_MODE_16)
		CTRL |= 0x00000008;	// 16-bit data
	if (!(subchn1->mode & SND_PCM1_MODE_U))
		CTRL |= 0x00000002;	// signed data
	if (subchn1->voices > 1)
		CTRL |= 0x00000004;	// stereo data

	FMC_RVOL_CVOL = 0x0000c000;
	GVSEL = 1;
	PAN = 0;
	VOL = 0;
	EC = 0;

	snd_trident_write_voice_regs(trident,
			     tpcm->channel0->number,
			     LBA,
			     0,	/* cso */
			     ESO,
			     Delta,
			     0,	/* alpha */
			     FMC_RVOL_CVOL,
			     GVSEL,
			     PAN,
			     VOL,
			     CTRL,
			     EC);

	/* set ESO */
	ESO = count;

 	if (subchn1->mode & SND_PCM1_MODE_16)
		ESO /= 2;
	if (subchn1->voices > 1)
		ESO /= 2;

	ESO = ESO - 1;
	//snd_printk("trid: intr ESO = %d\n", ESO);

	/* set ctrl mode
	   CTRL default: 8-bit (unsigned) mono, loop mode enabled
	 */
	CTRL = 0x00000001;
	if (subchn1->mode & SND_PCM1_MODE_16)
		CTRL |= 0x00000008;	// 16-bit data
	if (!(subchn1->mode & SND_PCM1_MODE_U))
		CTRL |= 0x00000002;	// signed data
	if (subchn1->voices > 1)
		CTRL |= 0x00000004;	// stereo data

	VOL = 0xff;
	FMC_RVOL_CVOL = 0x0000ffff;

	snd_trident_write_voice_regs(trident,
			     tpcm->channel1->number,
			     LBA,
			     0,	/* cso */
			     ESO,
			     Delta,
			     0,	/* alpha */
			     FMC_RVOL_CVOL,
			     GVSEL,
			     PAN,
			     VOL,
			     CTRL,
			     EC);

	spin_unlock_irqrestore(&trident->reg_lock, flags);
}

/*---------------------------------------------------------------------------
   snd_trident_capture_prepare
  
   Description: This routine will set up the 4DWave hardware in 
                preparation for capture.
  
   Parameters:	subchn  - PCM subchannel class
                buffer  - DMA transfer buffer pointer
                size    - size in bytes of buffer
                offset  - NA
                count   - how many bytes to process between intrs
  
   Returns:     None
  
  ---------------------------------------------------------------------------*/

static void snd_trident_capture_prepare(void *private_data,
					snd_pcm_subchn_t * subchn,
					unsigned char *buffer,
					unsigned int size,
					unsigned int offset,
					unsigned int count)
{
	trident_t *trident = (trident_t *) private_data;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	snd_trident_pcm_t *tpcm = (snd_trident_pcm_t *)subchn1->private_data;
	unsigned int LBA;
	unsigned int Delta;
	unsigned int rate;
	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;
	unsigned long flags;

	//snd_printk("trid: called snd_trident_capture_prepare\n");

	spin_lock_irqsave(&trident->reg_lock, flags);

	// Enable AC-97 ADC (capture), disable capture interrupt
	if (!trident->isNX) {
		bValue = inb(TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
		outb(bValue | 0x48, TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
	} else {
		wValue = inw(TRID_REG(trident, T4D_MISCINT));
		outw(wValue | 0x1000, TRID_REG(trident, T4D_MISCINT));
	}

	// Initilize the channel and set channel Mode
	outb(0, TRID_REG(trident, LEGACY_DMAR15));

	// Set DMA channel operation mode register
	bValue = inb(TRID_REG(trident, LEGACY_DMAR11)) & 0x03;
	outb(bValue | 0x54, TRID_REG(trident, LEGACY_DMAR11));

	// Set channel buffer Address
	LBA = virt_to_bus(buffer);
	outl(LBA, TRID_REG(trident, LEGACY_DMAR0));

	/* set ESO */
	ESO = size;

	dwValue = inl(TRID_REG(trident, LEGACY_DMAR4)) & 0xff000000;
	dwValue |= (ESO - 1) & 0x0000ffff;
	outl(dwValue, TRID_REG(trident, LEGACY_DMAR4));

	// Set channel sample rate , 4.12 format
	dwValue = (((unsigned int) 48000L << 12) / (unsigned long) (subchn1->real_rate));
	outw((unsigned short) dwValue, TRID_REG(trident, T4D_SBDELTA_DELTA_R));

	// Set channel interrupt blk length
	if (subchn1->mode & SND_PCM1_MODE_16) {
		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(trident, T4D_SBBL_SBCL));

	// Right now, set format and start to run captureing, 
	// continuous run loop enable.
	trident->bDMAStart = 0x19;	// 0001 1001b

	if (subchn1->mode & SND_PCM1_MODE_16)
		trident->bDMAStart |= 0xa0;
	if (subchn1->voices > 1)
		trident->bDMAStart |= 0x40;

	// Prepare capture intr channel

	/*
	   if (pcm1->capture.mode & SND_PCM1_MODE_16)
	   snd_printk("trid: 16bit and ");
	   else
	   snd_printk("trid: 8bit and ");
	   if (pcm1->capture.voices > 1)
	   snd_printk("stereo\n");
	   else
	   snd_printk("mono\n");

	   snd_printk("trid: size, count, offset = %d %d %d\n", size, count, offset);
	 */

	rate = subchn1->real_rate;
	Delta = ((((unsigned int) rate) << 12) / ((unsigned long) (48000L)));
	//snd_printk("trid: rate, delta = %d %d\n", rate, Delta);

	/* set Loop Back Address */
	LBA = virt_to_bus(buffer);

	/* set ESO */
	ESO = count;
	if (subchn1->mode & SND_PCM1_MODE_16)
		ESO /= 2;
	if (subchn1->voices > 1)
		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 (subchn1->mode & SND_PCM1_MODE_16)
		CTRL |= 0x00000008;	// 16-bit data
	if (!(subchn1->mode & SND_PCM1_MODE_U))
		CTRL |= 0x00000002;	// signed data
	if (subchn1->voices > 1)
		CTRL |= 0x00000004;	// stereo data

	FMC_RVOL_CVOL = 0x0000ffff;
	GVSEL = 1;
	PAN = 0xff;
	VOL = 0xff;
	EC = 0;

	snd_trident_write_voice_regs(trident,
			     tpcm->channel0->number,
			     LBA,
			     0,	/* cso */
			     ESO,
			     Delta,
			     0,	/* alpha */
			     FMC_RVOL_CVOL,
			     GVSEL,
			     PAN,
			     VOL,
			     CTRL,
			     EC);

	spin_unlock_irqrestore(&trident->reg_lock, flags);
}

/*---------------------------------------------------------------------------
   snd_trident_playback_open
  
   Description: This routine will open the 4DWave playback device. For now 
                we will simply allocate the dma transfer buffer.

   Parameters:	subchn  - PCM subchannel class

   Returns:     status  - success or failure flag
  
  ---------------------------------------------------------------------------*/

static void snd_trident_pcm_free_subchn1(void *private_data)
{
	unsigned long flags;
	snd_trident_pcm_t *tpcm = (snd_trident_pcm_t *)private_data;
	trident_t *trident;

	if (tpcm) {
		trident = tpcm->trident;
		spin_lock_irqsave(&trident->reg_lock, flags);
		if (tpcm->channel0) {
			trident->ChanDataPCM[tpcm->channel0->number] = NULL;
			snd_trident_free_voice(tpcm->trident, tpcm->channel0);
		}
		if (tpcm->channel1) {
			trident->ChanDataPCM[tpcm->channel1->number] = NULL;
			snd_trident_free_voice(tpcm->trident, tpcm->channel1);
		}
		spin_unlock_irqrestore(&trident->reg_lock, flags);
		snd_kfree(private_data);
	}
}

static int snd_trident_playback_open(void *private_data,
				     snd_pcm_subchn_t * subchn)
{
	unsigned long flags;
	trident_t *trident = (trident_t *) private_data;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	snd_trident_pcm_t *tpcm;
	int err;

	//snd_printk("trid: called snd_trident_playback_open\n");
	if ((err = snd_pcm1_dma_alloc(subchn,
				      trident->dma1ptr,
				      "Trident 4DWave (playback)")) < 0)
		return err;
	tpcm = (snd_trident_pcm_t *)snd_kcalloc(sizeof(*tpcm), GFP_KERNEL);
	if (tpcm == NULL) {
		snd_pcm1_dma_free(subchn);
		return -ENOMEM;
	}
	spin_lock_irqsave(&trident->reg_lock, flags);
	tpcm->trident = trident;
	tpcm->channel0 = snd_trident_alloc_voice(trident, SND_TRIDENT_VOICE_TYPE_PCM, 0, 0);
	tpcm->channel1 = snd_trident_alloc_voice(trident, SND_TRIDENT_VOICE_TYPE_PCM, 0, 0);
	if (tpcm->channel0 == NULL || tpcm->channel1 == NULL) {
		if (tpcm->channel0)
			snd_trident_free_voice(trident, tpcm->channel0);
		if (tpcm->channel1)
			snd_trident_free_voice(trident, tpcm->channel1);
		spin_unlock_irqrestore(&trident->reg_lock, flags);
		snd_kfree(tpcm);
		snd_pcm1_dma_free(subchn);
		return -EBUSY;
	}
	trident->ChanDataPCM[tpcm->channel0->number] =
	  trident->ChanDataPCM[tpcm->channel1->number] = tpcm;
	spin_unlock_irqrestore(&trident->reg_lock, flags);
	tpcm->subchn = subchn;
	tpcm->subchn1 = (snd_pcm1_subchn_t *)subchn->private_data;
	subchn1->private_data = tpcm;
	subchn1->private_free = snd_trident_pcm_free_subchn1;
	return 0;
}

/*---------------------------------------------------------------------------
   snd_trident_capture_open
  
   Description: This routine will open the 4DWave capture device. For now 
                we will simply allocate the dma transfer buffer.

   Parameters:	subchn  - PCM subchannel class

   Returns:     status  - success or failure flag

  ---------------------------------------------------------------------------*/

static int snd_trident_capture_open(void *private_data,
				    snd_pcm_subchn_t * subchn)
{
	unsigned long flags;

	trident_t *trident = (trident_t *) private_data;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	snd_trident_pcm_t *tpcm;
	int err;

	if ((err = snd_pcm1_dma_alloc(subchn,
				      trident->dma2ptr,
				      "Trident 4DWave (capture)")) < 0)
		return err;
	tpcm = (snd_trident_pcm_t *)snd_kcalloc(sizeof(*tpcm), GFP_KERNEL);
	if (tpcm == NULL) {
		snd_pcm1_dma_free(subchn);
		return -ENOMEM;
	}
	spin_lock_irqsave(&trident->reg_lock, flags);
	tpcm->trident = trident;
	tpcm->channel0 = snd_trident_alloc_voice(trident, SND_TRIDENT_VOICE_TYPE_PCM, 0, 0);
	tpcm->channel1 = NULL;
	if (tpcm->channel0 == NULL) {
		spin_unlock_irqrestore(&trident->reg_lock, flags);
		snd_kfree(tpcm);
		snd_pcm1_dma_free(subchn);
		return -EBUSY;
	}
	trident->ChanDataPCM[tpcm->channel0->number] = tpcm;
	spin_unlock_irqrestore(&trident->reg_lock, flags);
	tpcm->subchn = subchn;
	tpcm->subchn1 = (snd_pcm1_subchn_t *)subchn->private_data;
	subchn1->private_data = tpcm;
	subchn1->private_free = snd_trident_pcm_free_subchn1;
	return 0;
}

/*---------------------------------------------------------------------------
   snd_trident_playback_close
  
   Description: This routine will close the 4DWave playback device. For now 
                we will simply free the dma transfer buffer.
                
   Parameters:	subchn  - PCM subchannel class

  ---------------------------------------------------------------------------*/
static void snd_trident_playback_close(void *private_data,
				       snd_pcm_subchn_t * subchn)
{
	snd_pcm1_dma_free(subchn);
}

/*---------------------------------------------------------------------------
   snd_trident_capture_close
  
   Description: This routine will close the 4DWave capture device. For now 
                we will simply free the dma transfer buffer.
                
   Parameters:	subchn  - PCM subchannel class

  ---------------------------------------------------------------------------*/
static void snd_trident_capture_close(void *private_data,
				      snd_pcm_subchn_t * subchn)
{
	snd_pcm1_dma_free(subchn);
}

/*---------------------------------------------------------------------------
   snd_trident_playback_pointer
  
   Description: This routine return the playback position
                
   Parameters:	subchn  - PCM subchannel class

   Returns:     position of buffer
  
  ---------------------------------------------------------------------------*/

static unsigned int snd_trident_playback_pointer(void *private_data,
						 snd_pcm_subchn_t * subchn,
						 unsigned int used_size)
{
	trident_t *trident = (trident_t *) private_data;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	snd_trident_pcm_t *tpcm = (snd_trident_pcm_t *)subchn1->private_data;
	unsigned int cso;
	unsigned int eso;
	unsigned long flags;


	spin_lock_irqsave(&trident->reg_lock, flags);

	if (!tpcm->running) {
		spin_unlock_irqrestore(&trident->reg_lock, flags);
		return 0;
	}

	outb(tpcm->channel1->number, TRID_REG(trident, T4D_LFO_GC_CIR));

	if (!trident->isNX) {
		cso = inw(TRID_REG(trident, CH_DX_CSO_ALPHA_FMS + 2));
		eso = inw(TRID_REG(trident, CH_DX_ESO_DELTA + 2));
	} else			// ID_4DWAVE_NX
	 {
		cso = (unsigned int) inl(TRID_REG(trident, CH_NX_DELTA_CSO)) & 0x00ffffff;
		eso = (unsigned int) inl(TRID_REG(trident, CH_NX_DELTA_ESO)) & 0x00ffffff;
	}

	cso++;

	if (cso > eso)
		cso = eso;

	if (subchn1->mode & SND_PCM1_MODE_16)
		cso *= 2;
	if (subchn1->voices > 1)
		cso *= 2;

	spin_unlock_irqrestore(&trident->reg_lock, flags);

	//snd_printk("trid: called snd_trident_playback_pointer, used_size=%d, cso= %d\n", used_size, cso);

	return cso;
}

/*---------------------------------------------------------------------------
   snd_trident_capture_pointer
  
   Description: This routine return the capture position
                
   Paramters:   pcm1    - PCM device class

   Returns:     position of buffer
  
  ---------------------------------------------------------------------------*/

static unsigned int snd_trident_capture_pointer(void *private_data,
						snd_pcm_subchn_t * subchn,						
						unsigned int used_size)
{
	trident_t *trident = (trident_t *) private_data;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	snd_trident_pcm_t *tpcm = (snd_trident_pcm_t *)subchn1->private_data;
	unsigned int cso;
	unsigned long flags;


	spin_lock_irqsave(&trident->reg_lock, flags);

	if (!tpcm->running) {
		spin_unlock_irqrestore(&trident->reg_lock, flags);
		return 0;
	}

	outb(tpcm->channel0->number, TRID_REG(trident, T4D_LFO_GC_CIR));

	if (!trident->isNX) {
		cso = inw(TRID_REG(trident, CH_DX_CSO_ALPHA_FMS + 2));
	} else {		// ID_4DWAVE_NX 

		cso = (unsigned int) inl(TRID_REG(trident, CH_NX_DELTA_CSO)) & 0x00ffffff;
	}

	cso++;

	if (subchn1->mode & SND_PCM1_MODE_16)
		cso *= 2;
	if (subchn1->voices > 1)
		cso *= 2;

	spin_unlock_irqrestore(&trident->reg_lock, flags);

	//snd_printk("trid: called snd_trident_capture_pointer, used_size=%d, cso= %d\n", used_size, cso);

	return cso;
}

/*
   Playback support device description
 */

static struct snd_stru_pcm1_hardware snd_trident_playback =
{
	SND_PCM1_HW_AUTODMA | SND_PCM1_HW_BLOCKPTR,	/* flags */
	SND_PCM_FMT_MU_LAW | SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE | SND_PCM_FMT_S8 | SND_PCM_FMT_U16_LE, /* formats */
	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE | SND_PCM_FMT_S8 | SND_PCM_FMT_U16_LE, /* hardware formats */
	3,			/* align value */
	6,			/* minimal fragment */
	4000,			/* min. rate */
	48000,			/* max. rate */
	2,			/* max. voices */
	snd_trident_playback_open,
	snd_trident_playback_close,
	snd_trident_playback_ioctl,
	snd_trident_playback_prepare,
	snd_trident_playback_trigger,
	snd_trident_playback_pointer,
	snd_pcm1_playback_dma_ulaw_loud,
	snd_pcm1_dma_move,
	snd_pcm1_playback_dma_neutral
};

/*
   Capture support device description
 */

static struct snd_stru_pcm1_hardware snd_trident_capture =
{
	SND_PCM1_HW_AUTODMA | SND_PCM1_HW_BLOCKPTR,	/* flags */
	SND_PCM_FMT_MU_LAW | SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE | SND_PCM_FMT_S8 | SND_PCM_FMT_U16_LE, /* formats */
	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE | SND_PCM_FMT_S8 | SND_PCM_FMT_U16_LE, /* hardware formats */
	0,			/* align value */
	6,			/* minimal fragment */
	4000,			/* min. rate */
	48000,			/* max. rate */
	2,			/* max. voices */
	snd_trident_capture_open,
	snd_trident_capture_close,
	snd_trident_capture_ioctl,
	snd_trident_capture_prepare,
	snd_trident_capture_trigger,
	snd_trident_capture_pointer,
	snd_pcm1_capture_dma_ulaw_loud,
	snd_pcm1_dma_move,
	NULL
};

/*---------------------------------------------------------------------------
   snd_trident_pcm_free
  
   Description: This routine release the 4DWave private data.
                
   Paramters:   private_data - pointer to 4DWave device info.

   Returns:     None
  
  ---------------------------------------------------------------------------*/
static void snd_trident_pcm_free(void *private_data)
{
	trident_t *trident = (trident_t *) private_data;
	trident->pcm = NULL;
}

/*---------------------------------------------------------------------------
   snd_trident_pcm
  
   Description: This routine registers the 4DWave device for PCM support.
                
   Paramters:   trident - pointer to target device class for 4DWave.

   Returns:     None
  
  ---------------------------------------------------------------------------*/

snd_pcm_t *snd_trident_pcm(trident_t * trident)
{
	snd_pcm_t *pcm;
	snd_pcm1_channel_t *pchn1;

	//snd_printk("trid: called snd_trident_pcm\n");
	pcm = snd_pcm1_new_device(trident->card, "trident_dx_nx",
				  trident->ChanPCM / 2, 1);
	if (!pcm)
		return NULL;
	pchn1 = (snd_pcm1_channel_t *) pcm->playback.private_data;
	memcpy(&pchn1->hw, &snd_trident_playback, sizeof(snd_trident_playback));
	pchn1->private_data = trident;
	pchn1 = (snd_pcm1_channel_t *) pcm->capture.private_data;
	memcpy(&pchn1->hw, &snd_trident_capture, sizeof(snd_trident_capture));
	pchn1->private_data = trident;
	pcm->private_data = trident;
	pcm->private_free = snd_trident_pcm_free;
	pcm->info_flags = SND_PCM_INFO_CODEC | SND_PCM_INFO_MMAP |
			  SND_PCM_INFO_PLAYBACK | SND_PCM_INFO_CAPTURE |
			  SND_PCM_INFO_DUPLEX;
	strcpy(pcm->name, "Trident 4DWave");
	return trident->pcm = pcm;
}

/*
 *  Mixer part
 */


/*---------------------------------------------------------------------------
    snd_trident_get_pdif_switch

    Description: enabl/disable P/DIF out from ac97 mixer

  ---------------------------------------------------------------------------*/

static int snd_trident_spdif_get_switch(snd_kmixer_t * mixer,
					snd_kswitch_t * kswitch,
					snd_switch_t * uswitch)
{
	unsigned long flags;
	trident_t *trident = (trident_t *) kswitch->private_data;
	unsigned char val;

	uswitch->type = SND_SW_TYPE_BOOLEAN;
	spin_lock_irqsave(&trident->reg_lock, flags);
	val = trident->spdif_ctrl;
	uswitch->value.enable = val == kswitch->private_value;
	spin_unlock_irqrestore(&trident->reg_lock, flags);
	return 0;
}

static int snd_trident_spdif_set_switch(snd_kmixer_t * mixer,
				        snd_kswitch_t * kswitch,
				        snd_switch_t * uswitch)
{
	unsigned long flags;
	trident_t *trident = (trident_t *) kswitch->private_data;
	unsigned char val, val1;
	unsigned int val2;
	int change = 0;

	if (uswitch->type != SND_SW_TYPE_BOOLEAN)
		return -EINVAL;
	val1 = uswitch->value.enable ? (unsigned char) kswitch->private_value : 0x00;
	/* S/PDIF C Channel bits 0-31 : 48khz, SCMS disabled */
	val2 = uswitch->value.enable ? 0x02000004 : 0x00000000;
	spin_lock_irqsave(&trident->reg_lock, flags);
	change = kswitch->private_value != val1 ? 1 : 0;
	val = trident->spdif_ctrl = val1;
	outl(val2, TRID_REG(trident, NX_SPCSTATUS));
	outb(val, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
	spin_unlock_irqrestore(&trident->reg_lock, flags);
	return change;
}

static snd_kswitch_t snd_trident_spdif_switch =
{
	"S/PDIF Mixer Out",
	(snd_get_switch_t *)snd_trident_spdif_get_switch,
	(snd_set_switch_t *)snd_trident_spdif_set_switch,
	0x28,
	NULL,
	NULL
};

static void snd_trident_mixer_free_ac97(ac97_t * ac97)
{
	((trident_t *) ac97->private_data)->mixer = NULL;
	//snd_printk("trid: called snd_trident_mixer_free_ac97\n");
	snd_kfree(ac97);
}

/*---------------------------------------------------------------------------
   snd_trident_mixer
  
   Description: This routine registers the 4DWave device for mixer support.
                
   Paramters:   trident - pointer to target device class for 4DWave.

   Returns:     None
  
  ---------------------------------------------------------------------------*/

snd_kmixer_t *snd_trident_mixer(trident_t * trident)
{
	ac97_t *ac97;
	snd_kmixer_t *mixer;
	int pcm_dev = 0;

	//snd_printk("trid: called snd_trident_mixer\n");
	ac97 = snd_kcalloc(sizeof(ac97_t), GFP_KERNEL);
	if (!ac97)
		return NULL;
	ac97->write = WriteAC97;
	ac97->read = ReadAC97;
	ac97->private_data = trident;
	ac97->private_free = snd_trident_mixer_free_ac97;
	mixer = snd_ac97_mixer(trident->card, ac97, 1, &pcm_dev);
	if (!mixer) {
		snd_kfree(ac97);
		return NULL;
	}

	if (trident->isNX) {

		// 4 Speaker Codec initialization
		if (ac97->id == 0x83847608) {	// Sigmatel 9708.

			unsigned short TestReg;
			unsigned int DTemp;

			WriteAC97(trident, AC97_SIGMATEL_CIC1, 0xABBAL);
			WriteAC97(trident, AC97_SIGMATEL_CIC2, 0x1000L);

			TestReg = ReadAC97(trident, AC97_SIGMATEL_BIAS2);

			if (TestReg != 0x8000)  // Errata Notice.
			{
				WriteAC97(trident, AC97_SIGMATEL_BIAS1, 0xABBAL);
				WriteAC97(trident, AC97_SIGMATEL_BIAS2, 0x0007L);
			} else  // Newer version
			{
				WriteAC97(trident, AC97_SIGMATEL_CIC2, 0x1001L);	// recommended

				WriteAC97(trident, AC97_SIGMATEL_DAC2INVERT, 0x0008L);
			}

			//WriteAC97(trident, AC97_SURROUND_MASTER, 0x0000L);
			WriteAC97(trident, AC97_HEADPHONE, 0x8000L);

			DTemp = (unsigned int) ReadAC97(trident, AC97_GENERAL_PURPOSE);
			WriteAC97(trident, AC97_GENERAL_PURPOSE, DTemp & 0x0000FDFFL);	// bit9 = 0.

			DTemp = (unsigned int) ReadAC97(trident, AC97_MIC);
			WriteAC97(trident, AC97_MIC, 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 (ac97->id == 0x54524108) {	// TriTech TR28028

			WriteAC97(trident, AC97_SURROUND_MASTER, 0x0000L);
			WriteAC97(trident, AC97_RESERVED_3A, 0x0000L);
		} else if ((ac97->id >= 0x574D4C00) && (ac97->id <= 0x574D4C0f)) {	// Wolfson WM9704

			WriteAC97(trident, AC97_SURROUND_MASTER, 0x0000L);
		} else {
#if 0
			snd_printk("trid: No four Speaker Support with on board CODEC\n");
#endif
		}
		snd_mixer_switch_new(mixer, &snd_trident_spdif_switch, trident);
	}

	return trident->mixer = mixer;
}

/*---------------------------------------------------------------------------
   snd_trident_rawmidi
  
   Description: This routine registers the 4DWave device for raw midi support.
                
   Paramters:   trident - pointer to target device class for 4DWave.
		mpu - mpu401 handler class


   Returns:     None
  
  ---------------------------------------------------------------------------*/

void snd_trident_rawmidi(trident_t * trident, mpu401_t * mpu)
{
	/* I dont think we need to do anything special here but
	 * keep the hook anyway in case we need to later */
	mpu->private_data = trident;
}

/*  
   General boilerplate code required for driver.
 */

static void snd_trident_proc_read(snd_info_buffer_t * buffer,
				  void *private_data)
{
	trident_t *trident = (trident_t *) private_data;

	//snd_printk("trid: called snd_trident_proc_read\n");
	snd_iprintf(buffer, "Trident 4DWave PCI %s\n\n", trident->isNX ? "NX" : "DX");
	if (trident->isNX) {
		snd_iprintf(buffer, "S/PDIF Mixer Out : %s\n", trident->spdif_ctrl == 0x28 ? "on" : "off");
	}
#ifdef CONFIG_SND_SEQUENCER
	snd_iprintf(buffer,"\nWavetable Synth\n");
	snd_iprintf(buffer, "Memory Maximum : %d\n", trident->synth.max_size);
	snd_iprintf(buffer, "Memory Used    : %d\n", trident->synth.current_size);
	snd_iprintf(buffer, "Memory Free    : %d\n", (trident->synth.max_size-trident->synth.current_size));
#endif
}

static void snd_trident_proc_init(trident_t * trident)
{
	snd_info_entry_t *entry;

	//snd_printk("trid: called snd_trident_proc_init\n");

	if ((entry = snd_info_create_entry(trident->card, "4DWave")) != NULL) {
		entry->private_data = trident;
		entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
		entry->t.text.read_size = 256;
		entry->t.text.read = snd_trident_proc_read;
		if (snd_info_register(entry) < 0) {
			snd_info_free_entry(entry);
			entry = NULL;
		}
	}
	trident->proc_entry = entry;
}

static void snd_trident_proc_done(trident_t * trident)
{
	//snd_printk("trid: called snd_trident_proc_done\n");
	if (trident->proc_entry) {
		snd_info_unregister(trident->proc_entry);
		trident->proc_entry = NULL;
	}
}

/*---------------------------------------------------------------------------
   snd_trident_create
  
   Description: This routine will create the device specific class for
                the 4DWave card. It will also perform basic initialization.
                
   Paramters:   card  - which card to create
                pci   - interface to PCI bus resource info
                dma1ptr - playback dma buffer
                dma2ptr - capture dma buffer
                irqptr  -  interrupt resource info

   Returns:     4DWave device class private data
  
  ---------------------------------------------------------------------------*/

trident_t *snd_trident_create(snd_card_t * card,
			      struct pci_dev *pci,
			      snd_dma_t * dma1ptr,
			      snd_dma_t * dma2ptr,
			      snd_irq_t * irqptr,
			      int pcm_channels,
			      int max_wavetable_size)
{
	trident_t *trident;
	unsigned short cmdw;
	unsigned int *lpChannelData;
	unsigned int ChanDwordCount;
	unsigned int i;

	//snd_printk("trid: called snd_trident_create\n");

	trident = (trident_t *) snd_kcalloc(sizeof(trident_t), GFP_KERNEL);
	if (!trident)
		return NULL;
	trident->reg_lock = SPIN_LOCK_UNLOCKED;
	if (pcm_channels < 2)
		pcm_channels = 2;
	if (pcm_channels > 64)
		pcm_channels = 64;
	trident->ChanPCM = pcm_channels;
	if (max_wavetable_size < 0 )
		max_wavetable_size = 0;
	trident->synth.max_size = max_wavetable_size * 1024;
	ChanDwordCount = trident->ChanDwordCount = 2;
	trident->ChRegs = (LPCHANNELCONTROL) snd_kcalloc(sizeof(CHANNELCONTROL), GFP_KERNEL);
	if (!trident->ChRegs) {
		snd_kfree(trident);
		return NULL;
	}
	lpChannelData = (unsigned int *) snd_kcalloc(8 * (sizeof(unsigned int) * ChanDwordCount), GFP_KERNEL);
	if (!lpChannelData) {
		snd_kfree(trident->ChRegs);
		snd_kfree(trident);
		return NULL;
	}

	trident->ChRegs->lpChStart = lpChannelData;
	trident->ChRegs->lpChStop = trident->ChRegs->lpChStart + ChanDwordCount;
	trident->ChRegs->lpChAint = trident->ChRegs->lpChStop + ChanDwordCount;
	trident->ChRegs->lpChAinten = trident->ChRegs->lpChAint + ChanDwordCount;
	trident->ChRegs->lpAChStart = trident->ChRegs->lpChAinten + ChanDwordCount;
	trident->ChRegs->lpAChStop = trident->ChRegs->lpAChStart + ChanDwordCount;
	trident->ChRegs->lpAChAint = trident->ChRegs->lpAChStop + ChanDwordCount;
	trident->ChRegs->lpAChAinten = trident->ChRegs->lpAChAint + ChanDwordCount;
	// Assign addresses.

	trident->ChRegs->lpAChStart[0] = T4D_START_A;
	trident->ChRegs->lpAChStop[0] = T4D_STOP_A;
	trident->ChRegs->lpAChAint[0] = T4D_AINT_A;
	trident->ChRegs->lpAChAinten[0] = T4D_AINTEN_A;


	trident->ChRegs->lpAChStart[1] = T4D_START_B;
	trident->ChRegs->lpAChStop[1] = T4D_STOP_B;
	trident->ChRegs->lpAChAint[1] = T4D_AINT_B;
	trident->ChRegs->lpAChAinten[1] = T4D_AINTEN_B;

	trident->card = card;
	if (card->type == SND_CARD_TYPE_TRID4DWAVENX) {
		trident->isNX = TRUE;
#if 0
		snd_printk("trid: detected NX\n");
#endif
	} else {
		trident->isNX = FALSE;
#if 0
		snd_printk("trid: detected DX\n");
#endif
	}
	trident->pci = pci;
	trident->dma1ptr = dma1ptr;
	trident->dma2ptr = dma2ptr;
	trident->irqptr = irqptr;
#ifdef NEW_PCI
	trident->port = pci->resource[0].start;
#else
	trident->port = pci->base_address[0] & ~PCI_BASE_ADDRESS_SPACE;
#endif
	trident->midi_port = TRID_REG(trident, T4D_MPU401_BASE);
	pci_set_master(pci);
	pci_read_config_word(pci, PCI_COMMAND, &cmdw);
	if ((cmdw & (PCI_COMMAND_IO | PCI_COMMAND_MASTER)) != (PCI_COMMAND_IO | PCI_COMMAND_MASTER)) {
		cmdw |= PCI_COMMAND_IO | PCI_COMMAND_MASTER;
		pci_write_config_word(pci, PCI_COMMAND, cmdw);
	}
	pci_read_config_dword(pci, 0x10, &trident->port);
	trident->port &= ~0x0f;

	/*  initialize chip */

	outl(0x00, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL));
	WriteAC97(trident, 0x0L, 0L);
	WriteAC97(trident, 0x02L, 0L);
	WriteAC97(trident, 0x18L, 0L);

	if (trident->isNX) {

		// Enable rear channels
		outl(0x12, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
		// ...or not, since they sound ugly. :)
		// outl(0x02, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));

		// S/PDIF C Channel bits 0-31 : 48khz, SCMS disabled
		// outl(0x200004, TRID_REG(trident, NX_SPCSTATUS));
		// Disable S/PDIF out, 48khz only from ac97 fifo
		// outb(0x00, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
	} else {
		outl(0x02, TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
	}

	/* initialise synth voices*/
	for (i = 0; i < 64; i++ ) {
		trident->synth.voices[i].number = i;
	}

	EnableEndInterrupts(trident);

	snd_trident_proc_init(trident);
	return trident;
}

/*---------------------------------------------------------------------------
   snd_trident_free
  
   Description: This routine will free the device specific class for
                the 4DWave card. 
                
   Paramters:   trident  - device specific private data for 4DWave card

   Returns:     None.
  
  ---------------------------------------------------------------------------*/

void snd_trident_free(trident_t * trident)
{
	DisableEndInterrupts(trident);
	// Disable S/PDIF out
	if (trident->isNX)
		outb(0x00, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
	//snd_printk("trid: called snd_trident_free\n");
	snd_trident_proc_done(trident);
	if (trident->ChRegs) {
		if (trident->ChRegs->lpChStart)
			snd_kfree(trident->ChRegs->lpChStart);
		snd_kfree(trident->ChRegs);
	}
	snd_kfree(trident);
}

/*---------------------------------------------------------------------------
   snd_trident_interrupt
  
   Description: ISR for Trident 4DWave device
                
   Paramters:   trident  - device specific private data for 4DWave card

   Returns:     None.
  
  ---------------------------------------------------------------------------*/

void snd_trident_interrupt(trident_t * trident)
{
	snd_trident_pcm_t *tpcm;
	int channel;
	unsigned int audio_int;

	//snd_printk("trid: called snd_trident_interrupt\n");
	audio_int = inl(TRID_REG(trident, T4D_MISCINT));
	// check for mpu401 midi interrupt (Bit 3)
	if (audio_int & 0x00000008) {
		if (trident->rmidi) {
			unsigned int val;
			val = inb(TRID_REG(trident, T4D_MPUR1));
			if (!(val & 0x80))
				snd_mpu401_uart_interrupt(trident->rmidi);
		}
	}
	// check for wave (address engine) interrupt (Bit 5)
	if (audio_int & 0x00000020) {
		for (channel = 0; channel < 64; channel++) {
			if (DidChannelInterrupt(trident, channel)) {
#if 0
				snd_printk("Interrupt: %i\n", channel);
#endif
				AckChannelInterrupt(trident, channel);
				if (trident->synth.voices[channel].pcm) {
					/* pcm interrupt */
					tpcm = trident->ChanDataPCM[channel];
					if (tpcm != NULL) {
						tpcm->subchn1->ack(tpcm->subchn);
					} else {
						snd_trident_stop_voice(trident, channel);
						snd_trident_disable_voice_irq(trident, channel);
					}
				} else if (trident->synth.voices[channel].synth) {
					/* synth interrupt */
				} else if (trident->synth.voices[channel].midi) {
					/* midi interrupt */
				} else {
					/* unknown interrupt */
					snd_trident_stop_voice(trident, channel);
					snd_trident_disable_voice_irq(trident, channel);
				}

			}
		}
	}
}

/*---------------------------------------------------------------------------
   snd_trident_attach_synthesizer, snd_trident_detach_synthesizer
  
   Description: Attach/detach synthesizer hooks
                
   Paramters:   trident  - device specific private data for 4DWave card

   Returns:     None.
  
  ---------------------------------------------------------------------------*/
int snd_trident_attach_synthesizer(trident_t *trident)
{	
#ifdef CONFIG_SND_SEQUENCER
	snd_seq_device_register(trident->card, 1, "4DWave", SND_SEQ_DEV_TRIDENT, trident, 0, &trident->seq_dev);
#endif
	return 0;
}

int snd_trident_detach_synthesizer(trident_t *trident)
{
#ifdef CONFIG_SND_SEQUENCER
	if (trident->seq_dev) {
		snd_seq_device_unregister(trident->seq_dev);
		trident->seq_dev = NULL;
	}
#endif
	return 0;
}


snd_trident_voice_t *snd_trident_alloc_voice(trident_t * trident, int type, int client, int port)
{
	snd_trident_voice_t *pvoice;
	unsigned long flags;
	int idx;

	spin_lock_irqsave(&trident->voice_alloc, flags);
	if (type == SND_TRIDENT_VOICE_TYPE_PCM) {
		idx = AllocateChannelPCM(trident);
		if(idx < 0) {
			spin_unlock_irqrestore(&trident->voice_alloc, flags);
			return NULL;
		}
		pvoice = &trident->synth.voices[idx];
		pvoice->use = 1;
		pvoice->pcm = 1;
		spin_unlock_irqrestore(&trident->voice_alloc, flags);
		return pvoice;
	}
	if (type == SND_TRIDENT_VOICE_TYPE_SYNTH) {
		idx = AllocateChannelSynth(trident);
		if(idx < 0) {
			spin_unlock_irqrestore(&trident->voice_alloc, flags);
			return NULL;
		}
		pvoice = &trident->synth.voices[idx];
		pvoice->use = 1;
		pvoice->synth = 1;
		pvoice->client = client;
		pvoice->port = port;
		spin_unlock_irqrestore(&trident->voice_alloc, flags);
		return pvoice;
	}
	if (type == SND_TRIDENT_VOICE_TYPE_MIDI) {
	}
	spin_unlock_irqrestore(&trident->voice_alloc, flags);
	return NULL;
}
void snd_trident_free_voice(trident_t * trident, snd_trident_voice_t *voice)
{
	unsigned long flags;
	void (*private_free)(void *);
	void *private_data;

	if (voice == NULL || !voice->use)
		return;
	//snd_gf1_set_default_handlers(gus, SND_GF1_HANDLER_VOICE | voice->number);
	snd_trident_clear_voices(trident, voice->number, voice->number);
	spin_lock_irqsave(&trident->voice_alloc, flags);
	private_free = voice->private_free;
	private_data = voice->private_data;
	voice->private_free = NULL;
	voice->private_data = NULL;
	if (voice->pcm) {
		FreeChannelPCM(trident, voice->number);
	}
	if (voice->synth) {
		FreeChannelSynth(trident, voice->number);
	}
	voice->use = voice->pcm = voice->synth = voice->midi = 0;
	voice->sample_ops = NULL;
	spin_unlock_irqrestore(&trident->voice_alloc, flags);
	if (private_free)
		private_free(private_data);
}

void snd_trident_clear_voices(trident_t * trident, unsigned short v_min, unsigned short v_max)
{
	unsigned short i;

	for (i = v_min; i <= v_max; i++) {
		snd_trident_stop_voice(trident, i);
		snd_trident_disable_voice_irq(trident, i);
	}
}



EXPORT_SYMBOL(snd_trident_create);
EXPORT_SYMBOL(snd_trident_free);
EXPORT_SYMBOL(snd_trident_interrupt);
EXPORT_SYMBOL(snd_trident_pcm);
EXPORT_SYMBOL(snd_trident_mixer);
EXPORT_SYMBOL(snd_trident_rawmidi);
EXPORT_SYMBOL(snd_trident_attach_synthesizer);
EXPORT_SYMBOL(snd_trident_detach_synthesizer);
EXPORT_SYMBOL(snd_trident_alloc_voice);
EXPORT_SYMBOL(snd_trident_free_voice);
EXPORT_SYMBOL(snd_trident_write_voice_regs);
EXPORT_SYMBOL(snd_trident_start_voice);
EXPORT_SYMBOL(snd_trident_stop_voice);
EXPORT_SYMBOL(snd_trident_enable_voice_irq);
EXPORT_SYMBOL(snd_trident_disable_voice_irq);
EXPORT_SYMBOL(snd_trident_clear_voices);

/*
 *  INIT part
 */

int init_module(void)
{
	return 0;
}

void cleanup_module(void)
{
}
