/*
 *  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.
 *
 */

#include "driver.h"
#include "info.h"
#include "sndpci.h"
#include "control.h"
#include "trid4dwave.h"
#include "dma.h"

#define HW_PLAYBACK_CHANNEL 63
#define HW_PLAYBACK_CHANNEL_INTR 62
#define HW_RECORD_CHANNEL_INTR 61

/*
 *  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;

	trident_t *trident= (trident_t *) private_data;

	snd_mutex_lock (trident, reg);
	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;
		}
	}

	snd_mutex_unlock (trident, reg);
	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;

	trident_t *trident= (trident_t *) private_data;

	ulData= ((unsigned long) data) << 16;

	snd_mutex_lock (trident, reg);
	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");
			snd_mutex_unlock (trident, reg);
			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");
			snd_mutex_unlock (trident, reg);
			return;
		}

		ulData |= (0x0800 | (reg & 0x000000ff));
	}

	outl (ulData, TRID_REG (trident, address));
	snd_mutex_unlock (trident, reg);
}

/*---------------------------------------------------------------------------
   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.
  
  ---------------------------------------------------------------------------*/

void 
ResetAinten (trident_t * trident, int ChannelNum)
{
	unsigned int dwMask;
	unsigned int x= ChannelNum >> 5;
	unsigned int ChanDwordCount= trident->ChanDwordCount;

	IReadAinten (trident->ChRegs);
	dwMask= ((unsigned int) 1L) << (ChannelNum - 32);
	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, T4D_AINT_B));
}

