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

static int snd_trident_pcm_mixer_build(trident_t *trident, snd_trident_voice_t * voice, int idx, snd_pcm_subchn_t *subchn);
static int snd_trident_pcm_mixer_free(trident_t *trident, snd_trident_voice_t * voice, int idx);

/*
 *  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 = snd_magic_cast(trident_t, private_data, -ENXIO);

	spin_lock_irqsave(&trident->reg_lock, flags);
	if (trident->device == TRIDENT_DEVICE_ID_DX) {
		data = (DX_AC97_BUSY_READ | (reg & 0x000000ff));
		outl(data, TRID_REG(trident, DX_ACR1_AC97_R));
		do {
			data = inl(TRID_REG(trident, DX_ACR1_AC97_R));
			if ((data & DX_AC97_BUSY_READ) == 0)
				break;
		} while (--wCount);
	} else if (trident->device == TRIDENT_DEVICE_ID_NX) {
		data = (NX_AC97_BUSY_READ | (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);
	} else if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
		data = (SI_AC97_BUSY_READ | (reg & 0x000000ff));
		outl(data, TRID_REG(trident, SI_AC97_READ));
		do {
			data = inl(TRID_REG(trident, SI_AC97_READ));
			if ((data & (SI_AC97_BUSY_READ|SI_AC97_AUDIO_BUSY)) == 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);
	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 = snd_magic_cast(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->device == TRIDENT_DEVICE_ID_DX) {
		address = DX_ACR0_AC97_W;

		/* read AC-97 write register status */
		do {
			if ((inw(TRID_REG(trident, address)) & DX_AC97_BUSY_WRITE) == 0)
				break;
		} while (--wCount);

		ulData |= (DX_AC97_BUSY_WRITE | (reg & 0x000000ff));
	} else if (trident->device == TRIDENT_DEVICE_ID_NX) {
		address = NX_ACR1_AC97_W;

		/* read AC-97 write register status */
		do {
			if ((inw(TRID_REG(trident, address)) & NX_AC97_BUSY_WRITE) == 0)
				break;
		} while (--wCount);

		ulData |= (NX_AC97_BUSY_WRITE | (reg & 0x000000ff));
	} else if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
		address = SI_AC97_WRITE;

		/* read AC-97 write register status */
		do {
			if ((inw(TRID_REG(trident, address)) & (SI_AC97_BUSY_WRITE|SI_AC97_AUDIO_BUSY)) == 0)
				break;
		} while (--wCount);

		ulData |= (SI_AC97_BUSY_WRITE | (reg & 0x000000ff));
	} else {
		spin_unlock_irqrestore(&trident->reg_lock, flags);
		return;
	}

	if (wCount == 0) {
		spin_unlock_irqrestore(&trident->reg_lock, flags);
		return;
	}
	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 Bank = ChannelNum >> 5;
	unsigned int ChanDwordCount = trident->ChanDwordCount;

	IReadAinten(trident->ChRegs);
	dwMask = 1 << (ChannelNum & 0x1f);
	trident->ChRegs->lpChAinten[Bank] &= ~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[Bank]));
}

/*---------------------------------------------------------------------------
   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.
                Also enables middle of loop interrupts.
  
   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 = inl(TRID_REG(trident, T4D_LFO_GC_CIR));
	GlobalControl |= ENDLP_IE;
	GlobalControl |= MIDLP_IE;
	if (trident->device == TRIDENT_DEVICE_ID_SI7018)
		GlobalControl |= BANK_B_EN;
	outl(GlobalControl, TRID_REG(trident, T4D_LFO_GC_CIR));

	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.
                Also disables middle of loop interrupts.
  
   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 = inl(TRID_REG(trident, T4D_LFO_GC_CIR));
	GlobalControl &= ~ENDLP_IE;
	GlobalControl &= ~MIDLP_IE;
	outl(GlobalControl, TRID_REG(trident, T4D_LFO_GC_CIR));

	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 Bank, Data, ChanDwordCount;

	Bank = HwChannel >> 5;
	Data = 1 << (HwChannel & 0x1f);
	ChanDwordCount = trident->ChanDwordCount;
	IReadAinten(trident->ChRegs);
	trident->ChRegs->lpChAinten[Bank] |= 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 Bank, Data, ChanDwordCount;

	Bank = HwChannel >> 5;
	Data = 1 << (HwChannel & 0x1f);
	ChanDwordCount = trident->ChanDwordCount;
	IReadAinten(trident->ChRegs);
	trident->ChRegs->lpChAinten[Bank] &= ~Data;
	IWriteAinten(trident->ChRegs);
}

/*---------------------------------------------------------------------------
   unsigned int AllocateChannelPCM( void )
  
    Description: Allocate hardware channel in Bank B (32-63).
  
    Parameters :  trident - pointer to target device class for 4DWave.
  
    Return Value: hardware channel - 32-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[T4D_BANK_B] & (1 << idx))) {
			trident->ChanMap[T4D_BANK_B] |= 1 << idx;
			trident->ChanPCMcnt++;
			return idx + 32;
		}
	}
	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 in Bank A (0-31).
  
    Parameters :  trident - pointer to target device class for 4DWave.
  
    Return Value: hardware channel - 0-31 or -1 when no channel is available
  
  ---------------------------------------------------------------------------*/

