/*
 *  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 "driver.h"
#include "info.h"
#include "control.h"
#include "trid4dwave.h"

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

/*
 *  common I/O routines
 */


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

    snd_spin_lock(trident, reg, &flags);
    if (!trident->isNX)
    {
        data = (0x00008000L | (reg & 0x000000ff));
        outl(data, TRID_REG(trident, DX_ACR1_AC97_R));
        do
        {
            data = inl(TRID_REG(trident, DX_ACR1_AC97_R));
            if ( ( data & 0x0008000 ) == 0 )
                break;
        }
        while(wCount--);

        if ( wCount == 0 ) {
          snd_printk("trid: ReadAC97 ERROR ac97 register TIMEOUT!!!\n");
          data = 0;
        }
    }
    else  // ID_4DWAVE_NX
    {
        data = (0x00000800L | (reg & 0x000000ff));
        outl(data, TRID_REG(trident, NX_ACR2_AC97_R_PRIMARY));
        do
        {
            data = inl(TRID_REG(trident, NX_ACR2_AC97_R_PRIMARY));
            if ( ( data & 0x00000C00 ) == 0 )
                break;
        }
        while(wCount--);

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

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

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

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

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

        if ( wCount == 0 ) {
          snd_printk("trid: WriteAC97 ERROR Write ac97 register TIMEOUT!!!\n");
          snd_spin_unlock(trident, reg, &flags);
          return;
        }

        ulData |= (0x8000 | (reg & 0x000000ff));
    }
    else  // ID_4DWAVE_NX
    {
        address = NX_ACR1_AC97_W;
        /* read AC-97 write register status */
        do
        {
            if ((inw(TRID_REG(trident, address )) & 0x0800) == 0)
                break ;
        }
        while (wCount--) ;

        if ( wCount == 0 )
        {
          snd_printk("trid: WriteAC97 ERROR Write ac97 register TIMEOUT!!!\n");
          snd_spin_unlock(trident, reg, &flags);
          return;
        }

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

    outl(ulData, TRID_REG(trident, address));
    snd_spin_unlock(trident, reg, &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.
  
  ---------------------------------------------------------------------------*/

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

    IReadAinten(trident->ChRegs);
    dwMask = ((unsigned int) 1L) << (ChannelNum - 32);
    trident->ChRegs->lpChAinten[x] &= ~dwMask;
    IWriteAinten( trident->ChRegs );
    // Ack the channel in case the interrupt was set before we disable it.
    outl(dwMask, TRID_REG(trident, T4D_AINT_B));
}

/*---------------------------------------------------------------------------
   void EnableEndInterrupts( trident_t *trident)
  
   Description: This routine will enable end of loop interrupts.
                End of loop interrupts will occur when a running
                channel reaches ESO.
  
   Parameters:  trident - pointer to target device class for 4DWave.
  
   returns:     TRUE if everything went ok, else FALSE.
  
  ---------------------------------------------------------------------------*/

int EnableEndInterrupts(trident_t *trident )
{
    unsigned int GlobalControl;

    GlobalControl = inb(TRID_REG(trident, T4D_LFO_GC_CIR+1));
    GlobalControl |= 0x10;
    outb(GlobalControl, TRID_REG(trident, T4D_LFO_GC_CIR+1));

    return ( TRUE );
}

/*---------------------------------------------------------------------------
   void DisableEndInterrupts( trident_t *trident)
  
   Description: This routine will disable end of loop interrupts.
                End of loop interrupts will occur when a running
                channel reaches ESO.
  
   Parameters:  
                trident - pointer to target device class for 4DWave.
  
   returns:     TRUE if everything went ok, else FALSE.
  
  ---------------------------------------------------------------------------*/

int DisableEndInterrupts(trident_t *trident )
{
    unsigned int GlobalControl;

    GlobalControl = inb(TRID_REG(trident, T4D_LFO_GC_CIR+1));
    GlobalControl &= ~0x10;
    outb(GlobalControl, TRID_REG(trident, T4D_LFO_GC_CIR+1));

    return ( TRUE );
}

/*---------------------------------------------------------------------------
   void EnableInterruptChannel( unsigned int HwChannel )
  
    Description: Enable an interrupt channel, any channel 0 thru n.
                 This routine automatically handles the fact that there are
                 more than 32 channels available.
  
    Parameters : HwChannel - Channel number 0 thru n.
                 trident - pointer to target device class for 4DWave.
  
    Return Value: None.
  
  ---------------------------------------------------------------------------*/
void EnableInterruptChannel( trident_t *trident, unsigned int HwChannel )
{
    unsigned int x = HwChannel >> 5;
    unsigned int Data = (0x80000000 >> ((((x + 1) << 5) - 1) - HwChannel));
    unsigned int ChanDwordCount = trident->ChanDwordCount;
 
    IReadAinten( trident->ChRegs );
    trident->ChRegs->lpChAinten[x] |= Data;
    IWriteAinten( trident->ChRegs );
}

/*---------------------------------------------------------------------------
   void DisableInterruptChannel( unsigned int HwChannel )
  
    Description: Disable an interrupt channel, any channel 0 thru n.
                 This routine automatically handles the fact that there are
                 more than 32 channels available.
  
    Parameters : HwChannel - Channel number 0 thru n.
                 trident - pointer to target device class for 4DWave.
  
    Return Value: None.
  
  ---------------------------------------------------------------------------*/
void DisableInterruptChannel( trident_t *trident, unsigned int HwChannel )
{
    unsigned int x = HwChannel >> 5;
    unsigned int Data = (0x80000000 >> ((((x + 1) << 5) - 1) - HwChannel));
    unsigned int ChanDwordCount = trident->ChanDwordCount;
 
    IReadAinten( trident->ChRegs );
    trident->ChRegs->lpChAinten[x] &= ~Data;
    IWriteAinten( trident->ChRegs );
}

/*---------------------------------------------------------------------------
   void StartChannelHw( ULONG HwChannel )
  
    Description: Start a channel, any channel 0 thru n.
                 This routine automatically handles the fact that there are
                 more than 32 channels available.
  
    Parameters : HwChannel - Channel number 0 thru n.
                 trident - pointer to target device class for 4DWave.
  
    Return Value: None.
  
  ---------------------------------------------------------------------------*/
void StartChannelHw( trident_t *trident, unsigned int HwChannel )
{
    unsigned int x = HwChannel >> 5;
    unsigned int Data = (0x80000000 >> ((((x + 1) << 5) - 1) - HwChannel));

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

/*---------------------------------------------------------------------------
   void StopChannelHw( ULONG HwChannel )
  
    Description: Stop a channel, any channel 0 thru n.
                 This routine automatically handles the fact that there are
                 more than 32 channels available.
  
    Parameters : HwChannel - Channel number 0 thru n.
                 trident - pointer to target device class for 4DWave.
  
    Return Value: None.
  
  ---------------------------------------------------------------------------*/
void StopChannelHw( trident_t *trident, unsigned int HwChannel )
{
    unsigned int x = HwChannel >> 5;
    unsigned int Data = (0x80000000 >> ((((x + 1) << 5) - 1) - HwChannel));
 
    outl(Data, TRID_REG(trident, T4D_STOP_B));
    //snd_printk("trid: stopping channel %d\n", (int) HwChannel);
}

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

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

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

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

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

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

    return ( FALSE );
}

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

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

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

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

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

    return ( FALSE );
}

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

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

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