/*---------------------------------------------------------------------------
   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.
  
  ---------------------------------------------------------------------------*/

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.
  
  ---------------------------------------------------------------------------*/

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 EnableInterruptChannel( 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 
EnableInterruptChannel (trident_t * trident, unsigned int HwChannel)
{
	unsigned int x= HwChannel >> 5;
	unsigned int Data= (0x80000000 >> ((((x + 1) << 5) - 1) - HwChannel));
	unsigned int ChanDwordCount= trident->ChanDwordCount;

	IReadAinten (trident->ChRegs);
	trident->ChRegs->lpChAinten[x] |= Data;
	IWriteAinten (trident->ChRegs);
}

/*---------------------------------------------------------------------------
   void DisableInterruptChannel( 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 
DisableInterruptChannel (trident_t * trident, unsigned int HwChannel)
{
	unsigned int x= HwChannel >> 5;
	unsigned int Data= (0x80000000 >> ((((x + 1) << 5) - 1) - HwChannel));
	unsigned int ChanDwordCount= trident->ChanDwordCount;

	IReadAinten (trident->ChRegs);
	trident->ChRegs->lpChAinten[x] &= ~Data;
	IWriteAinten (trident->ChRegs);
}

/*---------------------------------------------------------------------------
   void StartChannelHw( 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 
StartChannelHw (trident_t * trident, unsigned int HwChannel)
{
	unsigned int x= HwChannel >> 5;
	unsigned int Data= (0x80000000 >> ((((x + 1) << 5) - 1) - HwChannel));

	outl (Data, TRID_REG (trident, T4D_START_B));
	snd_printk ("trid: starting channel %d\n", (int) HwChannel);
}

/*---------------------------------------------------------------------------
   void StopChannelHw( 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 
StopChannelHw (trident_t * trident, unsigned int HwChannel)
{
	unsigned int x= HwChannel >> 5;
	unsigned int Data= (0x80000000 >> ((((x + 1) << 5) - 1) - HwChannel));

	outl (Data, TRID_REG (trident, T4D_STOP_B));
	snd_printk ("trid: stopping channel %d\n", (int) HwChannel);
}

/*---------------------------------------------------------------------------
   int DidAWaveInterruptOccur( )
  
   Description:  This routine will read the MiscInt register and determine
                 if a wave (Address engine) interrupt has occurred. Bit 5.
  
   Parameters :  trident - pointer to target device class for 4DWave.
  
   Return Value: TRUE if interrupt occurred, else FALSE.
  
  ---------------------------------------------------------------------------*/
int 
DidAWaveInterruptOccur (trident_t * trident)
{
	unsigned int MiscInt;

	MiscInt= inl (TRID_REG (trident, T4D_MISCINT));

	return ((int) (MiscInt & 0x00000020));
}

/*---------------------------------------------------------------------------
   int DidWavePlaybackInterrupt( )
  
   Description:  Check if playback interrupt channel occurred.
  
   Parameters :  trident - pointer to target device class for 4DWave.
  
   Return Value: TRUE if interrupt occurred, else FALSE.
  
  ---------------------------------------------------------------------------*/
int 
DidWavePlaybackInterrupt (trident_t * trident)
{
	unsigned int ChanDwordCount= trident->ChanDwordCount;
	unsigned int dwMask;

	ReadAint (trident->ChRegs);
	dwMask= ((unsigned int) 1L) << (HW_PLAYBACK_CHANNEL_INTR - 32);

	if (trident->ChRegs->lpChAint[1] & dwMask)
	{
		return (TRUE);
	}

	return (FALSE);
}

/*---------------------------------------------------------------------------
   void AckWavePlaybackInterrupt( )
  
   Description:  Acknowledge the interrupt bit for Wave playback channel intrs.
  
   Parameters :  trident - pointer to target device class for 4DWave.
  
   Return Value: None
  
  ---------------------------------------------------------------------------*/
void 
AckWavePlaybackInterrupt (trident_t * trident)
{
	unsigned int ChanDwordCount= trident->ChanDwordCount;
	unsigned int x= HW_PLAYBACK_CHANNEL_INTR >> 5;
	unsigned int dwMask;

	ReadAint (trident->ChRegs);
	dwMask= ((unsigned int) 1L) << (HW_PLAYBACK_CHANNEL_INTR - 32);
	trident->ChRegs->lpChAint[x] &= dwMask;
	IWriteAint (trident->ChRegs);
}

/*---------------------------------------------------------------------------
   int DidWaveRecordInterrupt( )
  
   Description:  Check if record interrupt channel occurred.
  
   Parameters :  trident - pointer to target device class for 4DWave.
  
   Return Value: TRUE if interrupt occurred, else FALSE.
  
  ---------------------------------------------------------------------------*/
int 
DidWaveRecordInterrupt (trident_t * trident)
{
	unsigned int ChanDwordCount= trident->ChanDwordCount;
	unsigned int dwMask;

	ReadAint (trident->ChRegs);
	dwMask= ((unsigned int) 1L) << (HW_RECORD_CHANNEL_INTR - 32);

	if (trident->ChRegs->lpChAint[1] & dwMask)
	{
		return (TRUE);
	}

	return (FALSE);
}

/*---------------------------------------------------------------------------
   void AckWaveRecordInterrupt( )

   Description:  Acknowledge the interrupt bit for Wave record channel intrs.
  
   Parameters :  trident - pointer to target device class for 4DWave.
  
   Return Value: None
  
  ---------------------------------------------------------------------------*/
void 
AckWaveRecordInterrupt (trident_t * trident)
{
	unsigned int ChanDwordCount= trident->ChanDwordCount;
	unsigned int x= HW_RECORD_CHANNEL_INTR >> 5;
	unsigned int dwMask;

	ReadAint (trident->ChRegs);
	dwMask= ((unsigned int) 1L) << (HW_RECORD_CHANNEL_INTR - 32);
	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. 
  
  ---------------------------------------------------------------------------*/
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. 
  
  ---------------------------------------------------------------------------*/
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;
}

/*---------------------------------------------------------------------------
   WriteCompleteChannel
  
   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 
WriteCompleteChannel (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)
{
	if (rate > 48000)
		rate= 48000;
	if (rate < 4000)
		rate= 4000;
	if (set)
	{
		// 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. 
		unsigned int Delta;

		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_mutex_lock (trident, reg);
		LoadDeltaHw (trident, HW_PLAYBACK_CHANNEL, Delta);
		snd_mutex_unlock (trident, reg);
	}
	return rate;
}

/*---------------------------------------------------------------------------
   snd_trident_set_adc_rate
  
   Description: This routine will set the sample rate for record.
  
   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)
{
	if (rate > 48000)
		rate= 48000;
	if (rate < 4000)
		rate= 4000;
	return rate;
}

/*---------------------------------------------------------------------------
   snd_trident_trigger
  
   Description: This routine start or stop playback or record.
  
   Paramters:   trident - pointer to target device class for 4DWave.
                what    - what device (channel for us) to trigger.
                up      - start or stop it flag.
  
   Returns:     None
  
  ---------------------------------------------------------------------------*/

static void 
snd_trident_trigger (trident_t * trident, int what, int up)
{

	snd_printk ("trid: called snd_trident_trigger\n");
	snd_mutex_lock (trident, reg);
	if (up)
	{
		if ((what == HW_PLAYBACK_CHANNEL_INTR) || (what == HW_PLAYBACK_CHANNEL))
		{
			if (!trident->enable_playback)
			{
				StartChannelHw (trident, what);
			}
		}
		else
		{
			if (!trident->enable_record)
			{
				// Start them up, DMA then Channel.
				outb (trident->bDMAStart, TRID_REG (trident, T4D_SBCTRL_SBE2R_SBDD));
				StartChannelHw (trident, what);
			}
		}
	}
	else
	{
		if ((what == HW_PLAYBACK_CHANNEL_INTR) || (what == HW_PLAYBACK_CHANNEL))
		{
			if (trident->enable_playback)
			{
				DisableInterruptChannel (trident, what);
				StopChannelHw (trident, what);
			}
		}
		else
		{
			if (trident->enable_record)
			{
				outb (0x00, TRID_REG (trident, T4D_SBCTRL_SBE2R_SBDD));
				DisableInterruptChannel (trident, what);
				StopChannelHw (trident, what);
				ResetAinten (trident, what);
			}
		}
	}
	snd_mutex_unlock (trident, reg);
}

/*
 *  PCM part
 */

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

static int 
snd_trident_playback_ioctl (snd_pcm1_t * pcm1,
	unsigned int cmd, unsigned long *arg)
{
	trident_t *trident= (trident_t *) pcm1->private_data;

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

/*---------------------------------------------------------------------------
   snd_trident_record_ioctl
  
   Description: Device I/O control handler for record parameters.
  
   Paramters:   pcm1    - PCM device class
                cmd     - what ioctl message to process
                arg     - additional message info
  
   Returns:     None
  
  ---------------------------------------------------------------------------*/
static int 
snd_trident_record_ioctl (snd_pcm1_t * pcm1,
	unsigned int cmd, unsigned long *arg)
{
	trident_t *trident= (trident_t *) pcm1->private_data;

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

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

static void 
snd_trident_playback_trigger (snd_pcm1_t * pcm1, int up)
{
	trident_t *trident= pcm1->private_data;

	snd_printk ("trid: called snd_trident_playback_trigger\n");
	snd_trident_trigger (trident, HW_PLAYBACK_CHANNEL_INTR, up);
	snd_trident_trigger (trident, HW_PLAYBACK_CHANNEL, up);
	trident->enable_playback= up;
}

/*---------------------------------------------------------------------------
   snd_trident_record_trigger
  
   Description: This routine will start or stop record.
  
   Paramters:   pcm1    - PCM device class
                up      - start or stop it flag.
  
   Returns:     None
  
  ---------------------------------------------------------------------------*/

static void 
snd_trident_record_trigger (snd_pcm1_t * pcm1, int up)
{
	trident_t *trident= pcm1->private_data;

	snd_trident_trigger (trident, HW_RECORD_CHANNEL_INTR, up);
	trident->enable_record= up;
}

/*---------------------------------------------------------------------------
   snd_trident_playback_prepare
  
   Description: This routine will set up the 4DWave hardware in 
                preparation for playback.
  
   Paramters:   pcm1    - PCM device 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 (snd_pcm1_t * pcm1,
	unsigned char *buffer,
	unsigned int size,
	unsigned int offset,
	unsigned int count)
{
	trident_t *trident= (trident_t *) pcm1->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;

	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);

	snd_mutex_lock (trident, reg);

	// 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= pcm1->playback.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);
	snd_printk ("LBA = 0x%x (0x%x), addr = 0x%lx\n", LBA, virt_to_bus (buffer), (long) buffer);

	/* set ESO */
	ESO= size;
	if (pcm1->playback.mode & SND_PCM1_MODE_16)
		ESO /= 2;
	if (pcm1->playback.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 (pcm1->playback.mode & SND_PCM1_MODE_16)
		CTRL |= 0x0000000a;								   // signed data

	if (pcm1->playback.voices > 1)
		CTRL |= 0x00000004;

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

	WriteCompleteChannel (
		trident,
		HW_PLAYBACK_CHANNEL,
		LBA,
		0,												   /* cso */
		ESO,
		Delta,
		0,												   /* alpha */
		FMC_RVOL_CVOL,
		GVSEL,
		PAN,
		VOL,
		CTRL,
		EC);

	/* set ESO */
	ESO= count;

	if (pcm1->playback.mode & SND_PCM1_MODE_16)
		ESO /= 2;
	if (pcm1->playback.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 (pcm1->playback.mode & SND_PCM1_MODE_16)
		CTRL |= 0x0000000a;								   // signed data

	if (pcm1->playback.voices > 1)
		CTRL |= 0x00000004;

	VOL= 0xff;

	WriteCompleteChannel (
		trident,
		HW_PLAYBACK_CHANNEL_INTR,
		LBA,
		0,												   /* cso */
		ESO,
		Delta,
		0,												   /* alpha */
		FMC_RVOL_CVOL,
		GVSEL,
		PAN,
		VOL,
		CTRL,
		EC);

	EnableInterruptChannel (trident, HW_PLAYBACK_CHANNEL_INTR);

	EnableEndInterrupts (trident);
	snd_mutex_unlock (trident, reg);
}

/*---------------------------------------------------------------------------
   snd_trident_record_prepare
  
   Description: This routine will set up the 4DWave hardware in 
                preparation for record.
  
   Paramters:   pcm1    - PCM device 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_record_prepare (snd_pcm1_t * pcm1,
	unsigned char *buffer,
	unsigned int size,
	unsigned int offset,
	unsigned int count)
{

	trident_t *trident= (trident_t *) pcm1->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;


	snd_printk ("trid: called snd_trident_record_prepare\n");

	snd_mutex_lock (trident, reg);

	// Enable AC-97 ADC (record), disable record 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) (pcm1->record.real_rate));
	outw ((unsigned short) dwValue, TRID_REG (trident, T4D_SBDELTA_DELTA_R));

	// Set channel interrupt blk length
	if (pcm1->record.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 recording, 
	// continuous run loop enable.
	trident->bDMAStart= 0x19;							   // 0001 1001b

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

	// Prepare record intr channel

	if (pcm1->record.mode & SND_PCM1_MODE_16)
		snd_printk ("trid: 16bit and ");
	else
		snd_printk ("trid: 8bit and ");
	if (pcm1->record.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= pcm1->record.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 (pcm1->record.mode & SND_PCM1_MODE_16)
		ESO /= 2;
	if (pcm1->record.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 (pcm1->record.mode & SND_PCM1_MODE_16)
		CTRL |= 0x0000000a;								   // signed data

	if (pcm1->record.voices > 1)
		CTRL |= 0x00000004;

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

	WriteCompleteChannel (
		trident,
		HW_RECORD_CHANNEL_INTR,
		LBA,
		0,												   /* cso */
		ESO,
		Delta,
		0,												   /* alpha */
		FMC_RVOL_CVOL,
		GVSEL,
		PAN,
		VOL,
		CTRL,
		EC);

	EnableInterruptChannel (trident, HW_RECORD_CHANNEL_INTR);

	EnableEndInterrupts (trident);
	snd_mutex_unlock (trident, reg);
}

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

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

static int 
snd_trident_playback_open (snd_pcm1_t * pcm1)
{
	trident_t *trident= (trident_t *) pcm1->private_data;
	int     err;

	snd_printk ("trid: called snd_trident_playback_open\n");
	if ((err= snd_pcm1_dma_alloc (pcm1, SND_PCM1_PLAYBACK,
				trident->dma1ptr,
				"Trident 4DWave(playback)")) < 0)
		return err;
	return 0;
}

/*---------------------------------------------------------------------------
   snd_trident_record_open
  
   Description: This routine will open the 4DWave record device. For now 
                we will simply allocate the dma transfer buffer.
                
  
   Paramters:   pcm1    - PCM device class

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

static int 
snd_trident_record_open (snd_pcm1_t * pcm1)
{
	trident_t *trident= (trident_t *) pcm1->private_data;
	int     err;

	if ((err= snd_pcm1_dma_alloc (pcm1, SND_PCM1_RECORD,
				trident->dma2ptr,
				"Trident 4DWave(record)")) < 0)
		return err;
	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.
                
   Paramters:   pcm1    - PCM device class

   Returns:     status  - success or failure flag
  
  ---------------------------------------------------------------------------*/
static void 
snd_trident_playback_close (snd_pcm1_t * pcm1)
{
	trident_t *trident= (trident_t *) pcm1->private_data;

	snd_printk ("trid: called snd_trident_playback_close\n");
	snd_pcm1_dma_free (pcm1, SND_PCM1_PLAYBACK, trident->dma1ptr);
}

/*---------------------------------------------------------------------------
   snd_trident_record_close
  
   Description: This routine will close the 4DWave record device. For now 
                we will simply free the dma transfer buffer.
                
   Paramters:   pcm1    - PCM device class

   Returns:     status  - success or failure flag
  
  ---------------------------------------------------------------------------*/
static void 
snd_trident_record_close (snd_pcm1_t * pcm1)
{
	trident_t *trident= (trident_t *) pcm1->private_data;

	snd_pcm1_dma_free (pcm1, SND_PCM1_RECORD, trident->dma2ptr);
}

/*---------------------------------------------------------------------------
   snd_trident_playback_pointer
  
   Description: This routine return the playback position
                
   Paramters:   pcm1    - PCM device class

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

static unsigned int 
snd_trident_playback_pointer (snd_pcm1_t * pcm1,
	unsigned int used_size)
{
	trident_t *trident= (trident_t *) pcm1->private_data;
	unsigned int cso;
	unsigned int eso;


	if (!trident->enable_playback)
		return 0;

	snd_mutex_lock (trident, reg);

	outb (HW_PLAYBACK_CHANNEL_INTR, 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 (pcm1->playback.mode & SND_PCM1_MODE_16)
		cso *= 2;
	if (pcm1->playback.voices > 1)
		cso *= 2;

	snd_mutex_unlock (trident, reg);

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

	return cso;
}

/*---------------------------------------------------------------------------
   snd_trident_record_pointer
  
   Description: This routine return the record position
                
   Paramters:   pcm1    - PCM device class

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

static unsigned int 
snd_trident_record_pointer (snd_pcm1_t * pcm1,
	unsigned int used_size)
{
	trident_t *trident= (trident_t *) pcm1->private_data;
	unsigned int cso;


	if (!trident->enable_record)
		return 0;

	snd_mutex_lock (trident, reg);

	outb (HW_RECORD_CHANNEL_INTR, 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 (pcm1->record.mode & SND_PCM1_MODE_16)
		cso *= 2;
	if (pcm1->record.voices > 1)
		cso *= 2;

	snd_mutex_unlock (trident, reg);

	snd_printk ("trid: called snd_trident_record_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=
{
	NULL,								/* private data */
	NULL,								/* private_free */
	SND_PCM1_HW_AUTODMA | SND_PCM1_HW_BLOCKPTR,		/* flags */
	SND_PCM_FMT_MU_LAW | SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,	/* formats */
	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,	/* 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
};

/*
 * Record support device description
 */

static struct snd_stru_pcm1_hardware snd_trident_record=
{
	NULL,								/* private data */
	NULL,								/* private_free */
	SND_PCM1_HW_AUTODMA | SND_PCM1_HW_BLOCKPTR,		/* flags */
	SND_PCM_FMT_MU_LAW | SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,	/* formats */
	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,	/* hardware formats */
	0,									/* align value */
	6,									/* minimal fragment */
	4000,								/* min. rate */
	48000,								/* max. rate */
	2,									/* max. voices */
	snd_trident_record_open,
	snd_trident_record_close,
	snd_trident_record_ioctl,
	snd_trident_record_prepare,
	snd_trident_record_trigger,
	snd_trident_record_pointer,
	snd_pcm1_record_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_t *pcm1;

	snd_printk ("trid: called snd_trident_pcm\n");
	pcm= snd_pcm1_new_device (trident->card, "trident_dx_nx");
	if (!pcm)
		return NULL;
	pcm1= (snd_pcm1_t *) pcm->private_data;
	memcpy (&pcm1->playback.hw,
		&snd_trident_playback,
		sizeof (snd_trident_playback));
	memcpy (&pcm1->record.hw,
		&snd_trident_record,
		sizeof (snd_trident_record));
	pcm1->private_data= trident;
	pcm1->private_free= snd_trident_pcm_free;
	pcm->info_flags= SND_PCM_INFO_CODEC | SND_PCM_INFO_MMAP | SND_PCM_INFO_TRIGGER |
		SND_PCM_INFO_PLAYBACK | SND_PCM_INFO_RECORD | SND_PCM_INFO_DUPLEX;
	strcpy (pcm->name, "Trident 4DWave");
	return trident->pcm= pcm;
}

/*
 *  Mixer part
 */

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_free (ac97, sizeof (ac97_t));
}

/*---------------------------------------------------------------------------
   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;

	snd_printk ("trid: called snd_trident_mixer\n");
	ac97= snd_calloc (sizeof (ac97_t));
	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);
	if (!mixer)
	{
		snd_free (ac97, sizeof (ac97_t));
		return NULL;
	}
	return trident->mixer= mixer;
}

/*  
 * 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");
}

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 - record dma buffer
                irqptr  -  interrupt resource info

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

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

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

	trident= (trident_t *) snd_calloc (sizeof (trident_t));
	if (!trident)
		return NULL;
	snd_mutex_prepare (trident, reg);
	ChanDwordCount= trident->ChanDwordCount= 2;
	trident->ChRegs= (LPCHANNELCONTROL) snd_calloc (sizeof (CHANNELCONTROL));
	if (!trident->ChRegs)
	{
		snd_printk ("trid: trident->ChRegs alloc failed\n");
		return NULL;
	}
	lpChannelData= (unsigned int *) snd_calloc (8 * (sizeof (unsigned int) * ChanDwordCount));

	if (!lpChannelData)
		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_B;
	trident->ChRegs->lpAChStop[0]= T4D_STOP_B;
	trident->ChRegs->lpAChAint[0]= T4D_AINT_B;
	trident->ChRegs->lpAChAinten[0]= T4D_AINTEN_B;


	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;
		snd_printk ("trid: detected NX\n");
	}
	else
	{
		trident->isNX= FALSE;
		snd_printk ("trid: detected DX\n");
	}
	trident->pci= pci;
	trident->dma1ptr= dma1ptr;
	trident->dma2ptr= dma2ptr;
	trident->irqptr= irqptr;
	trident->port= PCI_IO_ADDR (pci->base_address[0]);
	snd_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;
		snd_pci_write_config_word (pci, PCI_COMMAND, cmdw);
	}
	snd_pci_read_config_dword (pci, 0x10, (unsigned long *) &trident->port);
	trident->port &= ~0x0f;
	snd_printk ("trid: ioport=%x\n", trident->port);

	trident->enable_playback= FALSE;
	trident->enable_record= FALSE;

	/*  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)
	{
		unsigned short VendorID1, VendorID2;

		outl (0x02, TRID_REG (trident, NX_ACR0_AC97_COM_STAT));

		// 4 Speaker Codec initialization
		VendorID1= ReadAC97 (trident, AC97_VENDORID1);
		VendorID2= ReadAC97 (trident, AC97_VENDORID2);

		if ((VendorID1 == 0x8384) && (VendorID2 == 0x7608))
		{												   // 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_SURROUNDMASTERVOL, 0x0000L);
			WriteAC97 (trident, AC97_HEADPHONEVOLUME, 0x8000L);

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

			DTemp= (unsigned int) ReadAC97 (trident, AC97_MICVOLUME);
			WriteAC97 (trident, AC97_MICVOLUME, 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

			WriteAC97 (trident, AC97_SURROUNDMASTERVOL, 0x0000L);
			WriteAC97 (trident, AC97_EXTENDEDSTATUS, 0x0000L);
		}
		else if ((VendorID1 == 0x574D) &&
			(VendorID2 >= 0x4C00) && (VendorID2 <= 0x4C0f))
		{												   // Wolfson WM9704

			WriteAC97 (trident, AC97_SURROUNDMASTERVOL, 0x0000L);
		}
		else
		{
			snd_printk ("trid: No four Speaker Support with on board CODEC");
		}

	}
	else
	{
		outl (0x02, TRID_REG (trident, DX_ACR2_AC97_COM_STAT));
	}


	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)
{
	snd_printk ("trid: called snd_trident_free\n");
	snd_trident_proc_done (trident);
	if (trident->ChRegs)
	{
		if (trident->ChRegs->lpChStart)
			snd_free (trident->ChRegs->lpChStart, sizeof (8 * (sizeof (unsigned int) * trident->ChanDwordCount)));

		snd_free (trident->ChRegs, sizeof (CHANNELCONTROL));
	}
	snd_mutex_destroy (trident, reg);
	snd_free (trident, sizeof (trident_t));
}

/*---------------------------------------------------------------------------
   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_pcm1_t *pcm1;

	snd_printk ("trid: called snd_trident_interrupt\n");
	if (!DidAWaveInterruptOccur (trident))
		return;

	if (trident->pcm)
	{
		if (DidWavePlaybackInterrupt (trident))
		{
			snd_printk ("trid: processing wave playback intr\n");
			AckWavePlaybackInterrupt (trident);
			pcm1= (snd_pcm1_t *) trident->pcm->private_data;
			if (pcm1 && pcm1->playback.ack)
				pcm1->playback.ack (pcm1);
		}
		else
		{
			snd_printk ("trid: processing wave record intr\n");
			AckWaveRecordInterrupt (trident);
			pcm1= (snd_pcm1_t *) trident->pcm->private_data;
			if (pcm1 && pcm1->record.ack)
				pcm1->record.ack (pcm1);
		}
	}
}