static int AllocateChannelSynth(trident_t * trident)
{
	int idx;

	for (idx = 31; idx >= 0; idx--) {
		if (!(trident->ChanMap[T4D_BANK_A] & (1 << idx))) {
			trident->ChanMap[T4D_BANK_A] |= 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 Bank = HwChannel >> 5;
	unsigned int Data = 1 << (HwChannel & 0x1f);

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

/*---------------------------------------------------------------------------
   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 Bank = HwChannel >> 5;
	unsigned int Data = 1 << (HwChannel & 0x1f);

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

/*---------------------------------------------------------------------------
   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->device != TRIDENT_DEVICE_ID_NX) {
		ChanData[0] = (CSO << 16) | (ALPHA_FMS & 0x0000ffff);
		ChanData[2] = (ESO << 16) | (DELTA & 0x0ffff);
		ChanData[3] = FmcRvolCvol;
	} else {		// TRIDENT_DEVICE_ID_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_convert_rate

   Description: This routine converts rate in HZ to hardware delta value.
  
   Paramters:   trident - pointer to target device class for 4DWave.
                rate - Real or Virtual channel number.
  
   Returns:     Delta value.
  
  ---------------------------------------------------------------------------*/
unsigned int snd_trident_convert_rate(unsigned int rate)
{
	unsigned int Delta;

	// We special case 44100 and 8000 since rounding with the equation
	// does not give us an accurate enough value. For 11025 and 22050
	// the equation gives us the best answer. All other frequencies will
	// also use the equation. JDW
	if (rate == 44100)
		Delta = 0xeb3;
	else if (rate == 8000)
		Delta = 0x2ab;
	else if (rate == 48000)
		Delta = 0x1000;
	else
		Delta = (((rate << 12) + rate) / 48000) & 0x0000ffff;
	return Delta;
}

/*---------------------------------------------------------------------------
   snd_trident_control_mode

   Description: This routine returns a control mode for a PCM channel.
  
   Paramters:   trident - pointer to target device class for 4DWave.
                subchn  - PCM subchannel
  
   Returns:     Control value.
  
  ---------------------------------------------------------------------------*/
unsigned int snd_trident_control_mode(snd_pcm_subchn_t *subchn)
{
	unsigned int CTRL;
	snd_pcm_runtime_t *runtime = subchn->runtime;

	/* set ctrl mode
	   CTRL default: 8-bit (unsigned) mono, loop mode enabled
	 */
	CTRL = 0x00000001;
	if (snd_pcm_format_width(runtime->format.format) == 16)
		CTRL |= 0x00000008;	// 16-bit data
	if (!snd_pcm_format_unsigned(runtime->format.format))
		CTRL |= 0x00000002;	// signed data
	if (runtime->format.voices > 1)
		CTRL |= 0x00000004;	// stereo data
	return CTRL;
}

/*
 *  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:     Error status
  
  ---------------------------------------------------------------------------*/

static int snd_trident_set_buffer_size(trident_t *trident, snd_pcm_subchn_t * subchn)
{
	int shift = 0;
	snd_pcm_runtime_t *runtime = subchn->runtime;

	if (snd_pcm_format_width(runtime->format.format) == 16)
		shift++;
	if (runtime->format.voices > 1)
		shift++;
	switch (shift) {
	case 2:
		if (runtime->dma_area->size > 128 * 1024)
			return snd_pcm_lib_set_buffer_size(subchn, 128 * 1024);
		break;
	case 1:
		if (runtime->dma_area->size > 64 * 1024)
			return snd_pcm_lib_set_buffer_size(subchn, 64 * 1024);
		break;
	default:
		if (runtime->dma_area->size > 32 * 1024)
			return snd_pcm_lib_set_buffer_size(subchn, 32 * 1024);
	}
	return 0;
}

static int snd_trident_playback_ioctl(void *private_data,
				      snd_pcm_subchn_t * subchn,
				      unsigned int cmd,
				      unsigned long *arg)
{
	int result;

	result = snd_pcm_lib_ioctl(private_data, subchn, cmd, arg);
	if (result < 0)
		return result;
	if (cmd == SND_PCM_IOCTL1_PARAMS)
		return snd_trident_set_buffer_size((trident_t *)private_data, subchn);
	return 0;
}

/*---------------------------------------------------------------------------
   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 infoarg     
  
   Returns:     Error status
  
  ---------------------------------------------------------------------------*/

static int snd_trident_capture_ioctl(void *private_data,
				     snd_pcm_subchn_t * subchn,
				     unsigned int cmd,
				     unsigned long *arg)
{
	int result;

	result = snd_pcm_lib_ioctl(private_data, subchn, cmd, arg);
	if (result < 0)
		return result;
	if (cmd == SND_PCM_IOCTL1_PARAMS)
		return snd_trident_set_buffer_size((trident_t *)private_data, subchn);
	return 0;
}

/*---------------------------------------------------------------------------
   snd_trident_foldback_ioctl
  
   Description: Device I/O control handler for foldback capture parameters.
  
   Paramters:   subchn  - PCM subchannel class
                cmd     - what ioctl message to process
                arg     - additional message infoarg     
  
   Returns:     Error status
  
  ---------------------------------------------------------------------------*/

static int snd_trident_foldback_ioctl(void *private_data,
				      snd_pcm_subchn_t * subchn,
				      unsigned int cmd,
				      unsigned long *arg)
{
	int result;

	result = snd_pcm_lib_ioctl(private_data, subchn, cmd, arg);
	if (result < 0)
		return result;
	if (cmd == SND_PCM_IOCTL1_PARAMS)
		return snd_trident_set_buffer_size((trident_t *)private_data, subchn);
	return 0;
}

/*---------------------------------------------------------------------------
   snd_trident_playback_prepare
  
   Description: Prepare playback device for playback.
  
   Parameters:  subchn  - PCM subchannel class
  
   Returns:     Error status
  
  ---------------------------------------------------------------------------*/

static int snd_trident_playback_prepare(void *private_data,
				        snd_pcm_subchn_t * subchn)
{
	trident_t *trident = snd_magic_cast(trident_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_trident_voice_t *tpcm = (snd_trident_voice_t *) runtime->private_data;
	unsigned long flags;
	unsigned int LBA;
	unsigned int Delta;
	unsigned int ESO;
	unsigned int CTRL;
	unsigned int FMC_RVOL_CVOL;
	unsigned int GVSEL;
	unsigned int PAN;
	unsigned int VOL;
	unsigned int EC;
	
	spin_lock_irqsave(&trident->reg_lock, flags);	

	/* set Delta (rate) value */
	Delta = snd_trident_convert_rate(runtime->format.rate);

	/* set Loop Back Address */
	LBA = virt_to_bus(runtime->dma_area->buf);

	/* set target ESO for channel */
	tpcm->eso = snd_pcm_lib_transfer_size(subchn); 
	/* set interrupt count size */
	tpcm->count = snd_pcm_lib_transfer_fragment(subchn);
	tpcm->ignore_middle = tpcm->eso == tpcm->count;
	/* convert to sample counters */
	if (snd_pcm_format_width(runtime->format.format) == 16) {
		tpcm->eso >>= 1;
		tpcm->count >>= 1;
	}
	if (runtime->format.voices > 1) {
		tpcm->eso >>= 1;
		tpcm->count >>= 1;
	}
	// printk("eso = %i, count = %i\n", tpcm->eso, tpcm->count);

	/* set ESO to capture first MIDLP interrupt */
	tpcm->csoint = tpcm->count;
	ESO = tpcm->ignore_middle ? tpcm->eso - 1 : tpcm->csoint << 1;

	/* set ctrl mode */
	CTRL = snd_trident_control_mode(subchn);

	FMC_RVOL_CVOL = 0x0000c000 | ((tpcm->RVol  & 0x7f) << 7) | (tpcm->CVol & 0x7f);
	GVSEL = 1;
	PAN = tpcm->Pan & 0x7f;
	VOL = tpcm->Vol;
	EC = 0;

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

	spin_unlock_irqrestore(&trident->reg_lock, flags);

	return 0;
}

/*---------------------------------------------------------------------------
   snd_trident_capture_prepare
  
   Description: Prepare capture device for playback.
  
   Parameters:  subchn  - PCM subchannel class
  
   Returns:     Error status
  
  ---------------------------------------------------------------------------*/

static int snd_trident_capture_prepare(void *private_data,
				       snd_pcm_subchn_t * subchn)
{
	trident_t *trident = snd_magic_cast(trident_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_trident_voice_t *tpcm = (snd_trident_voice_t *) runtime->private_data;
	unsigned int LBA;
	unsigned int Delta;
	unsigned int ESO;
	unsigned int CTRL;
	unsigned int FMC_RVOL_CVOL;
	unsigned int GVSEL;
	unsigned int PAN;
	unsigned int VOL;
	unsigned int EC;
	unsigned char bValue;
	unsigned short wValue;
	unsigned int dwValue;
	unsigned short wRecCODECSamples;
	unsigned int dwChanFlags;
	unsigned long flags;

	spin_lock_irqsave(&trident->reg_lock, flags);
	tpcm->ignore_middle = 1;

	// Enable AC-97 ADC (capture), disable capture interrupt
	if (trident->device == TRIDENT_DEVICE_ID_DX) {
		dwValue = trident->ac97_ctrl = inl(TRID_REG(trident, DX_ACR2_AC97_COM_STAT)) | 0x00000048;
		outl(dwValue, TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
	} else if (trident->device == TRIDENT_DEVICE_ID_NX) {
		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(runtime->dma_area->buf);
	outl(LBA, TRID_REG(trident, LEGACY_DMAR0));

	/* set ESO */
	ESO = tpcm->eso = snd_pcm_lib_transfer_size(subchn);

	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) / runtime->format.rate;
	outw((unsigned short) dwValue, TRID_REG(trident, T4D_SBDELTA_DELTA_R));

	// Set channel interrupt blk length
	if (snd_pcm_format_width(runtime->format.format) == 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 (snd_pcm_format_width(runtime->format.format) == 16)
		trident->bDMAStart |= 0xa0;
	if (runtime->format.voices > 1)
		trident->bDMAStart |= 0x40;

	// Prepare capture intr channel

	Delta = snd_trident_convert_rate(runtime->format.rate);

	/* set ESO */
	ESO = snd_pcm_lib_transfer_fragment(subchn);
	if (snd_pcm_format_width(runtime->format.format) == 16)
		ESO >>= 1;
	if (runtime->format.voices > 1)
		ESO >>= 1;
	ESO = ESO - 1;

	CTRL = snd_trident_control_mode(subchn);
	FMC_RVOL_CVOL = 0x0000ffff;
	GVSEL = 1;
	PAN = 0xff;
	VOL = 0xff;
	EC = 0;

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

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

/*---------------------------------------------------------------------------
   snd_trident_foldback_prepare
  
   Description: Prepare foldback capture device for playback.
  
   Parameters:  subchn  - PCM subchannel class
  
   Returns:     Error status
  
  ---------------------------------------------------------------------------*/

static int snd_trident_foldback_prepare(void *private_data,
				        snd_pcm_subchn_t * subchn)
{
	trident_t *trident = snd_magic_cast(trident_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_trident_voice_t *tpcm = (snd_trident_voice_t *) runtime->private_data;
	unsigned int LBA;
	unsigned int Delta;
	unsigned int ESO;
	unsigned int CTRL;
	unsigned int FMC_RVOL_CVOL;
	unsigned int GVSEL;
	unsigned int PAN;
	unsigned int VOL;
	unsigned int EC;
	unsigned long flags;

	spin_lock_irqsave(&trident->reg_lock, flags);

	/* Set channel buffer Address */
	LBA = virt_to_bus(runtime->dma_area->buf);

	/* set target ESO for channel */
	tpcm->eso = snd_pcm_lib_transfer_size(subchn); 
	/* set interrupt count size */
	tpcm->count = snd_pcm_lib_transfer_fragment(subchn);
	tpcm->ignore_middle = tpcm->eso == tpcm->count;
	/* convert to sample counters */
	if (snd_pcm_format_width(runtime->format.format) == 16) {
		tpcm->eso >>= 1;
		tpcm->count >>= 1;
	}
	if (runtime->format.voices > 1) {
		tpcm->eso >>= 1;
		tpcm->count >>= 1;
	}

	/* set ESO to capture first MIDLP interrupt */
	tpcm->csoint = tpcm->count;
	ESO = tpcm->ignore_middle ? tpcm->eso - 1 : tpcm->csoint << 1;

	/* set sample rate */
	Delta = 0x1000;

	CTRL = snd_trident_control_mode(subchn);
	FMC_RVOL_CVOL = 0x0000ffff;
	GVSEL = 1;
	PAN = 0xff;
	VOL = 0xff;
	EC = 0;

	/* set up capture channel */
	outb(((tpcm->number & 0x3f) | 0x80), TRID_REG(trident, T4D_RCI + tpcm->foldback_chan));

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

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

/*---------------------------------------------------------------------------
   snd_trident_spdif_prepare
  
   Description: Prepare SPDIF device for playback.
  
   Parameters:  subchn  - PCM subchannel class
  
   Returns:     Error status
  
  ---------------------------------------------------------------------------*/

static int snd_trident_spdif_prepare(void *private_data,
				     snd_pcm_subchn_t * subchn)
{
	trident_t *trident = snd_magic_cast(trident_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_trident_voice_t *tpcm = (snd_trident_voice_t *) runtime->private_data;
	unsigned long flags;
	unsigned int LBA;
	unsigned int ESO;
	unsigned int RESO;
	unsigned int CSO;
	unsigned int Delta;
	unsigned int CTRL;
	unsigned int FMC_RVOL_CVOL;
	unsigned int GVSEL;
	unsigned int PAN;
	unsigned int VOL;
	unsigned int EC;

	spin_lock_irqsave(&trident->reg_lock, flags);	

	/* set Delta (rate) value */
	Delta = snd_trident_convert_rate(runtime->format.rate);

	/* surrogate IRQ voice may get out of sync with output stream
	 * at 32000 Hz so make it fractionally slower */
	if (runtime->format.rate == 32000)
		Delta = 0xaaa;

	/* set Loop Back Address */
	LBA = virt_to_bus(runtime->dma_area->buf);

	/* set target ESO for channel */
	RESO = tpcm->eso = snd_pcm_lib_transfer_size(subchn) >> 2; 
	/* set interrupt count size */
	tpcm->count = snd_pcm_lib_transfer_fragment(subchn) >> 2;
	tpcm->ignore_middle = tpcm->eso == tpcm->count;

	/* set ESO to capture first MIDLP interrupt */
	tpcm->csoint = tpcm->count;
	ESO = tpcm->ignore_middle ? tpcm->eso - 1 : tpcm->csoint << 1;

	/* set ctrl mode */
	CTRL = snd_trident_control_mode(subchn);

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

	/* prepare surrogate IRQ channel */
	snd_trident_write_voice_regs(trident,
				     tpcm->number,
				     LBA,
				     CSO,
				     ESO,
				     Delta,
				     0,	/* alpha */
				     FMC_RVOL_CVOL,
				     GVSEL,
				     PAN,
				     VOL,
				     CTRL,
				     EC);

	/* prepare SPDIF channel */
	if (runtime->format.rate >= 48000) {
		trident->spdif_pcm_ctrl = 0x3c;	// 48000 Hz
		trident->spdif_pcm_bits = 0x02000004;
	}
	else if (runtime->format.rate >= 44100) {
		trident->spdif_pcm_ctrl = 0x3e;	// 44100 Hz
		trident->spdif_pcm_bits = 0x00000004;
	}
	else {
		trident->spdif_pcm_ctrl = 0x3d;	// 32000 Hz
		trident->spdif_pcm_bits = 0x03000004;
	}

	/* use custom channel status word if defined */
	if (runtime->digital.dig_valid)
		trident->spdif_pcm_bits = runtime->digital.dig_status[0] | 
					  runtime->digital.dig_status[1] << 8 |
					  runtime->digital.dig_status[2] << 16 |
					  runtime->digital.dig_status[3] << 24;

	outw((RESO & 0xffff), TRID_REG(trident, NX_SPESO));
	outb((RESO >> 16), TRID_REG(trident, NX_SPESO + 2));
	outl((LBA & 0xfffffffc), TRID_REG(trident, NX_SPLBA));
	outw((CSO & 0xffff), TRID_REG(trident, NX_SPCTRL_SPCSO));
	outb((CSO >> 16), TRID_REG(trident, NX_SPCTRL_SPCSO + 2));

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


/*---------------------------------------------------------------------------
   snd_trident_sync_trigger
  
   Description: Prepare playback or capture devices for operation.
  
   Parameters:	trident - trident class
   		subchn  - PCM subchannel class
  
   Returns:     Error status
  
  ---------------------------------------------------------------------------*/

static int snd_trident_sync_trigger(trident_t *trident, snd_pcm_subchn_t *subchn)
{
	unsigned long flags;
	int idx, capture_flag = 0, result = -EINVAL;
	unsigned int mask = 0, ChanDwordCount;

	for (idx = 32; idx < 64; idx++) {
		snd_trident_voice_t *voice;
		snd_pcm_runtime_t *runtime;
		
		spin_lock_irqsave(&trident->voice_alloc, flags);
		voice = &trident->synth.voices[idx];
		if (voice->subchn == NULL) {
			spin_unlock_irqrestore(&trident->voice_alloc, flags);
			continue;
		}
		runtime = voice->subchn->runtime;
		if (memcmp(&subchn->runtime->sync_group, &runtime->sync_group, sizeof(runtime->sync_group))) {
			spin_unlock_irqrestore(&trident->voice_alloc, flags);
			continue;
		}
		spin_unlock_irqrestore(&trident->voice_alloc, flags);
		if (*runtime->status != SND_PCM_STATUS_PREPARED)
			goto __end;
		if (subchn->channel == SND_PCM_CHANNEL_PLAYBACK) {
			if (!snd_pcm_playback_data(subchn))
				goto __end;
		} else {
			if (!snd_pcm_capture_empty(subchn))
				goto __end;
		}
		if ((result = snd_pcm_channel_go_pre(subchn, SND_PCM_TRIGGER_GO))<0)
			goto __end;
		result = -EINVAL;
		if (subchn->channel == SND_PCM_CHANNEL_CAPTURE)
			capture_flag++;
		mask |= 1 << (idx & 0x1f);
	}
	spin_lock_irqsave(&trident->reg_lock, flags);
	ChanDwordCount = trident->ChanDwordCount;
	ReadAint(trident->ChRegs);
	IReadAinten(trident->ChRegs);
	trident->ChRegs->lpChAinten[1] |= mask;
	IWriteAinten(trident->ChRegs);
	if (capture_flag)
		outb(trident->bDMAStart, TRID_REG(trident, T4D_SBCTRL_SBE2R_SBDD));
	outl(mask, TRID_REG(trident, trident->ChRegs->lpAChStart[1]));
	for (idx = 32; idx < 64; idx++)
		if (mask & (1 << (idx & 0x1f)))
			trident->synth.voices[idx].running = 1;
	spin_unlock_irqrestore(&trident->reg_lock, flags);
	result = 0;
      __end:
	for (idx = 63; idx >= 32; idx--) {
		snd_trident_voice_t *voice;
		snd_pcm_runtime_t *runtime;
			
		spin_lock_irqsave(&trident->voice_alloc, flags);
		voice = &trident->synth.voices[idx];
		if (voice->subchn == NULL) {
			spin_unlock_irqrestore(&trident->voice_alloc, flags);
			continue;
		}
		runtime = voice->subchn->runtime;
		if (memcmp(&subchn->runtime->sync_group, &runtime->sync_group, sizeof(runtime->sync_group))) {
			spin_unlock_irqrestore(&trident->voice_alloc, flags);
			continue;
		}
		spin_unlock_irqrestore(&trident->voice_alloc, flags);
		*runtime->status = SND_PCM_STATUS_PREPARED;
		snd_pcm_channel_go_post(subchn, SND_PCM_TRIGGER_GO, result);
	}
	return result;
}

/*---------------------------------------------------------------------------
   snd_trident_playback_trigger
  
   Description: Prepare playback device for playback.
  
   Parameters:  subchn  - PCM subchannel class
   		cmd	- trigger command (STOP, GO, SYNC_GO)
  
   Returns:     Error status
  
  ---------------------------------------------------------------------------*/

static int snd_trident_playback_trigger(void *private_data,
				        snd_pcm_subchn_t * subchn,
				        int cmd)
{
	unsigned long flags;
	trident_t *trident = snd_magic_cast(trident_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_trident_voice_t *tpcm = (snd_trident_voice_t *) runtime->private_data;

	if (cmd == SND_PCM_TRIGGER_SYNC_GO)
		return snd_trident_sync_trigger(trident, subchn);
	// printk("trigger - trident = 0x%x, cmd = %i, pointer = %i, int = 0x%x\n", (int)trident, cmd, runtime->hw->pointer(private_data, subchn), inl(TRID_REG(trident, T4D_MISCINT)));
	spin_lock_irqsave(&trident->reg_lock, flags);
	if (cmd == SND_PCM_TRIGGER_GO) {
		snd_trident_enable_voice_irq(trident, tpcm->number);
		snd_trident_start_voice(trident, tpcm->number);
		tpcm->running = 1;
	} else if (cmd == SND_PCM_TRIGGER_STOP) {
		snd_trident_stop_voice(trident, tpcm->number);
		snd_trident_disable_voice_irq(trident, tpcm->number);
		tpcm->running = 0;
	}
	spin_unlock_irqrestore(&trident->reg_lock, flags);
	return 0;
}

/*---------------------------------------------------------------------------
   snd_trident_capture_trigger
  
   Description: Prepare capture device for playback.
  
   Parameters:  subchn  - PCM subchannel class
   		cmd	- trigger command (STOP, GO, SYNC_GO)
  
   Returns:     Error status
  
  ---------------------------------------------------------------------------*/

static int snd_trident_capture_trigger(void *private_data,
				       snd_pcm_subchn_t * subchn,
				       int cmd)
{
	unsigned long flags;
	trident_t *trident = snd_magic_cast(trident_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_trident_voice_t *tpcm = (snd_trident_voice_t *) runtime->private_data;

	if (cmd == SND_PCM_TRIGGER_SYNC_GO)
		return snd_trident_sync_trigger(trident, subchn);
	// printk("trigger - trident = 0x%x, cmd = %i, pointer = %i, int = 0x%x\n", (int)trident, cmd, runtime->hw->pointer(private_data, subchn), inl(TRID_REG(trident, T4D_MISCINT)));
	spin_lock_irqsave(&trident->reg_lock, flags);
	if (cmd == SND_PCM_TRIGGER_GO) {
		snd_trident_enable_voice_irq(trident, tpcm->number);
		outb(trident->bDMAStart, TRID_REG(trident, T4D_SBCTRL_SBE2R_SBDD));
		snd_trident_start_voice(trident, tpcm->number);
		tpcm->running = 1;
	} else if (cmd == SND_PCM_TRIGGER_STOP) {
		snd_trident_disable_voice_irq(trident, tpcm->number);
		outb(0x00, TRID_REG(trident, T4D_SBCTRL_SBE2R_SBDD));
		snd_trident_stop_voice(trident, tpcm->number);
		snd_trident_disable_voice_irq(trident, tpcm->number);
		ResetAinten(trident, tpcm->number);
		tpcm->running = 0;
	}
	spin_unlock_irqrestore(&trident->reg_lock, flags);
	return 0;
}

/*---------------------------------------------------------------------------
   snd_trident_foldback_trigger
  
   Description: Prepare foldback capture device for playback.
  
   Parameters:  subchn  - PCM subchannel class
   		cmd	- trigger command (STOP, GO, SYNC_GO)
  
   Returns:     Error status
  
  ---------------------------------------------------------------------------*/

static int snd_trident_foldback_trigger(void *private_data,
				        snd_pcm_subchn_t * subchn,
				        int cmd)
{
	unsigned long flags;
	trident_t *trident = snd_magic_cast(trident_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_trident_voice_t *tpcm = (snd_trident_voice_t *) runtime->private_data;

	if (cmd == SND_PCM_TRIGGER_SYNC_GO)
		return snd_trident_sync_trigger(trident, subchn);
	// printk("trigger - trident = 0x%x, cmd = %i, pointer = %i, int = 0x%x\n", (int)trident, cmd, runtime->hw->pointer(private_data, subchn), inl(TRID_REG(trident, T4D_MISCINT)));
	spin_lock_irqsave(&trident->reg_lock, flags);
	if (cmd == SND_PCM_TRIGGER_GO) {
		snd_trident_enable_voice_irq(trident, tpcm->number);
		snd_trident_start_voice(trident, tpcm->number);
		tpcm->running = 1;
	} else if (cmd == SND_PCM_TRIGGER_STOP) {
		snd_trident_disable_voice_irq(trident, tpcm->number);
		snd_trident_stop_voice(trident, tpcm->number);
		snd_trident_disable_voice_irq(trident, tpcm->number);
		ResetAinten(trident, tpcm->number);
		tpcm->running = 0;
	}
	spin_unlock_irqrestore(&trident->reg_lock, flags);
	return 0;
}

/*---------------------------------------------------------------------------
   snd_trident_spdif_trigger
  
   Description: Prepare SPDIF device for playback.
  
   Parameters:  subchn  - PCM subchannel class
   		 cmd	 - trigger command (STOP, GO, SYNC_GO)
  
   Returns:     Error status
  
  ---------------------------------------------------------------------------*/

static int snd_trident_spdif_trigger(void *private_data,
				     snd_pcm_subchn_t * subchn,
				     int cmd)
{
	unsigned long flags;
	trident_t *trident = snd_magic_cast(trident_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_trident_voice_t *tpcm = (snd_trident_voice_t *) runtime->private_data;

	if (cmd == SND_PCM_TRIGGER_SYNC_GO)
		return snd_trident_sync_trigger(trident, subchn);
	spin_lock_irqsave(&trident->reg_lock, flags);
	if (cmd == SND_PCM_TRIGGER_GO) {
		outl(trident->spdif_pcm_bits, TRID_REG(trident, NX_SPCSTATUS));
		outb(trident->spdif_pcm_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
		snd_trident_enable_voice_irq(trident, tpcm->number);
		snd_trident_start_voice(trident, tpcm->number);
		tpcm->running = 1;
	}
	else if (cmd == SND_PCM_TRIGGER_STOP) {
		outb(trident->spdif_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
		outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS));
		snd_trident_stop_voice(trident, tpcm->number);
		snd_trident_disable_voice_irq(trident, tpcm->number);
		tpcm->running = 0;
	}
	spin_unlock_irqrestore(&trident->reg_lock, flags);
	return 0;
}

/*---------------------------------------------------------------------------
   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)
{
	trident_t *trident = snd_magic_cast(trident_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_trident_voice_t *tpcm = (snd_trident_voice_t *) runtime->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->number, TRID_REG(trident, T4D_LFO_GC_CIR));

	if (trident->device != TRIDENT_DEVICE_ID_NX) {
		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;
	}

	if (snd_pcm_format_width(runtime->format.format) == 16)
		cso <<= 1;
	if (runtime->format.voices > 1)
		cso <<= 1;

	spin_unlock_irqrestore(&trident->reg_lock, flags);

	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)
{
	trident_t *trident = snd_magic_cast(trident_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_trident_voice_t *tpcm = (snd_trident_voice_t *) runtime->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->number, TRID_REG(trident, T4D_LFO_GC_CIR));

	if (trident->device != TRIDENT_DEVICE_ID_NX) {
		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;
	}

	if (snd_pcm_format_width(runtime->format.format) == 16)
		cso <<= 1;
	if (runtime->format.voices > 1)
		cso <<= 1;
	if (runtime->mode == SND_PCM_MODE_BLOCK)
		cso += *runtime->frag_head * snd_pcm_lib_transfer_fragment(subchn);

	spin_unlock_irqrestore(&trident->reg_lock, flags);

	return cso;
}

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

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

static unsigned int snd_trident_spdif_pointer(void *private_data,
					      snd_pcm_subchn_t * subchn)
{
	trident_t *trident = snd_magic_cast(trident_t, private_data, -ENXIO);
	unsigned int cso;
	unsigned long flags;

	spin_lock_irqsave(&trident->reg_lock, flags);

	cso = (unsigned int) inl(TRID_REG(trident, NX_SPCTRL_SPCSO)) & 0x00ffffff;
	cso <<= 2;

	spin_unlock_irqrestore(&trident->reg_lock, flags);

	return cso;
}

/*
 *  Playback support device description
 */

static snd_pcm_hardware_t snd_trident_playback =
{
	SND_PCM_CHNINFO_MMAP | SND_PCM_CHNINFO_STREAM |
	SND_PCM_CHNINFO_BLOCK | SND_PCM_CHNINFO_INTERLEAVE |
	SND_PCM_CHNINFO_BLOCK_TRANSFER |
	SND_PCM_CHNINFO_MMAP_VALID,		/* flags */
	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE |
	SND_PCM_FMT_S8 | SND_PCM_FMT_U16_LE,	/* formats */
	SND_PCM_RATE_CONTINUOUS | SND_PCM_RATE_8000_48000, /* supported RATES */
	4000,			/* min. rate */
	48000,			/* max. rate */
	1,			/* min. voices */
	2,			/* max. voices */
	64,			/* min. fragment size */
	(128*1024),		/* max. fragment size */
	3,			/* fragment align */
	0,			/* FIFO size (unknown) */
	4,			/* transfer block size */
	snd_trident_playback_ioctl,
	snd_trident_playback_prepare,
	snd_trident_playback_trigger,
	snd_trident_playback_pointer
};

/*
 *  Capture support device description
 */

static snd_pcm_hardware_t snd_trident_capture =
{
	SND_PCM_CHNINFO_MMAP | SND_PCM_CHNINFO_STREAM |
	SND_PCM_CHNINFO_BLOCK | SND_PCM_CHNINFO_INTERLEAVE |
	SND_PCM_CHNINFO_BLOCK_TRANSFER |
	SND_PCM_CHNINFO_MMAP_VALID,		/* flags */
	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE |
	SND_PCM_FMT_S8 | SND_PCM_FMT_U16_LE,	/* formats */
	SND_PCM_RATE_CONTINUOUS | SND_PCM_RATE_8000_48000, /* supported RATES */
	4000,			/* min. rate */
	48000,			/* max. rate */
	1,			/* min. voices */
	2,			/* max. voices */
	64,			/* min. fragment size */
	(128*1024),		/* max. fragment size */
	3,			/* fragment align */
	0,			/* FIFO size (unknown) */
	16,			/* transfer block size */
	snd_trident_capture_ioctl,
	snd_trident_capture_prepare,
	snd_trident_capture_trigger,
	snd_trident_capture_pointer
};

/*
 *  Foldback capture support device description
 */

static snd_pcm_hardware_t snd_trident_foldback =
{
	SND_PCM_CHNINFO_MMAP | SND_PCM_CHNINFO_STREAM |
	SND_PCM_CHNINFO_BLOCK | SND_PCM_CHNINFO_INTERLEAVE |
	SND_PCM_CHNINFO_BLOCK_TRANSFER |
	SND_PCM_CHNINFO_MMAP_VALID,		/* flags */
	SND_PCM_FMT_S16_LE,	/* formats */
	SND_PCM_RATE_48000,	/* supported RATES */
	48000,			/* min. rate */
	48000,			/* max. rate */
	2,			/* min. voices */
	2,			/* max. voices */
	64,			/* min. fragment size */
	(128*1024),		/* max. fragment size */
	3,			/* fragment align */
	0,			/* FIFO size (unknown) */
	16,			/* transfer block size */
	snd_trident_foldback_ioctl,
	snd_trident_foldback_prepare,
	snd_trident_foldback_trigger,
	snd_trident_capture_pointer
};

/*
 *  SPDIF playback support device description
 */

static snd_pcm_hardware_t snd_trident_spdif =
{
	SND_PCM_CHNINFO_MMAP | SND_PCM_CHNINFO_STREAM |
	SND_PCM_CHNINFO_BLOCK | SND_PCM_CHNINFO_INTERLEAVE |
	SND_PCM_CHNINFO_BLOCK_TRANSFER |
	SND_PCM_CHNINFO_MMAP_VALID,		/* flags */
	SND_PCM_FMT_S16_LE,	/* formats */
	SND_PCM_RATE_32000 | SND_PCM_RATE_44100 |
	SND_PCM_RATE_48000,	/* supported RATES */
	32000,			/* min. rate */
	48000,			/* max. rate */
	2,			/* min. voices */
	2,			/* max. voices */
	64,			/* min. fragment size */
	(128*1024),		/* max. fragment size */
	3,			/* fragment align */
	0,			/* FIFO size (unknown) */
	4,			/* transfer block size */
	snd_trident_playback_ioctl,
	snd_trident_spdif_prepare,
	snd_trident_spdif_trigger,
	snd_trident_spdif_pointer
};

/*---------------------------------------------------------------------------
   snd_trident_playback_open
  
   Description: This routine will open the 4DWave playback device.

   Parameters:	subchn  - PCM subchannel class

   Returns:     status  - success or failure flag
  
  ---------------------------------------------------------------------------*/
static void snd_trident_pcm_free_subchn(void *private_data)
{
	unsigned long flags;
	snd_trident_voice_t *tpcm = (snd_trident_voice_t *) private_data;
	trident_t *trident;

	if (tpcm) {
		trident = tpcm->trident;
		spin_lock_irqsave(&trident->reg_lock, flags);
		snd_trident_free_voice(tpcm->trident, tpcm);
		spin_unlock_irqrestore(&trident->reg_lock, flags);
	}
}

static int snd_trident_playback_open(void *private_data,
				     snd_pcm_subchn_t * subchn)
{
	unsigned long flags;
	trident_t *trident = snd_magic_cast(trident_t, private_data, -ENXIO);
	snd_trident_voice_t *tpcm;
	int err;

	if ((err = snd_pcm_dma_alloc(subchn,
				     trident->dma1ptr,
				     "Trident 4DWave (playback)")) < 0)
		return err;
	spin_lock_irqsave(&trident->reg_lock, flags);
	tpcm = snd_trident_alloc_voice(trident, SND_TRIDENT_VOICE_TYPE_PCM, 0, 0);
	if (tpcm == NULL) {
		spin_unlock_irqrestore(&trident->reg_lock, flags);
		snd_pcm_dma_free(subchn);
		return -EAGAIN;
	}
	tpcm->trident = trident;
	spin_unlock_irqrestore(&trident->reg_lock, flags);
	snd_trident_pcm_mixer_build(trident, tpcm, subchn->number, subchn);
	tpcm->subchn = subchn;
	subchn->runtime->private_data = tpcm;
	subchn->runtime->private_free = snd_trident_pcm_free_subchn;
	subchn->runtime->hw = &snd_trident_playback;
	snd_pcm_set_sync(subchn);
	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 int snd_trident_playback_close(void *private_data,
				      snd_pcm_subchn_t * subchn)
{
	trident_t *trident = snd_magic_cast(trident_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_trident_voice_t *tpcm = (snd_trident_voice_t *) runtime->private_data;

	snd_trident_pcm_mixer_free(trident, tpcm, subchn->number);
	snd_pcm_dma_free(subchn);
	return 0;
}

/*---------------------------------------------------------------------------
   snd_trident_spdif_open
  
   Description: This routine will open the 4DWave SPDIF device.

   Parameters:	subchn  - PCM subchannel class

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

static int snd_trident_spdif_open(void *private_data,
				  snd_pcm_subchn_t * subchn)
{
	unsigned long flags;
	trident_t *trident = snd_magic_cast(trident_t, private_data, -ENXIO);
	snd_trident_voice_t *tpcm;
	snd_pcm_runtime_t *runtime = subchn->runtime;
	int err;

	if ((err = snd_pcm_dma_alloc(subchn,
				     trident->dma4ptr,
				     "Trident 4DWave (S/PDIF playback)")) < 0)
		return err;
	runtime->dig_mask = (snd_pcm_digital_t *) snd_kcalloc(sizeof(snd_pcm_digital_t), GFP_KERNEL);
	if (!runtime->dig_mask) {
		snd_pcm_dma_free(subchn);
		return -ENOMEM;
	}
	runtime->dig_mask_free = _snd_kfree;
	spin_lock_irqsave(&trident->reg_lock, flags);
	tpcm = snd_trident_alloc_voice(trident, SND_TRIDENT_VOICE_TYPE_PCM, 0, 0);
	if (tpcm == NULL) {
		spin_unlock_irqrestore(&trident->reg_lock, flags);
		snd_pcm_dma_free(subchn);
		return -EAGAIN;
	}
	tpcm->trident = trident;
	tpcm->subchn = subchn;
	// set channel status bit mask
	runtime->dig_mask->dig_status[0] = 0xff;
	runtime->dig_mask->dig_status[1] = 0xff;
	runtime->dig_mask->dig_status[2] = 0xff;
	runtime->dig_mask->dig_status[3] = 0xff;
	runtime->dig_mask->dig_valid = 1;
	spin_unlock_irqrestore(&trident->reg_lock, flags);

	runtime->private_data = tpcm;
	runtime->private_free = snd_trident_pcm_free_subchn;
	runtime->hw = &snd_trident_spdif;
	return 0;
}


/*---------------------------------------------------------------------------
   snd_trident_spdif_close
  
   Description: This routine will close the 4DWave SPDIF device.
                
   Parameters:	subchn  - PCM subchannel class

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

static int snd_trident_spdif_close(void *private_data,
				   snd_pcm_subchn_t * subchn)
{
	unsigned long flags;
	trident_t *trident = snd_magic_cast(trident_t, private_data, -ENXIO);
	spin_lock_irqsave(&trident->reg_lock, flags);
	// restore default SPDIF setting
	outb(trident->spdif_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
	outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS));
	spin_unlock_irqrestore(&trident->reg_lock, flags);
	snd_pcm_dma_free(subchn);
	return 0;
}

/*---------------------------------------------------------------------------
   snd_trident_capture_open
  
   Description: This routine will open the 4DWave capture device.

   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 = snd_magic_cast(trident_t, private_data, -ENXIO);
	snd_trident_voice_t *tpcm;
	int err;

	if ((err = snd_pcm_dma_alloc(subchn,
				     trident->dma2ptr,
				     "Trident 4DWave (capture)")) < 0)
		return err;
	spin_lock_irqsave(&trident->reg_lock, flags);
	tpcm = snd_trident_alloc_voice(trident, SND_TRIDENT_VOICE_TYPE_PCM, 0, 0);
	if (tpcm == NULL) {
		spin_unlock_irqrestore(&trident->reg_lock, flags);
		snd_pcm_dma_free(subchn);
		return -EAGAIN;
	}
	tpcm->trident = trident;
	spin_unlock_irqrestore(&trident->reg_lock, flags);
	tpcm->subchn = subchn;
	subchn->runtime->private_data = tpcm;
	subchn->runtime->private_free = snd_trident_pcm_free_subchn;
	subchn->runtime->hw = &snd_trident_capture;
	snd_pcm_set_sync(subchn);
	snd_pcm_set_mixer(subchn, trident->mixer->device, trident->ac97->me_capture);
	return 0;
}

/*---------------------------------------------------------------------------
   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 int snd_trident_capture_close(void *private_data,
				     snd_pcm_subchn_t * subchn)
{
	snd_pcm_dma_free(subchn);
	return 0;
}

/*---------------------------------------------------------------------------
   snd_trident_foldback_open
  
   Description: This routine will open the 4DWave foldback capture device.

   Parameters:	subchn  - PCM subchannel class

   Returns:     status  - success or failure flag

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

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

	trident_t *trident = snd_magic_cast(trident_t, private_data, -ENXIO);
	snd_trident_voice_t *tpcm;
	int err;

	if ((err = snd_pcm_dma_alloc(subchn,
				     trident->dma3ptr,
				     "Trident 4DWave (foldback)")) < 0)
		return err;
	spin_lock_irqsave(&trident->reg_lock, flags);
	tpcm = snd_trident_alloc_voice(trident, SND_TRIDENT_VOICE_TYPE_PCM, 0, 0);
	if (tpcm == NULL) {
		spin_unlock_irqrestore(&trident->reg_lock, flags);
		snd_pcm_dma_free(subchn);
		return -EAGAIN;
	}
	tpcm->trident = trident;
	tpcm->subchn = subchn;
	tpcm->foldback_chan = subchn->number;
	spin_unlock_irqrestore(&trident->reg_lock, flags);
	subchn->runtime->private_data = tpcm;
	subchn->runtime->private_free = snd_trident_pcm_free_subchn;
	subchn->runtime->hw = &snd_trident_foldback;
	return 0;
}

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

  ---------------------------------------------------------------------------*/
static int snd_trident_foldback_close(void *private_data,
				      snd_pcm_subchn_t * subchn)
{
	trident_t *trident = snd_magic_cast(trident_t, private_data, -ENXIO);
	snd_trident_voice_t *tpcm;
	unsigned long flags;
	snd_pcm_runtime_t *runtime = subchn->runtime;
	tpcm = (snd_trident_voice_t *) runtime->private_data;
	
	/* stop capture channel */
	spin_lock_irqsave(&trident->reg_lock, flags);
	outb(0x00, TRID_REG(trident, T4D_RCI + tpcm->foldback_chan));
	spin_unlock_irqrestore(&trident->reg_lock, flags);

	snd_pcm_dma_free(subchn);
	return 0;
}

/*---------------------------------------------------------------------------
   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 = snd_magic_cast(trident_t, private_data, );
	trident->pcm = NULL;
}

static void snd_trident_foldback_pcm_free(void *private_data)
{
	trident_t *trident = snd_magic_cast(trident_t, private_data, );
	trident->foldback = NULL;
}

static void snd_trident_spdif_pcm_free(void *private_data)
{
	trident_t *trident = snd_magic_cast(trident_t, private_data, );
	trident->spdif = 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
  
  ---------------------------------------------------------------------------*/

int snd_trident_pcm(trident_t * trident, int device, snd_pcm_t ** rpcm)
{
	snd_pcm_t *pcm;
	int err;

	*rpcm = NULL;
	//snd_printk("trid: called snd_trident_pcm\n");
	if ((err = snd_pcm_new(trident->card, "trident_dx_nx", device, trident->ChanPCM, 1, &pcm)) < 0)
		return err;

	pcm->private_data = trident;
	pcm->private_free = snd_trident_pcm_free;

	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].private_data = trident;
	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].open = snd_trident_playback_open;
	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].close = snd_trident_playback_close;

	pcm->chn[SND_PCM_CHANNEL_CAPTURE].private_data = trident;
	pcm->chn[SND_PCM_CHANNEL_CAPTURE].open = snd_trident_capture_open;
	pcm->chn[SND_PCM_CHANNEL_CAPTURE].close = snd_trident_capture_close;

	pcm->info_flags = SND_PCM_INFO_PLAYBACK | SND_PCM_INFO_CAPTURE |
			  SND_PCM_INFO_DUPLEX;
	strcpy(pcm->name, "Trident 4DWave");
	*rpcm = trident->pcm = pcm;
	return 0;
}

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

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

int snd_trident_foldback_pcm(trident_t * trident, int device, snd_pcm_t ** rpcm)
{
	snd_pcm_t *foldback;
	int err;
	int num_chan = 3;

	*rpcm = NULL;
	if (trident->device == TRIDENT_DEVICE_ID_NX)
		num_chan = 4;
	if ((err = snd_pcm_new(trident->card, "trident_dx_nx", device, 0, num_chan, &foldback)) < 0)
		return err;

	foldback->private_data = trident;
	foldback->private_free = snd_trident_foldback_pcm_free;
	foldback->chn[SND_PCM_CHANNEL_CAPTURE].private_data = trident;
	foldback->chn[SND_PCM_CHANNEL_CAPTURE].open = snd_trident_foldback_open;
	foldback->chn[SND_PCM_CHANNEL_CAPTURE].close = snd_trident_foldback_close;
	foldback->info_flags = SND_PCM_INFO_CAPTURE;
	strcpy(foldback->name, "Trident 4DWave");
	*rpcm = trident->foldback = foldback;
	return 0;
}

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

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

int snd_trident_spdif_pcm(trident_t * trident, int device, snd_pcm_t ** rpcm)
{
	snd_pcm_t *spdif;
	int err;

	*rpcm = NULL;
	//snd_printk("trid: called snd_trident_pcm\n");
	if ((err = snd_pcm_new(trident->card, "trident_dx_nx", device, 1, 0, &spdif)) < 0)
		return err;

	spdif->private_data = trident;
	spdif->private_free = snd_trident_spdif_pcm_free;
	spdif->chn[SND_PCM_CHANNEL_PLAYBACK].private_data = trident;
	spdif->chn[SND_PCM_CHANNEL_PLAYBACK].open = snd_trident_spdif_open;
	spdif->chn[SND_PCM_CHANNEL_PLAYBACK].close = snd_trident_spdif_close;
	spdif->info_flags = SND_PCM_INFO_PLAYBACK;
	strcpy(spdif->name, "Trident 4DWave S/PDIF");
	*rpcm = trident->spdif = spdif;
	return 0;
}

/*
 *  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 = snd_magic_cast(trident_t, kswitch->private_data, -ENXIO);
	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 = snd_magic_cast(trident_t, kswitch->private_data, -ENXIO);
	unsigned char val, val1;
	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 */
	trident->spdif_bits = 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(trident->spdif_bits, 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 =
{
	name:           "S/PDIF Mixer Out",
	get:            (snd_get_switch_t *)snd_trident_spdif_get_switch,
	set:            (snd_set_switch_t *)snd_trident_spdif_set_switch,
	private_value:  0x28,
};

static int snd_trident_mixer_ac97_sw2(snd_kmixer_element_t *element, int w_flag, int *value, int shift)
{
	trident_t *trident = snd_magic_cast(trident_t, element->private_data, -ENXIO);
	unsigned long flags;
	unsigned int val, mask;
	int change = 0;

	if (trident->device != TRIDENT_DEVICE_ID_NX)
		return 0;
	spin_lock_irqsave(&trident->reg_lock, flags);
	mask = 1 << shift;
	val = trident->ac97_ctrl = inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
	val = (val & mask) >> shift;
	if (!w_flag) {
		*value = val;
	} else {
		change = val != *value;
		trident->ac97_ctrl &= ~mask;
		trident->ac97_ctrl |= (*value << shift);
		outl(trident->ac97_ctrl, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
	}
	spin_unlock_irqrestore(&trident->reg_lock, flags);
	return change;
}

#define TRIDENT_AC97_CTRL(name, shift) \
	static int snd_trident_mixer_ac97_sw2_##name(snd_kmixer_element_t *element, \
						     int w_flag, \
						     int *value) \
	{ \
		return snd_trident_mixer_ac97_sw2(element, \
						  w_flag, \
						  value, \
						  shift); \
	}

TRIDENT_AC97_CTRL(reverb, 4)

static int snd_trident_mixer_volume_stereo(snd_kmixer_element_t *element, int w_flag, int *volume, int offset)
{
	trident_t *trident = snd_magic_cast(trident_t, element->private_data, -ENXIO);
	unsigned long flags;
	unsigned char left, right;
	int change = 0;
	int max = 0xff;

	spin_lock_irqsave(&trident->reg_lock, flags);
	left = max - ((trident->musicvol_wavevol >> offset) & max);
	right = max - ((trident->musicvol_wavevol >> (offset + 8)) & max);
	if (!w_flag) {
		volume[0] = left;
		volume[1] = right;
	} else {
		change = volume[0] != left || volume[1] != right;
		trident->musicvol_wavevol &= ~((max << (offset + 8)) | (max << offset));
		trident->musicvol_wavevol |= ((max - volume[1]) << (offset + 8)) | ((max - volume[0]) << offset);
		outl(trident->musicvol_wavevol, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL));
	}
	spin_unlock_irqrestore(&trident->reg_lock, flags);
	return change;
}

#define TRIDENT_VOLUME(name, offset) \
	static int snd_trident_mixer_volume_##name(snd_kmixer_element_t *element, int w_flag, int *volume) \
	{ \
		return snd_trident_mixer_volume_stereo(element, w_flag, volume, (offset)); \
	}

TRIDENT_VOLUME(music, 16)
TRIDENT_VOLUME(wave, 0)

static int snd_trident_mixer_volume_vol(snd_kmixer_element_t *element,
					int w_flag,
					int *volume)
{
	snd_trident_voice_t *tpcm = (snd_trident_voice_t *) element->private_data;
	trident_t *trident = tpcm->trident;
	unsigned long flags;
	unsigned char val;
	int change = 0;
	int max = 0xff;

	if (!tpcm->use)
		return -EINVAL;
	spin_lock_irqsave(&trident->reg_lock, flags);
	val = max - tpcm->Vol;
	if (!w_flag) {
		volume[0] = val;
	} else {
		change = volume[0] != val;
		tpcm->Vol = max - volume[0];
		outb(tpcm->number, TRID_REG(trident, T4D_LFO_GC_CIR));
		outb(tpcm->Vol, TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC + 2));
	}
	spin_unlock_irqrestore(&trident->reg_lock, flags);
	return change;
}

static int snd_trident_mixer_control_pan(snd_kmixer_element_t *element,
					 int w_flag,
					 int *pan)
{
	snd_trident_voice_t *tpcm = (snd_trident_voice_t *) element->private_data;
	trident_t *trident = tpcm->trident;
	unsigned long flags;
	unsigned char val, gvsel_pan;
	int change = 0;
	int max = 0x3f;

	if (!tpcm->use)
		return -EINVAL;
	spin_lock_irqsave(&trident->reg_lock, flags);
	if (tpcm->Pan & 0x40)
		val = max - (tpcm->Pan & max);	/* attenuate right (pan to left) */
	else
		val = tpcm->Pan | 0x40;			/* attenuate left (pan to right) */
	if (!w_flag) {
		pan[0] = val;
	} else {
		change = pan[0] != val;
		if (pan[0] & 0x40)
			tpcm->Pan = pan[0] & max;	/* attenuate left */
		else
			tpcm->Pan = (max - (pan[0] & max)) | 0x40; /* attenuate right */
		outb(tpcm->number, TRID_REG(trident, T4D_LFO_GC_CIR));
		gvsel_pan = inb(TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC + 3));
		gvsel_pan &= ~(max | 0x40);
		gvsel_pan |= tpcm->Pan;
		outb(gvsel_pan, TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC + 3));
	}
	spin_unlock_irqrestore(&trident->reg_lock, flags);
	return change;
}

static int snd_trident_mixer_volume_rvol(snd_kmixer_element_t *element,
					 int w_flag,
					 int *volume)
{
	snd_trident_voice_t *tpcm = (snd_trident_voice_t *) element->private_data;
	trident_t *trident = tpcm->trident;
	unsigned long flags;
	unsigned char val;
	unsigned short rvol_cvol;
	int change = 0;
	int max = 0x7f;

	if (!tpcm->use)
		return -EINVAL;
	spin_lock_irqsave(&trident->reg_lock, flags);
	val = max - (tpcm->RVol & max);
	if (!w_flag) {
		volume[0] = val;
	} else {
		change = volume[0] != val;
		tpcm->RVol = (max - (volume[0] & max));
		outb(tpcm->number, TRID_REG(trident, T4D_LFO_GC_CIR));
		rvol_cvol = inw(TRID_REG(trident, trident->device == TRIDENT_DEVICE_ID_NX ? CH_NX_ALPHA_FMS_FMC_RVOL_CVOL : CH_DX_FMC_RVOL_CVOL));
		rvol_cvol &= ~(max << 7);
		rvol_cvol |= (tpcm->RVol << 7);
		outw(rvol_cvol, TRID_REG(trident, trident->device == TRIDENT_DEVICE_ID_NX ? CH_NX_ALPHA_FMS_FMC_RVOL_CVOL : CH_DX_FMC_RVOL_CVOL));
	}
	spin_unlock_irqrestore(&trident->reg_lock, flags);
	return change;
}

#if 0
static int snd_trident_mixer_volume_cvol(snd_kmixer_element_t *element,
					 int w_flag,
					 int *volume)
{
	snd_trident_voice_t *tpcm = (snd_trident_voice_t *) element->private_data;
	trident_t *trident = tpcm->trident;
	unsigned long flags;
	unsigned char val;
	unsigned short rvol_cvol;
	int change = 0;
	int max = 0x7f;

	if (!tpcm->use)
		return -EINVAL;
	spin_lock_irqsave(&trident->reg_lock, flags);
	val = max - (tpcm->CVol & max);
	if (!w_flag) {
		volume[0] = val;
	} else {
		change = volume[0] != val;
		tpcm->CVol = (max - (volume[0] & max));
		outb(tpcm->number, TRID_REG(trident, T4D_LFO_GC_CIR));
		rvol_cvol = inw(TRID_REG(trident, trident->device == TRIDENT_DEVICE_ID_NX ? CH_NX_ALPHA_FMS_FMC_RVOL_CVOL : CH_DX_FMC_RVOL_CVOL));
		rvol_cvol &= ~(max);
		rvol_cvol |= tpcm->CVol;
		outw(rvol_cvol, TRID_REG(trident, trident->device == TRIDENT_DEVICE_ID_NX ? CH_NX_ALPHA_FMS_FMC_RVOL_CVOL : CH_DX_FMC_RVOL_CVOL));
	}
	spin_unlock_irqrestore(&trident->reg_lock, flags);
	return change;
}
#endif

static int snd_trident_mixer_group_ctrl1(snd_kmixer_group_t * group,
				         snd_kmixer_file_t * file,
				         int w_flag,
				         snd_mixer_group_t * ugroup,
				         snd_mixer_volume1_control_t *volume1,
				         snd_kmixer_element_t *volume1_element)
{
	int voices[2];
	int change = 0;

	if (!w_flag) {
		ugroup->caps = 0;
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;
		ugroup->caps |= SND_MIXER_GRPCAP_VOLUME;
		volume1(volume1_element, 0, voices);
		ugroup->volume.names.front_left = voices[0];
		ugroup->volume.names.front_right = voices[1];
		ugroup->min = 0;
		ugroup->max = 0xff;
	} else {
		voices[0] = ugroup->volume.names.front_left & 0xff;
		voices[1] = ugroup->volume.names.front_right & 0xff;
		if (volume1(volume1_element, 1, voices) > 0) {
			snd_mixer_element_value_change(file, volume1_element, 0);
			change = 1;
		}
	}
	return change;
}

#define TRIDENT_MIXER_GROUP1(name, volume1_element) \
	static int snd_trident_mixer_group_##name(snd_kmixer_group_t * group, \
						  snd_kmixer_file_t * file, \
						  int w_flag, \
						  snd_mixer_group_t * ugroup) \
	{ \
		trident_t *trident = snd_magic_cast(trident_t, group->private_data, -ENXIO); \
		return snd_trident_mixer_group_ctrl1(group, file, \
						     w_flag, ugroup, \
						     snd_trident_mixer_volume_##name, \
						     volume1_element); \
        }


static int snd_trident_mixer_group_pcm(snd_kmixer_group_t * group,
				       snd_kmixer_file_t * file,
				       int w_flag,
				       snd_mixer_group_t * ugroup)
{
	snd_trident_voice_t *tpcm = (snd_trident_voice_t *) group->private_data;
	trident_t *trident = tpcm->trident;
	snd_trident_pcm_mixer_t *tmix = &trident->pcm_mixer[tpcm->number - 32];	
	int voices[2];
	int change = 0;

	if (!w_flag) {
		ugroup->caps = 0;
		ugroup->channels = SND_MIXER_CHN_MASK_MONO;
		ugroup->caps |= SND_MIXER_GRPCAP_VOLUME;
		snd_trident_mixer_volume_vol(tmix->me_vol_vol, 0, voices);
		ugroup->volume.names.front_left = voices[0];
		ugroup->volume.names.front_right = voices[1];
		ugroup->min = 0;
		ugroup->max = 0xff;
	} else {
		voices[0] = ugroup->volume.names.front_left & 0xff;
		if (snd_trident_mixer_volume_vol(tmix->me_vol_vol, 1, voices) > 0) {
			snd_mixer_element_value_change(file, tmix->me_vol_vol, 0);
			change = 1;
		}
	}
	return change;
}

#if 0
static int snd_trident_mixer_group_pcm_rear(snd_kmixer_group_t * group,
					    snd_kmixer_file_t * file,
					    int w_flag,
					    snd_mixer_group_t * ugroup)
{
	snd_trident_voice_t *tpcm = (snd_trident_voice_t *) group->private_data;
	trident_t *trident = tpcm->trident;
	snd_trident_pcm_mixer_t *tmix = &trident->pcm_mixer[tpcm->number - 32];	
	int voices[2];
	int max = 0xff;
	int change = 0;

	if (!w_flag) {
		ugroup->caps = SND_MIXER_GRPCAP_VOLUME;
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;
		if (tmix->me_vol_rvol)
			ugroup->channels |= SND_MIXER_CHN_MASK_REAR_LEFT |
					    SND_MIXER_CHN_MASK_REAR_RIGHT;
		if (tmix->me_vol_cvol)
			ugroup->channels |= SND_MIXER_CHN_MASK_FRONT_CENTER;
		ugroup->min = 0;
		ugroup->max = max;

		/* front */
		snd_trident_mixer_volume_vol(tmix->me_vol_vol, 0, voices);
		ugroup->volume.names.front_left = voices[0];
		ugroup->volume.names.front_right = voices[0];
		/* rear */
		if (tmix->me_vol_rvol) {
			snd_trident_mixer_volume_rvol(tmix->me_vol_rvol, 0, voices);
			ugroup->volume.names.rear_left = voices[0];
			ugroup->volume.names.rear_right = voices[0];
			ugroup->volume.names.rear_left <<= 1;
			ugroup->volume.names.rear_right <<= 1;
		}
		/* center */
		if (tmix->me_vol_cvol) {
			snd_trident_mixer_volume_cvol(tmix->me_vol_cvol, 0, voices);
			ugroup->volume.names.front_center = voices[0];
			ugroup->volume.names.front_center <<= 1;
		}
	} else {
		voices[0] = ugroup->volume.names.front_left & max;
		if (snd_trident_mixer_volume_vol(tmix->me_vol_vol, 1, voices) > 0) {
			snd_mixer_element_value_change(file, tmix->me_vol_vol, 0);
			change = 1;
		}
		if (tmix->me_vol_rvol) {
			voices[0] = ugroup->volume.names.rear_left & max;
			voices[0] >>= 1;
			if (snd_trident_mixer_volume_rvol(tmix->me_vol_rvol, 1, voices) > 0) {
				snd_mixer_element_value_change(file, tmix->me_vol_rvol, 0);
				change = 1;
			}
		}
		if (tmix->me_vol_cvol) {
			voices[0] = ugroup->volume.names.front_center & max;
			voices[0] >>= 1;
			if (snd_trident_mixer_volume_cvol(tmix->me_vol_cvol, 1, voices) > 0) {
				snd_mixer_element_value_change(file, tmix->me_vol_cvol, 0);
				change = 1;
			}
		}
	}
	return change;
}
#endif

TRIDENT_MIXER_GROUP1(music, trident->me_vol_music)
TRIDENT_MIXER_GROUP1(wave, trident->me_vol_wave)

static int snd_trident_pcm_mixer_build(trident_t *trident, snd_trident_voice_t *voice, int idx, snd_pcm_subchn_t *subchn)
{
	snd_kmixer_t *mixer = trident->mixer;
	snd_kmixer_group_t *group;
	snd_trident_pcm_mixer_t *tmix;

	int volume;
	static struct snd_mixer_element_volume1_range vol_range[1] = {
		{0, 0xff, -3200, 0},
	};
	static struct snd_mixer_element_volume1_range rvol_cvol_range[1] = {
		{0, 0x7f, -3200, 0},
	};
	static struct snd_mixer_element_pan_control1_range pan_range[1] = {
		{SND_MIXER_PAN_LEFT_RIGHT, 0, 0x7f, -1600, 0},
	};

	snd_debug_check(mixer == NULL, -EINVAL);
	tmix = &trident->pcm_mixer[voice->number - 32];
	snd_mixer_lock(mixer, 0);
	/* build pcm group */
	if ((group = snd_mixer_lib_group_ctrl(mixer, "PCM Playback", idx, SND_MIXER_OSS_UNKNOWN, snd_trident_mixer_group_pcm, voice)) == NULL)
		goto __error;
	tmix->group = group;
	if ((tmix->me_pcm = snd_mixer_lib_pcm2(mixer, "PCM Playback", idx, SND_MIXER_ETYPE_PLAYBACK2, trident->pcm->device, idx)) == NULL)
		goto __error;
	snd_pcm_set_mixer(subchn, trident->mixer->device, tmix->me_pcm);
	/* pcm front */
	if ((tmix->me_vol_vol = snd_mixer_lib_volume1(mixer, "PCM Front Volume", idx, 1, vol_range, snd_trident_mixer_volume_vol, voice)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, tmix->me_pcm, tmix->me_vol_vol) < 0)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, tmix->me_vol_vol) < 0)
		goto __error;
	/* pcm pan */
	if ((tmix->me_pan = snd_mixer_lib_pan_control1(mixer, "PCM Pan Control", idx, 1, pan_range, snd_trident_mixer_control_pan, voice)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, tmix->me_pan) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, tmix->me_vol_vol, tmix->me_pan) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, tmix->me_pan, trident->me_wave) < 0)
		goto __error;
	/* pcm rear */
	if ((tmix->me_vol_rvol = snd_mixer_lib_volume1(mixer, "PCM Reverb Volume", idx, 1, rvol_cvol_range, snd_trident_mixer_volume_rvol, voice)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, tmix->me_pcm, tmix->me_vol_rvol) < 0)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, tmix->me_vol_rvol) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, tmix->me_vol_rvol, trident->me_reverb) < 0)
		goto __error;

	volume = 0xff;
	snd_trident_mixer_volume_vol(tmix->me_vol_vol, 1, &volume);
	volume = 0x7f;
	snd_trident_mixer_volume_rvol(tmix->me_vol_rvol, 1, &volume);
	
	snd_mixer_lock(mixer, 1);
	return 0;

     __error:
	snd_mixer_lock(mixer, 1);
	snd_trident_pcm_mixer_free(trident, voice, idx);
	return -EINVAL;
}

static int snd_trident_pcm_mixer_free(trident_t *trident, snd_trident_voice_t *voice, int idx)
{
	snd_kmixer_t *mixer = trident->mixer;
	snd_kmixer_group_t *group;
	snd_trident_pcm_mixer_t *tmix;
	
	snd_debug_check(mixer == NULL || voice == NULL, -EINVAL);
	tmix = &trident->pcm_mixer[voice->number - 32];
	group = tmix->group;
	snd_debug_check(group == NULL, -EINVAL);
	snd_mixer_lock(mixer, 0);
	/* remove all the routes, elements */
	/* remove pcm reverb */
	if (tmix->me_vol_rvol) {
		snd_mixer_group_element_remove(mixer, group, tmix->me_vol_rvol);
		snd_mixer_element_remove(mixer, tmix->me_vol_rvol);
		tmix->me_vol_rvol = NULL;
	}
	/* remove pcm pan */
	if (tmix->me_pan) {
		snd_mixer_group_element_remove(mixer, group, tmix->me_pan);
		snd_mixer_element_remove(mixer, tmix->me_pan);
		tmix->me_pan = NULL;
	}
	/* remove pcm front */
	if (tmix->me_vol_vol) {
		snd_mixer_group_element_remove(mixer, group, tmix->me_vol_vol);
		snd_mixer_element_remove(mixer, tmix->me_vol_vol);
		tmix->me_vol_vol = NULL;
	}
	/* remove pcm playback */
	if (tmix->me_pcm) {
		snd_mixer_element_remove(mixer, tmix->me_pcm);
		tmix->me_pcm = NULL;
	}
	/* remove pcm playback group */
	if (tmix->group) {
		snd_mixer_group_remove(mixer, tmix->group);
		tmix->group = NULL;
	}
	snd_mixer_lock(mixer, 1);
	return 0;
}

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

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

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

int snd_trident_mixer(trident_t * trident, int device, snd_pcm_t * pcm, snd_kmixer_t ** rmixer)
{
	ac97_t _ac97, *ac97;
	snd_kmixer_t *mixer;
	snd_kmixer_group_t *group;
	int err;
	static struct snd_mixer_element_volume1_range stereo_range[2] = {
		{0, 255, -6400, 0},
		{0, 255, -6400, 0},
	};

	*rmixer = NULL;
	// snd_printk("trid: called snd_trident_mixer\n");
	memset(&_ac97, 0, sizeof(_ac97));
	_ac97.write = WriteAC97;
	_ac97.read = ReadAC97;
	_ac97.private_data = trident;
	_ac97.private_free = snd_trident_mixer_free_ac97;
	if ((err = snd_ac97_mixer(trident->card, device, &_ac97, 1, &pcm->device, &mixer)) < 0)
		return err;

	ac97 = snd_magic_cast(ac97_t, (mixer->private_data), -ENXIO);
	/* build wave volume */
	if ((group = snd_mixer_lib_group_ctrl(mixer, "Wave", 0, SND_MIXER_OSS_UNKNOWN, snd_trident_mixer_group_wave, trident)) == NULL)
		goto __error;
	if ((trident->me_wave = snd_mixer_lib_accu1(mixer, "Wave", 0, 0)) == NULL)
		goto __error;
	if ((trident->me_vol_wave = snd_mixer_lib_volume1(mixer, "Wave Volume", 0, 2, stereo_range, snd_trident_mixer_volume_wave, trident)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, trident->me_vol_wave) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, trident->me_wave, trident->me_vol_wave) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, trident->me_vol_wave, ac97->me_playback) < 0)
		goto __error;
	/* build music volume */
	if ((group = snd_mixer_lib_group_ctrl(mixer, "Music", 0, SND_MIXER_OSS_UNKNOWN, snd_trident_mixer_group_music, trident)) == NULL)
		goto __error;
	if ((trident->me_music = snd_mixer_lib_accu1(mixer, "Music", 0, 0)) == NULL)
		goto __error;
	if ((trident->me_vol_music = snd_mixer_lib_volume1(mixer, "Music Volume", 0, 2, stereo_range, snd_trident_mixer_volume_music, trident)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, trident->me_vol_music) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, trident->me_music, trident->me_vol_music) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, trident->me_vol_music, ac97->me_playback) < 0)
		goto __error;
	/* build reverb (rear) */
	if ((trident->me_reverb = snd_mixer_lib_accu1(mixer, "Reverb", 0, 0)) == NULL)
		goto __error;
	if (trident->device != TRIDENT_DEVICE_ID_NX) {
		if (snd_mixer_element_route_add(mixer, trident->me_reverb, ac97->me_playback) < 0)
			goto __error;
	} else {
		if ((trident->me_sw2_reverb = snd_mixer_lib_sw2(mixer, "Rear Out Switch", 0, snd_trident_mixer_ac97_sw2_reverb, trident)) == NULL)
			goto __error;
		if (snd_mixer_element_route_add(mixer, trident->me_reverb, trident->me_sw2_reverb) < 0)
			goto __error;
		if (ac97->me_in_surround)
			if (snd_mixer_element_route_add(mixer, trident->me_sw2_reverb, ac97->me_in_surround) < 0)
				goto __error;
	}

	if (trident->device == TRIDENT_DEVICE_ID_NX)
		snd_mixer_switch_new(mixer, &snd_trident_spdif_switch, trident);

	trident->ac97 = ac97;
	*rmixer = trident->mixer = mixer;
	return 0;

      __error:
	snd_device_free(trident->card, mixer);
	return -ENOMEM;
}