/*---------------------------------------------------------------------------
   int LoadDeltaHw( unsigned int HwChannel, unsigned int Delta )
  
   Description: This routine writes Delta to the hardware.
  
   Parameters:  Delta - data to write (2 Bytes only)
                HwChannel - Hardware channel to write to.
   		trident - pointer to target device class for 4DWave.
  
   Returns:     TRUE if all goes well, else FALSE. 
  
  ---------------------------------------------------------------------------*/
int LoadDeltaHw( trident_t *trident, unsigned int HwChannel, unsigned int Delta )
{

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

        if (!trident->isNX)
        {
            outw((unsigned short)Delta, TRID_REG(trident, CH_DX_ESO_DELTA));
        }
        else   // ID_4DWAVE_NX
        {
	    outb((unsigned char) Delta, TRID_REG(trident, CH_NX_DELTA_CSO+3));
	    outb((unsigned char) (Delta >> 8), TRID_REG(trident, CH_NX_DELTA_ESO+3));
        }

    return TRUE;
}

/*---------------------------------------------------------------------------
   int LoadVirtualChannel( ULONG *Data, ULONG HwChannel)
  
   Description: This routine writes all required channel registers to hardware.
  
   Parameters:  *Data - a pointer to the data to write (5 ULONGS always).
                HwChannel - Hardware channel to write to.
   		trident - pointer to target device class for 4DWave.
  
   Returns:     TRUE if all goes well, else FALSE. 
  
  ---------------------------------------------------------------------------*/
int LoadVirtualChannel( trident_t *trident, unsigned int *Data, unsigned int HwChannel)
{
    unsigned int ChanData[CHANNEL_REGS];
    unsigned int ULONGSToDo = CHANNEL_REGS;
    unsigned int i;
    unsigned int Address = CHANNEL_START;

    /* Copy the data first... Hack... Before mucking with Volume! */
    memcpy((unsigned char *)ChanData, (unsigned char *)Data, ULONGSToDo*4);

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

    for(i = 0; i < ULONGSToDo; i++, Address += 4)
      outl(ChanData[i], TRID_REG(trident, Address));

    return TRUE;
}

/*---------------------------------------------------------------------------
   WriteCompleteChannel
  
   Description: This routine will write the 5 hardware channel registers
                to hardware.
  
   Paramters:   trident - pointer to target device class for 4DWave.
                Channel - Real or Virtual channel number.
                Each register field.
  
   Returns:     TRUE if all goes well, else FALSE. 
  
  ---------------------------------------------------------------------------*/
int WriteCompleteChannel( trident_t *trident,
                          unsigned int Channel,
                          unsigned int LBA,
                          unsigned int CSO,
                          unsigned int ESO,
                          unsigned int DELTA,
                          unsigned int ALPHA_FMS,
                          unsigned int FMC_RVOL_CVOL,
                          unsigned int GVSEL,
                          unsigned int PAN,
                          unsigned int VOL,
                          unsigned int CTRL,
                          unsigned int EC
                          )
{
    unsigned int  ChanData[CHANNEL_REGS+1], FmcRvolCvol;

    ChanData[1] = LBA;
    ChanData[4] = (GVSEL << 31) | 
                  ((PAN & 0x0000007f) << 24) |
                  ((VOL & 0x000000ff) << 16) |
                  ((CTRL & 0x0000000f) << 12) |
                  (EC & 0x00000fff);

    FmcRvolCvol = FMC_RVOL_CVOL & 0x0000ffff;

    if (!trident->isNX)
    {
        ChanData[0] = (CSO << 16) | (ALPHA_FMS & 0x0000ffff);
        ChanData[2] = (ESO << 16) | (DELTA & 0x0ffff);
        ChanData[3] = FmcRvolCvol;
    }
    else // ID_4DWAVE_NX
    {
        ChanData[0] = (DELTA << 24) | (CSO & 0x00ffffff);
        ChanData[2] = ((DELTA << 16) & 0xff000000) | (ESO & 0x00ffffff);
        ChanData[3] = (ALPHA_FMS << 16) | FmcRvolCvol;
    }

    LoadVirtualChannel( trident, ChanData, Channel );

    return TRUE;
}

/*---------------------------------------------------------------------------
   snd_trident_set_dac_rate
  
   Description: This routine will set the sample rate for playback.
  
   Paramters:   trident - pointer to target device class for 4DWave.
                rate    - desired sample rate
                set     - actually write hardware if set is true.
  
   Returns:     The rate allowed by device.
  
  ---------------------------------------------------------------------------*/