/*---------------------------------------------------------------------------
   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 = snd_magic_cast(trident_t, private_data, );

	//snd_printk("trid: called snd_trident_proc_read\n");
	snd_iprintf(buffer, "Trident 4DWave PCI %s\n\n", trident->device == TRIDENT_DEVICE_ID_NX ? "NX" : "DX");
	if (trident->device == TRIDENT_DEVICE_ID_NX) {
		snd_iprintf(buffer, "S/PDIF Mixer Out : %s\n", trident->spdif_ctrl == 0x28 ? "on" : "off");
		snd_iprintf(buffer, "Rear Speakers    : %s\n", trident->ac97_ctrl & 0x00000010 ? "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
  
  ---------------------------------------------------------------------------*/

int snd_trident_create(snd_card_t * card,
		       struct pci_dev *pci,
		       snd_dma_t * dma1ptr,
		       snd_dma_t * dma2ptr,
		       snd_dma_t * dma3ptr,
		       snd_dma_t * dma4ptr,
		       snd_irq_t * irqptr,
		       int pcm_channels,
		       int max_wavetable_size,
		       trident_t ** rtrident)
{
	trident_t *trident;
	unsigned short cmdw;
	unsigned int *lpChannelData;
	unsigned int ChanDwordCount;
	unsigned int i;
	int err;
	static snd_device_ops_t ops = {
		(snd_dev_free_t *)snd_trident_free,
		NULL,
		NULL
	};

	*rtrident = NULL;
	trident = snd_magic_kcalloc(trident_t, 0, GFP_KERNEL);
	if (trident == NULL)
		return -ENOMEM;
	trident->card = card;
	spin_lock_init(&trident->reg_lock);
	spin_lock_init(&trident->event_lock);
	spin_lock_init(&trident->voice_alloc);
	if (pcm_channels < 1)
		pcm_channels = 1;
	if (pcm_channels > 32)
		pcm_channels = 32;
	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 == NULL) {
		snd_magic_kfree(trident);
		return -ENOMEM;
	}
	lpChannelData = (unsigned int *) snd_kcalloc(8 * (sizeof(unsigned int) * ChanDwordCount), GFP_KERNEL);
	if (lpChannelData == NULL) {
		snd_kfree(trident->ChRegs);
		snd_magic_kfree(trident);
		return -ENOMEM;
	}

	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->device = (pci->vendor << 16) | pci->device;
	trident->pci = pci;
	trident->dma1ptr = dma1ptr;
	trident->dma2ptr = dma2ptr;
	trident->dma3ptr = dma3ptr;
	trident->dma4ptr = dma4ptr;
	trident->irqptr = irqptr;
	trident->port = pci_resource_start(pci, 0);
	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_IO) {
		cmdw |= PCI_COMMAND_IO;
		pci_write_config_word(pci, PCI_COMMAND, cmdw);
	}
	pci_set_master(pci);
	trident->port = pci_resource_start(pci, 0);

	/* initialize chip */

	if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
		/* warm reset of the AC'97 codec */
		i = inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL));
		outl(i | 1, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
		udelay(100);
		/* release reset (warm & cold) */
		outl(i & ~3, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
		/* disable AC97 GPIO interrupt */
		outb(0x00, TRID_REG(trident, SI_AC97_GPIO));
		/* enable 64 channel mode */
		outl(BANK_B_EN, TRID_REG(trident, T4D_LFO_GC_CIR));
	}
	//outl(0x00, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL));
	WriteAC97(trident, 0x0L, 0L);
	WriteAC97(trident, 0x02L, 0L);
	WriteAC97(trident, 0x18L, 0L);
	
	if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
		/* enable playback to the ac97 codec */
		trident->ac97_ctrl = 0x00000002;
		outl(trident->ac97_ctrl, TRID_REG(trident, trident->device == TRIDENT_DEVICE_ID_NX ? NX_ACR0_AC97_COM_STAT : 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);
	if ((err = snd_device_new(card, SND_DEV_LOWLEVEL, trident, 0, &ops, NULL)) < 0) {
		snd_trident_free(trident);
		return err;
	}
	*rtrident = trident;
	return 0;
}

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

int snd_trident_free(trident_t * trident)
{
	DisableEndInterrupts(trident);
	// Disable S/PDIF out
	if (trident->device == TRIDENT_DEVICE_ID_NX)
		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_magic_kfree(trident);
	return 0;
}

/*---------------------------------------------------------------------------
   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_voice_t *tpcm;
	int channel;
	unsigned int audio_int;

	audio_int = inl(TRID_REG(trident, T4D_MISCINT));
	if ((audio_int & (ADDRESS_IRQ|MPU401_IRQ)) == 0)
		return;
	if (audio_int & ADDRESS_IRQ) {
		unsigned int temp;
		unsigned int eso;
		unsigned int ChanDwordCount = trident->ChanDwordCount;
		// get interrupt status for all channels
		ReadAint(trident->ChRegs);
		for (channel = 0; channel < 64; channel++) {
			// check if interrupt occured for channel
			unsigned int x = channel >> 5;
			unsigned int dwMask = ((unsigned int) 1L) << (channel & 0x1f);
			unsigned int old = trident->ChRegs->lpChAint[x];
			unsigned int old1 = trident->ChRegs->lpChAint[x^1];
			if (old & dwMask) {
				spin_lock(&trident->reg_lock);
				if (trident->synth.voices[channel].pcm) {
					/* pcm interrupt */
					tpcm = &trident->synth.voices[channel];
					if (tpcm->running) {
						outb((unsigned char) tpcm->number, TRID_REG(trident, T4D_LFO_GC_CIR));
						if (tpcm->ignore_middle) {
							// get CSPF for channel (ie. CSO >= ESO/2)
							if (channel >= 32)
								temp = inl(TRID_REG(trident,T4D_CSPF_B ));
							else
								temp = inl(TRID_REG(trident,T4D_CSPF_A ));
							if (!(temp & dwMask)) {
								spin_unlock(&trident->reg_lock);
								snd_pcm_transfer_done(tpcm->subchn);
								spin_lock(&trident->reg_lock);
							}
						} else {
							if (trident->device == TRIDENT_DEVICE_ID_NX)
								temp = inl(TRID_REG(trident, CH_NX_DELTA_CSO)) & 0x00ffffff;
							else
								temp = inw(TRID_REG(trident, CH_DX_CSO_ALPHA_FMS + 2));
							if( temp >= tpcm->csoint ) {
								// set next interrupt location
								tpcm->csoint += tpcm->count;
								if (tpcm->csoint < tpcm->eso) {
									eso = tpcm->csoint << 1;
								} else {
									// time for channel to loop
									eso = tpcm->eso - 1;
									tpcm->csoint = 0;
								}
								if (trident->device == TRIDENT_DEVICE_ID_NX) {
									outb(((eso >> 16) & 0xff), TRID_REG(trident, CH_NX_DELTA_ESO + 2));
									outw((eso & 0xffff), TRID_REG(trident, CH_NX_DELTA_ESO));
								} else {
									outw((eso & 0xffff), TRID_REG(trident, CH_DX_ESO_DELTA + 2));
								}
								spin_unlock(&trident->reg_lock);
								snd_pcm_transfer_done(tpcm->subchn);
								spin_lock(&trident->reg_lock);
							}
						}
					} 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);
				}
				spin_unlock(&trident->reg_lock);
				trident->ChRegs->lpChAint[x] = dwMask;
				trident->ChRegs->lpChAint[x^1] = 0;
				IWriteAint(trident->ChRegs);
				trident->ChRegs->lpChAint[x] = (old & ~dwMask);
				trident->ChRegs->lpChAint[x^1] = old1;
			}
		}
	}
	if (audio_int & MPU401_IRQ) {
		if (trident->rmidi) {
			unsigned int val;
			val = inb(TRID_REG(trident, T4D_MPUR1));
			if (!(val & 0x80))
				snd_mpu401_uart_interrupt(trident->rmidi);
		}
	}
	// outl((ST_TARGET_REACHED | MIXER_OVERFLOW | MIXER_UNDERFLOW), TRID_REG(trident, T4D_MISCINT));
}

/*---------------------------------------------------------------------------
   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
	if (snd_seq_device_new(trident->card, 1, SND_SEQ_DEV_ID_TRIDENT,
			       sizeof(trident_t*), &trident->seq_dev) >= 0) {
		strcpy(trident->seq_dev->name, "4DWave");
		*(trident_t**)SND_SEQ_DEVICE_ARGPTR(trident->seq_dev) = trident;
	}
#endif
	return 0;
}

int snd_trident_detach_synthesizer(trident_t *trident)
{
#ifdef CONFIG_SND_SEQUENCER
	if (trident->seq_dev) {
		snd_device_free(trident->card, 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;
	voice->subchn = 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_foldback_pcm);
EXPORT_SYMBOL(snd_trident_spdif_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
 */

static int __init alsa_trident_init(void)
{
	return 0;
}

static void __exit alsa_trident_exit(void)
{
}

module_init(alsa_trident_init)
module_exit(alsa_trident_exit)