static unsigned int snd_trident_set_dac_rate(trident_t *trident,
						unsigned int rate, int set)
{
	unsigned long flags;

        //snd_printk("trid: called snd_trident_set_dac_rate : rate = %d, set = %d\n", rate, set);
	if (rate > 48000)
		rate = 48000;
	if (rate < 4000)
		rate = 4000;
	if (set) {
          // We special case 44100 and 8000 since rounding with the equation
          // does not give us an accurate enough value. For 11025 and 22050
          // the equation gives us the best answer. All other frequencies will
          // also use the equation. 
          unsigned int Delta;
          if(rate == 44100)
            Delta = 0xeb3;
          else if(rate == 8000)
            Delta = 0x2ab;
          else if(rate == 48000)
            Delta = 0x1000;
          else
            Delta = (((rate << 12) + rate )/48000 ) & 0x0000ffff ;

          snd_spin_lock(trident, reg, &flags);
          LoadDeltaHw(trident, HW_PLAYBACK_CHANNEL, Delta);
          snd_spin_unlock(trident, reg, &flags);
	}
        return rate;
}

/*---------------------------------------------------------------------------
   snd_trident_set_adc_rate
  
   Description: This routine will set the sample rate for record.
  
   Paramters:   trident - pointer to target device class for 4DWave.
                rate    - desired sample rate
                set     - actually write hardware if set is true.
  
   Returns:     The rate allowed by device.
  
  ---------------------------------------------------------------------------*/

static unsigned int snd_trident_set_adc_rate(trident_t * trident,
						unsigned int rate, int set)
{
        //snd_printk("trid: called snd_trident_set_adc_rate\n");
	if (rate > 48000)
		rate = 48000;
	if (rate < 4000)
		rate = 4000;
	return rate;
}

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

static void snd_trident_trigger(trident_t * trident, int what, int up)
{
	unsigned long flags;

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

/*
 *  PCM part
 */

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	snd_spin_lock(trident, reg, &flags);

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

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

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

        /* set ESO */
        ESO = size;
	if (pcm1->playback.mode & SND_PCM1_MODE_16)
          ESO /= 2;
	if (pcm1->playback.voices > 1)
          ESO /= 2;
        
        ESO = ESO - 1;
        //snd_printk("trid: ESO = %d\n", ESO);

        /* set ctrl mode
           CTRL default: 8-bit (unsigned) mono, loop mode enabled
         */
        CTRL = 0x00000001;
	if (pcm1->playback.mode & SND_PCM1_MODE_16)
          CTRL |= 0x0000000a; // signed data
	if (pcm1->playback.voices > 1)
          CTRL |= 0x00000004;

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

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

        /* set ESO */
        ESO = count;

	if (pcm1->playback.mode & SND_PCM1_MODE_16)
          ESO /= 2;
	if (pcm1->playback.voices > 1)
          ESO /= 2;
        
        ESO = ESO - 1;
        //snd_printk("trid: intr ESO = %d\n", ESO);

        /* set ctrl mode
           CTRL default: 8-bit (unsigned) mono, loop mode enabled
         */
        CTRL = 0x00000001;
	if (pcm1->playback.mode & SND_PCM1_MODE_16)
          CTRL |= 0x0000000a; // signed data
	if (pcm1->playback.voices > 1)
          CTRL |= 0x00000004;
        
        VOL = 0xff;

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

      EnableInterruptChannel(trident, HW_PLAYBACK_CHANNEL_INTR);

      EnableEndInterrupts(trident);
      snd_spin_unlock(trident, reg, &flags);
}

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

static void snd_trident_record_prepare(snd_pcm1_t * pcm1,
					  unsigned char *buffer,
					  unsigned int size,
					  unsigned int offset,
					  unsigned int count)
{

	trident_t *trident= (trident_t *) pcm1->private_data;
        unsigned int LBA;
        unsigned int Delta;
        unsigned int rate;
        unsigned int ESO;
        unsigned int CTRL;
        unsigned int FMC_RVOL_CVOL;
        unsigned int GVSEL;
        unsigned int PAN;
        unsigned int VOL;
        unsigned int EC;
        unsigned char bValue;
        unsigned short wValue;
        unsigned int dwValue;
        unsigned short wRecCODECSamples;
        unsigned int dwChanFlags;
        unsigned long flags;

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

	snd_spin_lock(trident, reg, &flags);

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

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

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

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

        /* set ESO */
        ESO = size;

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

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

	// Set channel interrupt blk length
        if (pcm1->record.mode & SND_PCM1_MODE_16) {
          wRecCODECSamples = (unsigned short ) ((ESO >> 1) - 1);
          dwChanFlags = 0xffffb000;
        } else {
          wRecCODECSamples = (unsigned short ) (ESO - 1);
          dwChanFlags = 0xffff1000;
        }

	dwValue = ((unsigned int) wRecCODECSamples ) << 16 ;
	dwValue |= (unsigned int)( wRecCODECSamples ) & 0x0000ffff ;
	outl(dwValue, TRID_REG(trident, T4D_SBBL_SBCL));

	// Right now, set format and start to run recording, 
	// continuous run loop enable.
	trident->bDMAStart = 0x19 ; // 0001 1001b
        if (pcm1->record.mode & SND_PCM1_MODE_16) 
	  trident->bDMAStart |= 0xa0 ;
	if (pcm1->record.voices > 1)
	  trident->bDMAStart |= 0x40 ;
 
        // Prepare record intr channel

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

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

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

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

        /* set ESO */
        ESO = count;
        if (pcm1->record.mode & SND_PCM1_MODE_16)
          ESO /= 2;
        if (pcm1->record.voices > 1)
          ESO /= 2; 
        
        ESO = ESO - 1;
        //snd_printk("trid: ESO = %d\n", ESO);

        /* set ctrl mode
          CTRL default: 8-bit (unsigned) mono, loop mode enabled
         */
        CTRL = 0x00000001;
        if (pcm1->record.mode & SND_PCM1_MODE_16)
          CTRL |= 0x0000000a; // signed data
        if (pcm1->record.voices > 1)
          CTRL |= 0x00000004;

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

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

      EnableInterruptChannel(trident, HW_RECORD_CHANNEL_INTR);

      EnableEndInterrupts(trident);
      snd_spin_unlock(trident, reg, &flags);
}

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

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

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

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

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

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

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

	if ((err = snd_pcm1_dma_alloc(pcm1, SND_PCM1_RECORD,
				      trident->dma2ptr,
				      "Trident 4DWave(record)")) < 0)
		return err;
	return 0;
}

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

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

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

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

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

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

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

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

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


	if (!trident->enable_playback)
		return 0;
        
	snd_spin_lock(trident, reg, &flags);

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

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

        cso++;

        if (cso > eso)
          cso = eso;

	if (pcm1->playback.mode & SND_PCM1_MODE_16)
          cso *= 2;
	if (pcm1->playback.voices > 1)
          cso *= 2;

	snd_spin_unlock(trident, reg, &flags);

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

	return cso;
}

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

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

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


	if (!trident->enable_record)
		return 0;
        
	snd_spin_lock(trident, reg, &flags);

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

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

        cso++;

	if (pcm1->record.mode & SND_PCM1_MODE_16)
          cso *= 2;
	if (pcm1->record.voices > 1)
          cso *= 2;

	snd_spin_unlock(trident, reg, &flags);

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

	return cso;
}

/*
  Playback support device description
 */

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

/*
  Record support device description
 */

static struct snd_stru_pcm1_hardware snd_trident_record =
{
	NULL,			/* private data */
	NULL,			/* private_free */
	SND_PCM1_HW_AUTODMA | SND_PCM1_HW_BLOCKPTR,	/* flags */
	SND_PCM_FMT_MU_LAW | SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE, /* formats */
	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,	/* hardware formats */
	0,			/* align value */
	6,			/* minimal fragment */
	4000,			/* min. rate */
	48000,			/* max. rate */
	2,			/* max. voices */
	snd_trident_record_open,
	snd_trident_record_close,
	snd_trident_record_ioctl,
	snd_trident_record_prepare,
	snd_trident_record_trigger,
	snd_trident_record_pointer,
	snd_pcm1_record_dma_ulaw_loud,
	snd_pcm1_dma_move,
	NULL
};

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

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

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

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

snd_pcm_t *snd_trident_pcm(trident_t * trident)
{
	snd_pcm_t *pcm;
	snd_pcm1_t *pcm1;

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

/*
 *  Mixer part
 */

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

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

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

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

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

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


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

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

/*  
   General boilerplate code required for driver.
*/

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

        //snd_printk("trid: called snd_trident_proc_read\n");
	snd_iprintf(buffer, "Trident 4DWave PCI %s\n\n", trident->isNX ? "NX" : "DX");
}

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

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

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

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

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

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

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

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

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

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

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


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

	trident->card = card;
        if (card->type == SND_CARD_TYPE_TRID4DWAVENX) {
          trident->isNX = TRUE;
#if 0
	  snd_printk("trid: detected NX\n");
#endif
        } else {
          trident->isNX = FALSE;
#if 0
	  snd_printk("trid: detected DX\n");
#endif
        }
	trident->pci = pci;
	trident->dma1ptr = dma1ptr;
	trident->dma2ptr = dma2ptr;
	trident->irqptr = irqptr;
	trident->port = pci->base_address[0] & ~PCI_BASE_ADDRESS_SPACE;
	trident->midi_port = TRID_REG(trident, T4D_MPU401_BASE);
	snd_pci_read_config_word(pci, PCI_COMMAND, &cmdw);
	if ((cmdw & (PCI_COMMAND_IO|PCI_COMMAND_MASTER)) != (PCI_COMMAND_IO|PCI_COMMAND_MASTER)) {
		cmdw |= PCI_COMMAND_IO | PCI_COMMAND_MASTER;
		snd_pci_write_config_word(pci, PCI_COMMAND, cmdw);
	}
	snd_pci_read_config_dword(pci, 0x10, &trident->port);
	trident->port &= ~0x0f;
        
        trident->enable_playback = FALSE;
        trident->enable_record = FALSE;
       
	/*  initialize chip */

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

        if(trident->isNX)
        {   unsigned short VendorID1, VendorID2;

            outl(0x02, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
 
            // 4 Speaker Codec initialization
            VendorID1 = ReadAC97(trident, AC97_VENDOR_ID1);
            VendorID2 = ReadAC97(trident, AC97_VENDOR_ID2);

            if( (VendorID1 == 0x8384) && (VendorID2 == 0x7608) )
            {   // Sigmatel 9708.
            
                unsigned short TestReg;
                unsigned int DTemp;

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

                TestReg = ReadAC97( trident, AC97_SIGMATEL_BIAS2 );

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

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

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

                DTemp = (unsigned int)ReadAC97( trident, AC97_MIC );
                WriteAC97( trident, AC97_MIC, DTemp | 0x00008000L );  // bit15 = 1.

                DTemp = inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
                outl(DTemp | 0x0010, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));

            }
            else if((VendorID1 == 0x5452) && (VendorID2 == 0x4108) )
            {   // TriTech TR28028

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

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

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


	snd_trident_proc_init(trident);
	return trident;
}

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

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

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

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

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

void snd_trident_interrupt(trident_t * trident)
{
	snd_pcm1_t *pcm1;

        //snd_printk("trid: called snd_trident_interrupt\n");
        if (!DidAWaveInterruptOccur(trident))
        {
        	if( trident->rmidi )
        	{
        		unsigned int val;
        		val = inb(TRID_REG(trident,T4D_MPUR1));
        		if( !(val&0x80) )
				snd_mpu401_uart_interrupt(trident->rmidi);
        	}
          	return;
	}

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

/*
 *  INIT part
 */

#ifndef LINUX_2_1
extern struct symbol_table snd_symbol_table_trident_dx_nx_export;
#endif

int init_module(void)
{
#ifndef LINUX_2_1
	if (register_symtab(&snd_symbol_table_trident_dx_nx_export) < 0)
		return -ENOMEM;
#endif
	return 0;
}

void cleanup_module(void)
{
}
