/*
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *  Routines for control of Cirrus Logic CS461x chips
 *
 *  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_MAIN_OBJECT_FILE
#include "driver.h"
#include "control.h"
#include "info.h"
#include "cs461x.h"

/*
 *  constants
 */

#define CS461X_BA0_SIZE		0x2000
#define CS461X_BA1_DATA0_SIZE	0x3000
#define CS461X_BA1_DATA1_SIZE	0x3800
#define CS461X_BA1_PRG_SIZE	0x7000
#define CS461X_BA1_REG_SIZE	0x0100

/*
 *  common I/O routines
 */

static void snd_cs461x_poke(cs461x_t *codec, unsigned long reg, unsigned int val)
{
	codec->ba1.idx[(reg >> 16) & 3][(reg >> 2) & 0x3fff] = val;
}

static unsigned int snd_cs461x_peek(cs461x_t *codec, unsigned long reg)
{
	return codec->ba1.idx[(reg >> 16) & 3][(reg >> 2) & 0x3fff];
}

static void snd_cs461x_pokeBA0(cs461x_t *codec, unsigned long reg, unsigned int val)
{
	codec->ba0[reg >> 2] = val;
}

static unsigned int snd_cs461x_peekBA0(cs461x_t *codec, unsigned long reg)
{
	return codec->ba0[reg >> 2];
}

static void snd_cs461x_codec_write(void *private_data,
				   unsigned short reg,
				   unsigned short val)
{
	/*
	 *  1. Write ACCAD = Command Address Register = 46Ch for AC97 register address
	 *  2. Write ACCDA = Command Data Register = 470h    for data to write to AC97
	 *  3. Write ACCTL = Control Register = 460h for initiating the write
	 *  4. Read ACCTL = 460h, DCV should be reset by now and 460h = 07h
	 *  5. if DCV not cleared, break and return error
	 */
	cs461x_t *codec = (cs461x_t *)private_data;
	int count;

	/*
	 *  Setup the AC97 control registers on the CS461x to send the
	 *  appropriate command to the AC97 to perform the read.
	 *  ACCAD = Command Address Register = 46Ch
	 *  ACCDA = Command Data Register = 470h
	 *  ACCTL = Control Register = 460h
	 *  set DCV - will clear when process completed
	 *  reset CRW - Write command
	 *  set VFRM - valid frame enabled
	 *  set ESYN - ASYNC generation enabled
	 *  set RSTN - ARST# inactive, AC97 codec not reset
         */
	snd_cs461x_pokeBA0(codec, BA0_ACCAD, reg);
	snd_cs461x_pokeBA0(codec, BA0_ACCDA, val);
	snd_cs461x_pokeBA0(codec, BA0_ACCTL, ACCTL_DCV | ACCTL_VFRM |
				             ACCTL_ESYN | ACCTL_RSTN);
	for (count = 0; count < 100; count++) {
		/*
		 *  First, we want to wait for a short time.
		 */
		snd_delay(1);
		/*
		 *  Now, check to see if the write has completed.
		 *  ACCTL = 460h, DCV should be reset by now and 460h = 07h
		 */
		if (!(snd_cs461x_peekBA0(codec, BA0_ACCTL) & ACCTL_DCV))
			break;
	}
	/*
	 *  Make sure the write completed.
	 */
	if (snd_cs461x_peekBA0(codec, BA0_ACCTL) & ACCTL_DCV)
		snd_printk("cs461x: AC'97 write problem, reg = 0x%x, val = 0x%x\n", reg, val);
}

static unsigned short snd_cs461x_codec_read(void *private_data,
					    unsigned short reg)
{
	/*
	 *  1. Write ACCAD = Command Address Register = 46Ch for AC97 register address
	 *  2. Write ACCDA = Command Data Register = 470h    for data to write to AC97 
	 *  3. Write ACCTL = Control Register = 460h for initiating the write
	 *  4. Read ACCTL = 460h, DCV should be reset by now and 460h = 17h
	 *  5. if DCV not cleared, break and return error
	 *  6. Read ACSTS = Status Register = 464h, check VSTS bit
	 */

	int count;
	cs461x_t *codec = (cs461x_t *)private_data;

	snd_cs461x_peekBA0(codec, BA0_ACSDA);

	/*
	 *  Setup the AC97 control registers on the CS461x to send the
	 *  appropriate command to the AC97 to perform the read.
	 *  ACCAD = Command Address Register = 46Ch
	 *  ACCDA = Command Data Register = 470h
	 *  ACCTL = Control Register = 460h
	 *  set DCV - will clear when process completed
	 *  set CRW - Read command
	 *  set VFRM - valid frame enabled
	 *  set ESYN - ASYNC generation enabled
	 *  set RSTN - ARST# inactive, AC97 codec not reset
	 */

	snd_cs461x_pokeBA0(codec, BA0_ACCAD, reg);
	snd_cs461x_pokeBA0(codec, BA0_ACCDA, 0);
	snd_cs461x_pokeBA0(codec, BA0_ACCTL, ACCTL_DCV | ACCTL_CRW |
					     ACCTL_VFRM | ACCTL_ESYN |
					     ACCTL_RSTN);


	/*
	 *  Wait for the read to occur.
	 */
	for (count = 0; count < 500; count++) {
		/*
		 *  First, we want to wait for a short time.
	 	 */
		snd_delay(1);
		/*
		 *  Now, check to see if the read has completed.
		 *  ACCTL = 460h, DCV should be reset by now and 460h = 17h
		 */
		if (!(snd_cs461x_peekBA0(codec, BA0_ACCTL) & ACCTL_DCV))
			break;
	}

	/*
	 *  Make sure the read completed.
	 */
	if (snd_cs461x_peekBA0(codec, BA0_ACCTL) & ACCTL_DCV) {
		snd_printk("cs461x: AC'97 read problem (ACCTL_DCV), reg = 0x%x\n", reg);
		return 0xffff;
	}

	/*
	 *  Wait for the valid status bit to go active.
	 */
	for (count = 0; count < 100; count++) {
		/*
		 *  Read the AC97 status register.
		 *  ACSTS = Status Register = 464h
		 *  VSTS - Valid Status
		 */
		if (snd_cs461x_peekBA0(codec, BA0_ACSTS) & ACSTS_VSTS)
			break;
		snd_delay(1);
	}
	
	/*
	 *  Make sure we got valid status.
	 */
	if (!(snd_cs461x_peekBA0(codec, BA0_ACSTS) & ACSTS_VSTS)) {
		snd_printk("cs461x: AC'97 read problem (ACSTS_VSTS), reg = 0x%x\n", reg);
		return 0xffff;
	}

	/*
	 *  Read the data returned from the AC97 register.
	 *  ACSDA = Status Data Register = 474h
	 */
#if 0
	printk("e) reg = 0x%x, val = 0x%x, BA0_ACCAD = 0x%x\n", reg,
			snd_cs461x_peekBA0(codec, BA0_ACSDA),
			snd_cs461x_peekBA0(codec, BA0_ACCAD));
#endif
#if 0
	snd_delay(400);
#endif
	return snd_cs461x_peekBA0(codec, BA0_ACSDA);
}

/*
 *  Chip initialization
 */

int snd_cs461x_download(cs461x_t *codec,
			unsigned int *src,
                        unsigned long offset,
                        unsigned long len)
{
	unsigned long counter;
	unsigned int *dst;

	if ((offset & 3) || (len & 3))
		return -EINVAL;
	dst = codec->ba1.idx[(offset >> 16) & 3];
	offset &= 0xffff;
	len >>= 2;
	/* I'm not sure, if the big endian architecture needs the byteswap here */
	for (counter = 0; counter < len; counter++)
		dst[offset + counter] = src[counter];
	return 0;
}

/* 3*1024 parameter, 3.5*1024 sample, 2*3.5*1024 code */
#define BA1_DWORD_SIZE		(13 * 1024 + 512)
#define BA1_MEMORY_COUNT	3

struct BA1struct {
	struct {
		unsigned long offset;
		unsigned long size;
	} memory[BA1_MEMORY_COUNT];
	unsigned int map[BA1_DWORD_SIZE];
};

static
#include "cs461x_image.h"

int snd_cs461x_download_image(cs461x_t *codec)
{
	int idx, err;
	unsigned long offset = 0;

	for (idx = 0; idx < BA1_MEMORY_COUNT; idx++) {
		if ((err = snd_cs461x_download(codec,
					       &BA1Struct.map[offset],
					       BA1Struct.memory[idx].offset,
					       BA1Struct.memory[idx].size)) < 0)
			return err;
		offset += BA1Struct.memory[idx].size >> 2;
	}	
	return 0;
}

/*
 *  Chip reset
 */

static void snd_cs461x_reset(cs461x_t *codec)
{
	int idx;

	/*
	 *  Write the reset bit of the SP control register.
	 */
	snd_cs461x_poke(codec, BA1_SPCR, SPCR_RSTSP);

	/*
	 *  Write the control register.
	 */
	snd_cs461x_poke(codec, BA1_SPCR, SPCR_DRQEN);

	/*
	 *  Clear the trap registers.
	 */
	for (idx = 0; idx < 8; idx++) {
		snd_cs461x_poke(codec, BA1_DREG, DREG_REGID_TRAP_SELECT + idx);
		snd_cs461x_poke(codec, BA1_TWPR, 0xFFFF);
	}
	snd_cs461x_poke(codec, BA1_DREG, 0);

	/*
	 *  Set the frame timer to reflect the number of cycles per frame.
	 */
	snd_cs461x_poke(codec, BA1_FRMT, 0xadf);
}

static void snd_cs461x_clear_serial_FIFOs(cs461x_t *codec)
{
	int idx, loop, powerdown = 0;
	unsigned int tmp;

	/*
	 *  See if the devices are powered down.  If so, we must power them up first
	 *  or they will not respond.
	 */
	if (!((tmp = snd_cs461x_peekBA0(codec, BA0_CLKCR1)) & CLKCR1_SWCE)) {
		snd_cs461x_pokeBA0(codec, BA0_CLKCR1, tmp | CLKCR1_SWCE);
		powerdown = 1;
	}

	/*
	 *  We want to clear out the serial port FIFOs so we don't end up playing
	 *  whatever random garbage happens to be in them.  We fill the sample FIFOS
	 *  with zero (silence).
         */
	snd_cs461x_pokeBA0(codec, BA0_SERBWP, 0);

	/*
	 *  Fill all 256 sample FIFO locations.
	 */
	for (idx = 0; idx < 256; idx++) {
		/*
		 *  Make sure the previous FIFO write operation has completed.
		 */
		for (loop = 0; loop < 5; loop++) {
			snd_delay(5);
			if (!(snd_cs461x_peekBA0(codec, BA0_SERBST) & SERBST_WBSY))
				break;
		}
		if (snd_cs461x_peekBA0(codec, BA0_SERBST) & SERBST_WBSY) {
			if (powerdown)
				snd_cs461x_pokeBA0(codec, BA0_CLKCR1, tmp);
		}
		/*
		 *  Write the serial port FIFO index.
		 */
		snd_cs461x_pokeBA0(codec, BA0_SERBAD, idx);
		/*
		 *  Tell the serial port to load the new value into the FIFO location.
		 */
		snd_cs461x_pokeBA0(codec, BA0_SERBCM, SERBCM_WRC);
	}
	/*
	 *  Now, if we powered up the devices, then power them back down again.
	 *  This is kinda ugly, but should never happen.
	 */
	if (powerdown)
		snd_cs461x_pokeBA0(codec, BA0_CLKCR1, tmp);
}

static void snd_cs461x_powerup_dac(cs461x_t *codec)
{
	int count;
	unsigned int tmp;

	/*
	 *  Power on the DACs on the AC97 codec.  We turn off the DAC
	 *  powerdown bit and write the new value of the power control
	 *  register.
	 */
	tmp = snd_cs461x_codec_read(codec, AC97_POWERDOWN);
	snd_cs461x_codec_write(codec, AC97_POWERDOWN, tmp & 0xfdff);

	/*
	 *  Now, we wait until we sample a DAC ready state.
	 */
	for (count = 0; count < 32; count++) {
		/*
		 *  First, lets wait a short while to let things settle out a
		 *  bit, and to prevent retrying the read too quickly.
		 */
		snd_delay(5);

		/*
		 *  Read the current state of the power control register.
		 */
		if (snd_cs461x_codec_read(codec, AC97_POWERDOWN) & 2)
			break;
	}
	
	/*
	 *  Check the status..
	 */
	if (!(snd_cs461x_codec_read(codec, AC97_POWERDOWN) & 2))
		snd_printk("cs461x: powerup DAC failed\n");
}

static void snd_cs461x_powerup_adc(cs461x_t *codec)
{
	int count;
	unsigned int tmp;

	/*
	 *  Power on the ADCs on the AC97 codec.  We turn off the DAC
	 *  powerdown bit and write the new value of the power control
	 *  register.
	 */
	tmp = snd_cs461x_codec_read(codec, AC97_POWERDOWN);
	snd_cs461x_codec_write(codec, AC97_POWERDOWN, tmp & 0xfeff);

	/*
	 *  Now, we wait until we sample a ADC ready state.
	 */
	for (count = 0; count < 32; count++) {
		/*
		 *  First, lets wait a short while to let things settle out a
		 *  bit, and to prevent retrying the read too quickly.
		 */
		snd_delay(5);

		/*
		 *  Read the current state of the power control register.
		 */
		if (snd_cs461x_codec_read(codec, AC97_POWERDOWN) & 1)
			break;
	}

	/*
	 *  Check the status..
	 */
	if (!(snd_cs461x_codec_read(codec, AC97_POWERDOWN) & 1))
		snd_printk("cs461x: powerup ADC failed\n");
}

static void snd_cs461x_proc_start(cs461x_t *codec)
{
	int cnt;

	/*
	 *  Set the frame timer to reflect the number of cycles per frame.
	 */
	snd_cs461x_poke(codec, BA1_FRMT, 0xadf);
	/*
	 *  Turn on the run, run at frame, and DMA enable bits in the local copy of
	 *  the SP control register.
	 */
	snd_cs461x_poke(codec, BA1_SPCR, SPCR_RUN | SPCR_RUNFR | SPCR_DRQEN);
	/*
	 *  Wait until the run at frame bit resets itself in the SP control
	 *  register.
	 */
	for (cnt = 0; cnt < 25; cnt++) {
		snd_delay(5);
		if (!(snd_cs461x_peek(codec, BA1_SPCR) & SPCR_RUNFR))
			break;
	}

	if (snd_cs461x_peek(codec, BA1_SPCR) & SPCR_RUNFR)
		snd_printk("cs461x: SPCR_RUNFR never reset\n");
}

static void snd_cs461x_proc_stop(cs461x_t *codec)
{
	/*
	 *  Turn off the run, run at frame, and DMA enable bits in the local copy of
	 *  the SP control register.
	 */
	snd_cs461x_poke(codec, BA1_SPCR, 0);
}

/*
 *  Sample rate routines
 */

#define GOF_PER_SEC 200

static void snd_cs461x_set_play_sample_rate(cs461x_t *codec, unsigned int rate)
{
	unsigned long flags;
	unsigned int tmp1, tmp2;
	unsigned int phiIncr;
	unsigned int correctionPerGOF, correctionPerSec;

	/*
	 *  Compute the values used to drive the actual sample rate conversion.
	 *  The following formulas are being computed, using inline assembly
	 *  since we need to use 64 bit arithmetic to compute the values:
	 *
	 *  phiIncr = floor((Fs,in * 2^26) / Fs,out)
	 *  correctionPerGOF = floor((Fs,in * 2^26 - Fs,out * phiIncr) /
         *                                   GOF_PER_SEC)
         *  ulCorrectionPerSec = Fs,in * 2^26 - Fs,out * phiIncr -M
         *                       GOF_PER_SEC * correctionPerGOF
	 *
	 *  i.e.
	 *
	 *  phiIncr:other = dividend:remainder((Fs,in * 2^26) / Fs,out)
	 *  correctionPerGOF:correctionPerSec =
	 *      dividend:remainder(ulOther / GOF_PER_SEC)
	 */
	tmp1 = rate << 16;
	phiIncr = tmp1 / 48000;
	tmp1 -= phiIncr * 48000;
	tmp1 <<= 10;
	phiIncr <<= 10;
	tmp2 = tmp1 / 48000;
	phiIncr += tmp2;
	tmp1 -= tmp2 * 48000;
	correctionPerGOF = tmp1 / GOF_PER_SEC;
	tmp1 -= correctionPerGOF * GOF_PER_SEC;
	correctionPerSec = tmp1;

	/*
	 *  Fill in the SampleRateConverter control block.
	 */
	snd_spin_lock(codec, reg, &flags);
	snd_cs461x_poke(codec, BA1_PSRC,
	  ((correctionPerSec << 16) & 0xFFFF0000) | (correctionPerGOF & 0xFFFF));
	snd_cs461x_poke(codec, BA1_PPI, phiIncr);
	snd_spin_unlock(codec, reg, &flags);
}

static void snd_cs461x_set_capture_sample_rate(cs461x_t *codec, unsigned int rate)
{
	unsigned long flags;
	unsigned int phiIncr, coeffIncr, tmp1, tmp2;
	unsigned int correctionPerGOF, correctionPerSec, initialDelay;
	unsigned int frameGroupLength, cnt;

	/*
	 *  We can only decimate by up to a factor of 1/9th the hardware rate.
	 *  Correct the value if an attempt is made to stray outside that limit.
	 */
	if ((rate * 9) < 48000)
		rate = 48000 / 9;

	/*
	 *  We can not capture at at rate greater than the Input Rate (48000).
	 *  Return an error if an attempt is made to stray outside that limit.
	 */
	if (rate > 48000)
		rate = 48000;

	/*
	 *  Compute the values used to drive the actual sample rate conversion.
	 *  The following formulas are being computed, using inline assembly
	 *  since we need to use 64 bit arithmetic to compute the values:
	 *
	 *     coeffIncr = -floor((Fs,out * 2^23) / Fs,in)
	 *     phiIncr = floor((Fs,in * 2^26) / Fs,out)
	 *     correctionPerGOF = floor((Fs,in * 2^26 - Fs,out * phiIncr) /
	 *                                GOF_PER_SEC)
	 *     correctionPerSec = Fs,in * 2^26 - Fs,out * phiIncr -
	 *                          GOF_PER_SEC * correctionPerGOF
	 *     initialDelay = ceil((24 * Fs,in) / Fs,out)
	 *
	 * i.e.
	 *
	 *     coeffIncr = neg(dividend((Fs,out * 2^23) / Fs,in))
	 *     phiIncr:ulOther = dividend:remainder((Fs,in * 2^26) / Fs,out)
	 *     correctionPerGOF:correctionPerSec =
	 * 	    dividend:remainder(ulOther / GOF_PER_SEC)
	 *     initialDelay = dividend(((24 * Fs,in) + Fs,out - 1) / Fs,out)
	 */

	tmp1 = rate << 16;
	coeffIncr = tmp1 / 48000;
	tmp1 -= coeffIncr * 48000;
	tmp1 <<= 7;
	coeffIncr <<= 7;
	coeffIncr += tmp1 / 48000;
	coeffIncr ^= 0xFFFFFFFF;
	coeffIncr++;
	tmp1 = 48000 << 16;
	phiIncr = tmp1 / rate;
	tmp1 -= phiIncr * rate;
	tmp1 <<= 10;
	phiIncr <<= 10;
	tmp2 = tmp1 / rate;
	phiIncr += tmp2;
	tmp1 -= tmp2 * rate;
	correctionPerGOF = tmp1 / GOF_PER_SEC;
	tmp1 -= correctionPerGOF * GOF_PER_SEC;
	correctionPerSec = tmp1;
	initialDelay = ((48000 * 24) + rate - 1) / rate;

	/*
	 *  Fill in the VariDecimate control block.
	 */
	snd_spin_lock(codec, reg, &flags);
	snd_cs461x_poke(codec, BA1_CSRC,
		((correctionPerSec << 16) & 0xFFFF0000) | (correctionPerGOF & 0xFFFF));
	snd_cs461x_poke(codec, BA1_CCI, coeffIncr);
	snd_cs461x_poke(codec, BA1_CD,
		(((BA1_VARIDEC_BUF_1 + (initialDelay << 2)) << 16) & 0xFFFF0000) | 0x80);
	snd_cs461x_poke(codec, BA1_CPI, phiIncr);
	snd_spin_unlock(codec, reg, &flags);

	/*
	 *  Figure out the frame group length for the write back task.  Basically,
	 *  this is just the factors of 24000 (2^6*3*5^3) that are not present in
	 *  the output sample rate.
	 */
	frameGroupLength = 1;
	for (cnt = 2; cnt <= 64; cnt *= 2) {
		if (((rate / cnt) * cnt) != rate)
			frameGroupLength *= 2;
	}
	if (((rate / 3) * 3) != rate) {
		frameGroupLength *= 3;
	}
	for (cnt = 5; cnt <= 125; cnt *= 5) {
		if (((rate / cnt) * cnt) != rate) 
			frameGroupLength *= 5;
        }

	/*
	 * Fill in the WriteBack control block.
	 */
	snd_spin_lock(codec, reg, &flags);
	snd_cs461x_poke(codec, BA1_CFG1, frameGroupLength);
	snd_cs461x_poke(codec, BA1_CFG2, (0x00800000 | frameGroupLength));
	snd_cs461x_poke(codec, BA1_CCST, 0x0000FFFF);
	snd_cs461x_poke(codec, BA1_CSPB, ((65536 * rate) / 24000));
	snd_cs461x_poke(codec, (BA1_CSPB + 4), 0x0000FFFF);
	snd_spin_unlock(codec, reg, &flags);
}

/*
 *  PCM part
 */

static int snd_cs461x_playback_ioctl(snd_pcm1_t * pcm1,
				     unsigned int cmd,
				     unsigned long *arg)
{
	switch (cmd) {
	case SND_PCM1_IOCTL_RATE:
		pcm1->playback.real_rate = pcm1->playback.rate;
		return 0;
	case SND_PCM1_IOCTL_FRAG:
		/* fixed size */
		*(unsigned int *)arg = 2048;
		return 0;
	}
	return -ENXIO;
}

static int snd_cs461x_record_ioctl(snd_pcm1_t * pcm1,
				    unsigned int cmd, unsigned long *arg)
{
	switch (cmd) {
	case SND_PCM1_IOCTL_RATE:
		pcm1->record.real_rate = pcm1->record.rate;
		return 0;
	case SND_PCM1_IOCTL_FRAG:
		/* fixed size */
		*(unsigned int *)arg = 2048;
		return 0;
	}
	return -ENXIO;
}

static void snd_cs461x_copy_playback_buffer(snd_pcm1_t * pcm1, cs461x_t * codec)
{
	if (pcm1->playback.mode & SND_PCM1_MODE_16) {
		memcpy(codec->pbuf + (2048 * codec->ppingbuf++),
		       codec->pbuffer + (2048 * codec->pringbuf++),
		       2048);
		codec->ppingbuf &= 1;
		if (codec->psize <= (codec->pringbuf * 2048))
			codec->pringbuf = 0;
	} else {
		unsigned char *dest;
		unsigned char *src;
		int count = 1024;
		
		dest = codec->pbuf + (2048 * codec->ppingbuf++);
		src = codec->pbuffer + (1024 * codec->pringbuf++);
		while (count-- > 0) {
			*dest++ = 0x80;
			*dest++ = *src++;
		}
		codec->ppingbuf &= 1;
		if (codec->psize <= (codec->pringbuf * 1024))
			codec->pringbuf = 0;
	}
}

static void snd_cs461x_playback_trigger(snd_pcm1_t * pcm1, int up)
{
	unsigned long flags;
	cs461x_t *codec = (cs461x_t *)pcm1->private_data;
	unsigned int tmp;

	snd_spin_lock(codec, reg, &flags);
	if (up) {
		snd_cs461x_copy_playback_buffer(pcm1, codec);
		tmp = snd_cs461x_peek(codec, BA1_PCTL);
		tmp &= 0x0000ffff;
		snd_cs461x_poke(codec, BA1_PCTL, codec->pctl | tmp);
	} else {
		tmp = snd_cs461x_peek(codec, BA1_PCTL);
		tmp &= 0x0000ffff;
		snd_cs461x_poke(codec, BA1_PCTL, tmp);
	}
	snd_spin_unlock(codec, reg, &flags);
}

static void snd_cs461x_copy_record_buffer(snd_pcm1_t * pcm1, cs461x_t * codec)
{
	signed int b16;

	if (codec->cff == 1) {
		memcpy(codec->cbuffer + (2048 * codec->cringbuf++),
		       codec->cbuf + (2048 * codec->cpingbuf++),
		       2048);
		codec->cpingbuf &= 1;
		if (codec->csize <= (codec->cringbuf * 2048))
			codec->cringbuf = 0;
	} else if (codec->cff == 2) {
		unsigned char *dest;
		unsigned char *src;
		int count = 1024;

		dest = codec->cbuffer + (1024 * codec->cringbuf++);
		src = codec->cbuf + (2048 * codec->cpingbuf++);
		if (pcm1->record.mode & SND_PCM1_MODE_16) {
			while (count-- > 0) {
				b16 = *((signed short *)src)++;
				b16 += *((signed short *)src)++;
				*((unsigned short *)dest)++ = b16 >> 1;
			}
		} else {
			while (count-- > 0) {
				src++;
				*dest++ = *src++ ^ 0x80;
				src++;
				*dest++ = *src++ ^ 0x80;
			}
		}
		codec->cpingbuf &= 1;
		if (codec->csize <= (codec->cringbuf * 1024))
			codec->cringbuf = 0;
	} else if (codec->cff == 4) {
		unsigned char *dest;
		unsigned char *src;
		int count = 512;
		
		dest = codec->cbuffer + (512 * codec->cringbuf++);
		src = codec->cbuf + (2048 * codec->cpingbuf++);
		while (count-- > 0) {
			b16 = *((signed short *)src)++;
			b16 += *((signed short *)src)++;
			*dest++ = (b16 >> 9) ^ 0x80;
		}
		codec->cpingbuf &= 1;
		if (codec->csize <= (codec->cringbuf * 512))
			codec->cringbuf = 0;
	}
}

static void snd_cs461x_record_trigger(snd_pcm1_t * pcm1, int up)
{
	unsigned long flags;
	cs461x_t *codec = (cs461x_t *)pcm1->private_data;
	unsigned int tmp;

	snd_spin_lock(codec, reg, &flags);
	if (up) {
		tmp = snd_cs461x_peek(codec, BA1_CCTL);
		tmp &= 0xffff0000;
		snd_cs461x_poke(codec, BA1_CCTL, codec->cctl | tmp);
	} else {
		tmp = snd_cs461x_peek(codec, BA1_CCTL);
		tmp &= 0xffff0000;
		snd_cs461x_poke(codec, BA1_CCTL, tmp);
	}
	snd_spin_unlock(codec, reg, &flags);
}

static void snd_cs461x_playback_prepare(snd_pcm1_t * pcm1,
					unsigned char *buffer,
					unsigned int size,
					unsigned int offset,
					unsigned int count)
{
	unsigned int tmp, tmp1;
	cs461x_t *codec = (cs461x_t *)pcm1->private_data;

	if (count != 2048)
		snd_printk("cs461x: wrong playback count - %i\n", count);
	/* set the attenuation to 0dB */ 
	snd_cs461x_poke(codec, BA1_PVOL, 0x80008000);
	snd_cs461x_poke(codec, BA1_PBA, virt_to_bus(codec->pbuf));
	tmp1 = 16;
	if (pcm1->playback.voices < 2)
		tmp1 >>= 1;
	tmp = snd_cs461x_peek(codec, BA1_PDTC);
	tmp &= ~0x000003ff;
	tmp |= tmp1 - 1;
	snd_cs461x_poke(codec, BA1_PDTC, tmp);
	tmp = snd_cs461x_peek(codec, BA1_PFIE);
	tmp &= ~0x0000f03f;
	if (pcm1->playback.mode & SND_PCM1_MODE_U)
		tmp |= 0x00008000;
	if (pcm1->playback.mode & SND_PCM1_MODE_BIG)
		tmp |= 0x00004000;
	if (pcm1->playback.voices < 2)
		tmp |= 0x00002000;
	snd_cs461x_poke(codec, BA1_PFIE, tmp);
	codec->ppingbuf = 0;
	codec->pbuffer = buffer;
	codec->psize = size;
	codec->pringbuf = 0;
	snd_cs461x_set_play_sample_rate(codec, pcm1->playback.rate);
}

static void snd_cs461x_record_prepare(snd_pcm1_t * pcm1,
				      unsigned char *buffer,
				      unsigned int size,
				      unsigned int offset,
				      unsigned int count)
{
	cs461x_t *codec = (cs461x_t *) pcm1->private_data;

	if (count != 2048)
		snd_printk("cs461x: wrong record count - %i\n", count);
	/* set the attenuation to 0dB */
	snd_cs461x_poke(codec, BA1_CVOL, 0x80008000);
	snd_cs461x_poke(codec, BA1_CBA, virt_to_bus(codec->cbuf));
	codec->cpingbuf = 0;
	codec->cbuffer = buffer;
	codec->csize = size;
	codec->cringbuf = 0;
	snd_cs461x_set_capture_sample_rate(codec, pcm1->record.rate);
	codec->cff = 1;
	codec->cffc = 0;
	if (pcm1->record.voices < 2)
		codec->cff <<= 1;
	if (!(pcm1->record.mode & SND_PCM1_MODE_16))
		codec->cff <<= 1;
}

static int snd_cs461x_playback_open(snd_pcm1_t * pcm1)
{
	cs461x_t *codec = (cs461x_t *) pcm1->private_data;
	int err, pg;

	if ((err = snd_pcm1_dma_alloc(pcm1, SND_PCM1_PLAYBACK, codec->dma1ptr, "CS461x - DAC")) < 0)
		return err;
	codec->pbuf = snd_malloc_pages(PAGE_SIZE, &pg, 0);
	if (codec->pbuf == NULL) {
		snd_pcm1_dma_free(pcm1, SND_PCM1_PLAYBACK, codec->dma1ptr);
		return -ENOMEM;
	}
	return 0;
}

static int snd_cs461x_record_open(snd_pcm1_t * pcm1)
{
	cs461x_t *codec = (cs461x_t *) pcm1->private_data;
	int err, pg;

	if ((err = snd_pcm1_dma_alloc(pcm1, SND_PCM1_RECORD, codec->dma2ptr, "CS461x - ADC")) < 0)
		return err;
	codec->cbuf = snd_malloc_pages(PAGE_SIZE, &pg, 0);
	if (codec->cbuf == NULL) {
		snd_pcm1_dma_free(pcm1, SND_PCM1_RECORD, codec->dma2ptr);
		return -ENOMEM;
	}
	return 0;
}

static void snd_cs461x_playback_close(snd_pcm1_t * pcm1)
{
	cs461x_t *codec = (cs461x_t *) pcm1->private_data;

	if (codec->pbuf)
		snd_free_pages(codec->pbuf, PAGE_SIZE);
	snd_pcm1_dma_free(pcm1, SND_PCM1_PLAYBACK, codec->dma1ptr);
}

static void snd_cs461x_record_close(snd_pcm1_t * pcm1)
{
	cs461x_t *codec = (cs461x_t *) pcm1->private_data;

	if (codec->cbuf)
		snd_free_pages(codec->cbuf, PAGE_SIZE);
	snd_pcm1_dma_free(pcm1, SND_PCM1_RECORD, codec->dma2ptr);
}

static unsigned int snd_cs461x_playback_pointer(snd_pcm1_t * pcm1,
						unsigned int used_size)
{
	unsigned int result;
	cs461x_t *codec = (cs461x_t *) pcm1->private_data;

	result = snd_cs461x_peek(codec, BA1_PBA) - virt_to_bus(codec->pbuf);
	if (result > 2048)
		result -= 2048;
	if (!(pcm1->playback.mode & SND_PCM1_MODE_16))
		result >>= 1;
	return result;
}

static unsigned int snd_cs461x_record_pointer(snd_pcm1_t * pcm1,
					      unsigned int used_size)
{
	unsigned int result;
	cs461x_t *codec = (cs461x_t *) pcm1->private_data;

	result = snd_cs461x_peek(codec, BA1_CBA) - virt_to_bus(codec->cbuf);
	if (result > 2048)
		result -= 2048;
	if (!(pcm1->record.mode & SND_PCM1_MODE_16))
		result >>= 1;
	return result;
}

void snd_cs461x_interrupt(cs461x_t *codec)
{
	unsigned int status;

	/*
	 *  Read the Interrupt Status Register to clear the interrupt
	 */
	status = snd_cs461x_peekBA0(codec, BA0_HISR);
	if ((status & 0x7fffffff) == 0) {
		snd_cs461x_pokeBA0(codec, BA0_HICR, HICR_CHGM | HICR_IEV);
		return;
	}

	if ((status & HISR_VC0) && codec->pcm) {
		snd_pcm1_t *pcm1 = (snd_pcm1_t *)codec->pcm->private_data;
		if (pcm1 && (pcm1->playback.flags & SND_PCM1_FLG_TRIGGER)) {
			snd_cs461x_copy_playback_buffer(pcm1, codec);
			if (((pcm1->playback.mode & SND_PCM1_MODE_16) ||
			     codec->ppingbuf == 1) &&
			    pcm1->playback.ack)
				pcm1->playback.ack(pcm1);
		}
	}
	if ((status & HISR_VC1) && codec->pcm) {
		snd_pcm1_t *pcm1 = (snd_pcm1_t *)codec->pcm->private_data;
		if (pcm1 && (pcm1->record.flags & SND_PCM1_FLG_TRIGGER)) {
			snd_cs461x_copy_record_buffer(pcm1, codec);
			codec->cffc++;
			if (codec->cffc >= codec->cff) {
				codec->cffc = 0;
				if (pcm1->record.ack)
					pcm1->record.ack(pcm1);
			}
		}
	}
	/*
	 *  EOI to the PCI part....reenables interrupts
	 */
	snd_cs461x_pokeBA0(codec, BA0_HICR, HICR_CHGM | HICR_IEV);
}

static struct snd_stru_pcm1_hardware snd_cs461x_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,	/* hardware formats */
	3,			/* align value */
	6,			/* minimal fragment */
	5500,			/* min. rate */
	48000,			/* max. rate */
	2,			/* max. voices */
	snd_cs461x_playback_open,
	snd_cs461x_playback_close,
	snd_cs461x_playback_ioctl,
	snd_cs461x_playback_prepare,
	snd_cs461x_playback_trigger,
	snd_cs461x_playback_pointer,
	snd_pcm1_playback_dma_ulaw_loud,
	snd_pcm1_dma_move,
	snd_pcm1_playback_dma_neutral
};

static struct snd_stru_pcm1_hardware snd_cs461x_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 */
	3,			/* align value */
	6,			/* minimal fragment */
	5500,			/* min. rate */
	48000,			/* max. rate */
	2,			/* max. voices */
	snd_cs461x_record_open,
	snd_cs461x_record_close,
	snd_cs461x_record_ioctl,
	snd_cs461x_record_prepare,
	snd_cs461x_record_trigger,
	snd_cs461x_record_pointer,
	snd_pcm1_record_dma_ulaw,
	snd_pcm1_dma_move,
	NULL
};

static void snd_cs461x_pcm_free(void *private_data)
{
	cs461x_t *codec = (cs461x_t *) private_data;
	codec->pcm = NULL;
}

snd_pcm_t *snd_cs461x_pcm(cs461x_t * codec)
{
	snd_pcm_t *pcm;
	snd_pcm1_t *pcm1;

	pcm = snd_pcm1_new_device(codec->card, "CS461x");
	if (!pcm)
		return NULL;
	pcm1 = (snd_pcm1_t *) pcm->private_data;
	memcpy(&pcm1->playback.hw, &snd_cs461x_playback, sizeof(snd_cs461x_playback));
	memcpy(&pcm1->record.hw, &snd_cs461x_record, sizeof(snd_cs461x_record));
	pcm1->private_data = codec;
	pcm1->private_free = snd_cs461x_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, "CS461x");
	return codec->pcm = pcm;
}

/*
 *  Mixer routines
 */

static void snd_cs461x_mixer_free_ac97(ac97_t * ac97)
{
	((cs461x_t *) ac97->private_data)->mixer = NULL;
	snd_free(ac97, sizeof(ac97_t));
}

snd_kmixer_t *snd_cs461x_mixer(cs461x_t * codec, int pcm_dev)
{
	ac97_t *ac97;
	snd_kmixer_t *mixer;

	ac97 = snd_calloc(sizeof(ac97_t));
	if (!ac97)
		return NULL;
	ac97->write = snd_cs461x_codec_write;
	ac97->read = snd_cs461x_codec_read;
	ac97->private_data = codec;
	ac97->private_free = snd_cs461x_mixer_free_ac97;
	mixer = snd_ac97_mixer(codec->card, ac97, 1, &pcm_dev);
	if (!mixer) {
		snd_free(ac97, sizeof(ac97));
		return NULL;
	}
	return codec->mixer = mixer;
}

/*
 *  proc interface
 */

static long snd_cs461x_BA0_read(void *private_data, void *file_private_data,
				struct file *file, char *buf, long count)
{
	long size;
	cs461x_t *codec = (cs461x_t *)private_data;
	
	size = count;
	if (file->f_pos + size > CS461X_BA0_SIZE)
		size = (long)CS461X_BA0_SIZE - file->f_pos;
	if (size > 0) {
		if (verify_area(VERIFY_WRITE, buf, size))
			return -EFAULT;
		copy_to_user(buf, codec->ba0 + file->f_pos, size);
		file->f_pos += size;
		return size;
	}
	return 0;
}

static long snd_cs461x_BA1_data0_read(void *private_data, void *file_private_data,
				      struct file *file, char *buf, long count)
{
	long size;
	cs461x_t *codec = (cs461x_t *)private_data;
	
	size = count;
	if (file->f_pos + size > CS461X_BA1_DATA0_SIZE)
		size = (long)CS461X_BA1_DATA0_SIZE - file->f_pos;
	if (size > 0) {
		if (verify_area(VERIFY_WRITE, buf, size))
			return -EFAULT;
		copy_to_user(buf, codec->ba1.name.data0 + file->f_pos, size);
		file->f_pos += size;
		return size;
	}
	return 0;
}

static long snd_cs461x_BA1_data1_read(void *private_data, void *file_private_data,
				      struct file *file, char *buf, long count)
{
	long size;
	cs461x_t *codec = (cs461x_t *)private_data;
	
	size = count;
	if (file->f_pos + size > CS461X_BA1_DATA1_SIZE)
		size = (long)CS461X_BA1_DATA1_SIZE - file->f_pos;
	if (size > 0) {
		if (verify_area(VERIFY_WRITE, buf, size))
			return -EFAULT;
		copy_to_user(buf, codec->ba1.name.data1 + file->f_pos, size);
		file->f_pos += size;
		return size;
	}
	return 0;
}

static long snd_cs461x_BA1_prg_read(void *private_data, void *file_private_data,
				    struct file *file, char *buf, long count)
{
	long size;
	cs461x_t *codec = (cs461x_t *)private_data;
	
	size = count;
	if (file->f_pos + size > CS461X_BA1_PRG_SIZE)
		size = (long)CS461X_BA1_PRG_SIZE - file->f_pos;
	if (size > 0) {
		if (verify_area(VERIFY_WRITE, buf, size))
			return -EFAULT;
		copy_to_user(buf, codec->ba1.name.pmem + file->f_pos, size);
		file->f_pos += size;
		return size;
	}
	return 0;
}

static int snd_cs461x_proc_init(snd_card_t * card, cs461x_t * codec)
{
	snd_info_entry_t *entry;
	
	entry = snd_info_create_entry(card, "cs461x_BA0");
	if (entry) {
		entry->type = SND_INFO_ENTRY_DATA;
		entry->private_data = codec;
		entry->t.data.read = snd_cs461x_BA0_read;
		entry->size = CS461X_BA0_SIZE;
		if (snd_info_register(entry) < 0) {
			snd_info_unregister(entry);
			entry = NULL;
		}
	}
	codec->entry_proc_BA0 = entry;
	entry = snd_info_create_entry(card, "cs461x_BA1_data0");
	if (entry) {
		entry->type = SND_INFO_ENTRY_DATA;
		entry->private_data = codec;
		entry->t.data.read = snd_cs461x_BA1_data0_read;
		entry->size = CS461X_BA1_DATA0_SIZE;
		if (snd_info_register(entry) < 0) {
			snd_info_unregister(entry);
			entry = NULL;
		}
	}
	codec->entry_proc_BA1_data0 = entry;
	entry = snd_info_create_entry(card, "cs461x_BA1_data1");
	if (entry) {
		entry->type = SND_INFO_ENTRY_DATA;
		entry->private_data = codec;
		entry->t.data.read = snd_cs461x_BA1_data1_read;
		entry->size = CS461X_BA1_DATA1_SIZE;
		if (snd_info_register(entry) < 0) {
			snd_info_unregister(entry);
			entry = NULL;
		}
	}
	codec->entry_proc_BA1_data1 = entry;
	entry = snd_info_create_entry(card, "cs461x_BA1_prg");
	if (entry) {
		entry->type = SND_INFO_ENTRY_DATA;
		entry->private_data = codec;
		entry->t.data.read = snd_cs461x_BA1_prg_read;
		entry->size = CS461X_BA1_PRG_SIZE;
		if (snd_info_register(entry) < 0) {
			snd_info_unregister(entry);
			entry = NULL;
		}
	}
	codec->entry_proc_BA1_prg = entry;
	return 0;
}

static int snd_cs461x_proc_done(cs461x_t * codec)
{
	if (codec->entry_proc_BA0)
		snd_info_unregister((snd_info_entry_t *) codec->entry_proc_BA0);
	if (codec->entry_proc_BA1_data0)
		snd_info_unregister((snd_info_entry_t *) codec->entry_proc_BA1_data0);
	if (codec->entry_proc_BA1_data1)
		snd_info_unregister((snd_info_entry_t *) codec->entry_proc_BA1_data1);
	if (codec->entry_proc_BA1_prg)
		snd_info_unregister((snd_info_entry_t *) codec->entry_proc_BA1_prg);
	return 0;
}

/*
 *  initialization routines
 */

cs461x_t *snd_cs461x_create(snd_card_t * card,
			    struct snd_pci_dev * pci,
			    snd_dma_t * dma1ptr,
			    snd_dma_t * dma2ptr,
			    snd_irq_t * irqptr)
{
	cs461x_t *codec;
	unsigned short cmdw;
	unsigned char cmdb;
	unsigned int tmp;
	int idx;
	
	codec = (cs461x_t *) snd_calloc(sizeof(*codec));
	if (!codec)
		return NULL;
	snd_spin_prepare(codec, reg);
	snd_sleep_prepare(codec, codec);
	codec->card = card;
	codec->pci = pci;
	codec->dma1ptr = dma1ptr;
	codec->dma2ptr = dma2ptr;
	codec->irqptr = irqptr;
	codec->ba0_addr = pci->base_address[0] & ~PCI_BASE_ADDRESS_SPACE;
	codec->ba1_addr = pci->base_address[1] & ~PCI_BASE_ADDRESS_SPACE;
	if (codec->ba0_addr == 0 || codec->ba0_addr == ~0 ||
	    codec->ba1_addr == 0 || codec->ba1_addr == ~0) {
	    	snd_printk("cs461x: wrong address(es) - ba0 = 0x%lx, ba1 = 0x%lx\n", codec->ba0_addr, codec->ba1_addr);
	    	snd_cs461x_free(codec);
	    	return NULL;
	}
	codec->ba0 = ioremap(codec->ba0_addr, CS461X_BA0_SIZE);
	codec->ba1.name.data0 = ioremap(codec->ba1_addr + BA1_SP_DMEM0, CS461X_BA1_DATA0_SIZE);
	codec->ba1.name.data1 = ioremap(codec->ba1_addr + BA1_SP_DMEM1, CS461X_BA1_DATA1_SIZE);
	codec->ba1.name.pmem = ioremap(codec->ba1_addr + BA1_SP_PMEM, CS461X_BA1_PRG_SIZE);
	codec->ba1.name.reg = ioremap(codec->ba1_addr + BA1_SP_REG, CS461X_BA1_REG_SIZE);
	if (codec->ba0 == NULL || codec->ba1.name.data0 == NULL ||
	    codec->ba1.name.data1 == NULL || codec->ba1.name.pmem == NULL ||
	    codec->ba1.name.reg == NULL) {
		snd_cs461x_free(codec);
		return NULL;
	}
	snd_pci_read_config_word(pci, PCI_COMMAND, &cmdw);
	if ((cmdw & (PCI_COMMAND_MEMORY|PCI_COMMAND_MASTER)) != (PCI_COMMAND_MEMORY|PCI_COMMAND_MASTER)) {
		cmdw |= PCI_COMMAND_MEMORY|PCI_COMMAND_MASTER;
		snd_pci_write_config_word(pci, PCI_COMMAND, cmdw);
	}
	snd_pci_read_config_byte(pci, PCI_LATENCY_TIMER, &cmdb);
	if (cmdb < 32)
		cmdb = 32;
	snd_pci_write_config_byte(pci, PCI_LATENCY_TIMER, cmdb);

	/* 
	 *  First, blast the clock control register to zero so that the PLL starts
         *  out in a known state, and blast the master serial port control register
         *  to zero so that the serial ports also start out in a known state.
         */
        snd_cs461x_pokeBA0(codec, BA0_CLKCR1, 0);
        snd_cs461x_pokeBA0(codec, BA0_SERMC1, 0);

	/*
	 *  If we are in AC97 mode, then we must set the part to a host controlled
         *  AC-link.  Otherwise, we won't be able to bring up the link.
         */        
        snd_cs461x_pokeBA0(codec, BA0_SERACC, SERACC_HSP | SERACC_CODEC_TYPE_1_03);	/* 1.03 codec */
        /* snd_cs461x_pokeBA0(codec, BA0_SERACC, SERACC_HSP | SERACC_CODEC_TYPE_2_0); */ /* 2.00 codec */

        /*
         *  Drive the ARST# pin low for a minimum of 1uS (as defined in the AC97
         *  spec) and then drive it high.  This is done for non AC97 modes since
         *  there might be logic external to the CS461x that uses the ARST# line
         *  for a reset.
         */
        snd_cs461x_pokeBA0(codec, BA0_ACCTL, 0);
        snd_delay(5);
        snd_cs461x_pokeBA0(codec, BA0_ACCTL, ACCTL_RSTN);

	/*
	 *  The first thing we do here is to enable sync generation.  As soon
	 *  as we start receiving bit clock, we'll start producing the SYNC
	 *  signal.
	 */
	snd_cs461x_pokeBA0(codec, BA0_ACCTL, ACCTL_ESYN | ACCTL_RSTN);

	/*
	 *  Now wait for a short while to allow the AC97 part to start
	 *  generating bit clock (so we don't try to start the PLL without an
	 *  input clock).
	 */
	snd_delay(1000);

	/*
	 *  Set the serial port timing configuration, so that
	 *  the clock control circuit gets its clock from the correct place.
	 */
	snd_cs461x_pokeBA0(codec, BA0_SERMC1, SERMC1_PTC_AC97);

	/*
	 *  Write the selected clock control setup to the hardware.  Do not turn on
	 *  SWCE yet (if requested), so that the devices clocked by the output of
	 *  PLL are not clocked until the PLL is stable.
	 */
	snd_cs461x_pokeBA0(codec, BA0_PLLCC, PLLCC_LPF_1050_2780_KHZ | PLLCC_CDR_73_104_MHZ);
	snd_cs461x_pokeBA0(codec, BA0_PLLM, 0x3a);
	snd_cs461x_pokeBA0(codec, BA0_CLKCR2, CLKCR2_PDIVS_8);

	/*
	 *  Power up the PLL.
	 */
	snd_cs461x_pokeBA0(codec, BA0_CLKCR1, CLKCR1_PLLP);

	/*
         *  Wait until the PLL has stabilized.
	 */
	snd_delay(1000);

	/*
	 *  Turn on clocking of the core so that we can setup the serial ports.
	 */
	tmp = snd_cs461x_peekBA0(codec, BA0_CLKCR1) | CLKCR1_SWCE;
	snd_cs461x_pokeBA0(codec, BA0_CLKCR1, tmp);

	/*
	 *  Fill the serial port FIFOs with silence.
	 */
	snd_cs461x_clear_serial_FIFOs(codec);

	/*
	 *  Set the serial port FIFO pointer to the first sample in the FIFO.
	 */
	/* snd_cs461x_pokeBA0(codec, BA0_SERBSP, 0); */

	/*
	 *  Write the serial port configuration to the part.  The master
	 *  enable bit is not set until all other values have been written.
	 */
	snd_cs461x_pokeBA0(codec, BA0_SERC1, SERC1_SO1F_AC97 | SERC1_SO1EN);
	snd_cs461x_pokeBA0(codec, BA0_SERC2, SERC2_SI1F_AC97 | SERC1_SO1EN);
	snd_cs461x_pokeBA0(codec, BA0_SERMC1, SERMC1_PTC_AC97 | SERMC1_MSPE);

	/*
	 * Wait for the codec ready signal from the AC97 codec.
	 */
	for (idx = 0; idx < 1000; idx++) {
		/*
		 *  First, lets wait a short while to let things settle out a bit,
		 *  and to prevent retrying the read too quickly.
		 */
		snd_delay(5);

		/*
		 *  Read the AC97 status register to see if we've seen a CODEC READY
		 *  signal from the AC97 codec.
		 */
		if (snd_cs461x_peekBA0(codec, BA0_ACSTS) & ACSTS_CRDY)
			break;
	}

	/*
	 *  Make sure CODEC is READY.
	 */
	if (!(snd_cs461x_peekBA0(codec, BA0_ACSTS) & ACSTS_CRDY)) {
		snd_printk("cs461x: create - never read codec ready from AC'97\n");
		snd_cs461x_free(codec);
		return NULL;
	}

	/*
	 *  Assert the vaid frame signal so that we can start sending commands
	 *  to the AC97 codec.
	 */
	snd_cs461x_pokeBA0(codec, BA0_ACCTL, ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN);

	/*
	 *  Wait until we've sampled input slots 3 and 4 as valid, meaning that
	 *  the codec is pumping ADC data across the AC-link.
	 */
	for (idx = 0; idx < 1000; idx++) {
		/*
		 *  First, lets wait a short while to let things settle out a bit,
		 *  and to prevent retrying the read too quickly.
		 */
		snd_delay(50);
		/*
		 *  Read the input slot valid register and see if input slots 3 and
		 *  4 are valid yet.
		 */
		if ((snd_cs461x_peekBA0(codec, BA0_ACISV) & (ACISV_ISV3 | ACISV_ISV4)) == (ACISV_ISV3 | ACISV_ISV4))
			break;
	}

	/*
	 *  Make sure input slots 3 and 4 are valid.  If not, then return
	 *  an error.
	 */
	if ((snd_cs461x_peekBA0(codec, BA0_ACISV) & (ACISV_ISV3 | ACISV_ISV4)) != (ACISV_ISV3 | ACISV_ISV4)) {
		snd_printk("cs461x: create - never read ISV3 & ISV4 from AC'97\n");
		snd_cs461x_free(codec);
		return NULL;
	}

	/*
	 *  Now, assert valid frame and the slot 3 and 4 valid bits.  This will
	 *  commense the transfer of digital audio data to the AC97 codec.
	 */
	snd_cs461x_pokeBA0(codec, BA0_ACOSV, ACOSV_SLV3 | ACOSV_SLV4);

	/*
	 *  Power down the DAC and ADC.  We will power them up (if) when we need
	 *  them.
	 */
	/* snd_cs461x_pokeBA0(codec, BA0_AC97_POWERDOWN, 0x300); */

	/*
	 *  Turn off the Processor by turning off the software clock enable flag in 
	 *  the clock control register.
	 */
	/* tmp = snd_cs461x_peekBA0(codec, BA0_CLKCR1) & ~CLKCR1_SWCE; */
	/* snd_cs461x_pokeBA0(codec, BA0_CLKCR1, tmp); */

	/*
         *  Reset the processor.
         */
	snd_cs461x_reset(codec);

	/*
         *  Download the image to the processor.
	 */
	if (snd_cs461x_download_image(codec) < 0) {
		snd_printk("cs461x: image download error\n");
		snd_cs461x_free(codec);
		return NULL;
	}

	/*
         *  Stop playback DMA.
	 */
	tmp = snd_cs461x_peek(codec, BA1_PCTL);
	codec->pctl = tmp & 0xffff0000;
	snd_cs461x_poke(codec, BA1_PCTL, tmp & 0x0000ffff);

	/*
         *  Stop capture DMA.
	 */
	tmp = snd_cs461x_peek(codec, BA1_CCTL);
	codec->cctl = tmp & 0x0000ffff;
	snd_cs461x_poke(codec, BA1_CCTL, tmp & 0xffff0000);

	snd_cs461x_powerup_adc(codec);
	snd_cs461x_powerup_dac(codec);

	snd_cs461x_set_play_sample_rate(codec, 8000);
	snd_cs461x_set_capture_sample_rate(codec, 8000);

	snd_cs461x_proc_start(codec);

	/*
	 *  Enable interrupts on the part.
	 */
	snd_cs461x_pokeBA0(codec, BA0_HICR, HICR_IEV | HICR_CHGM);

	tmp = snd_cs461x_peek(codec, BA1_PFIE);
	tmp &= ~0x0000f03f;
	snd_cs461x_poke(codec, BA1_PFIE, tmp);	/* playback interrupt enable */

	tmp = snd_cs461x_peek(codec, BA1_CIE);
	tmp &= ~0x0000003f;
	tmp |=  0x00000001;
	snd_cs461x_poke(codec, BA1_CIE, tmp);	/* capture interrupt enable */	

	snd_cs461x_proc_init(card, codec);

	return codec;
}

void snd_cs461x_free(cs461x_t * codec)
{
	unsigned int tmp;

	snd_cs461x_proc_done(codec);

	tmp = snd_cs461x_peek(codec, BA1_PFIE);
	tmp &= ~0x0000f03f;
	tmp |=  0x00000010;
	snd_cs461x_poke(codec, BA1_PFIE, tmp);	/* playback interrupt disable */

	tmp = snd_cs461x_peek(codec, BA1_CIE);
	tmp &= ~0x0000003f;
	tmp |=  0x00000011;
	snd_cs461x_poke(codec, BA1_CIE, tmp);	/* capture interrupt disable */

	/*
         *  Stop playback DMA.
	 */
	tmp = snd_cs461x_peek(codec, BA1_PCTL);
	snd_cs461x_poke(codec, BA1_PCTL, tmp & 0x0000ffff);

	/*
         *  Stop capture DMA.
	 */
	tmp = snd_cs461x_peek(codec, BA1_CCTL);
	snd_cs461x_poke(codec, BA1_CCTL, tmp & 0xffff0000);

	/*
         *  Reset the processor.
         */
	snd_cs461x_reset(codec);

	snd_cs461x_proc_stop(codec);

	/*
	 *  Power down the DAC and ADC.  We will power them up (if) when we need
	 *  them.
	 */
	snd_cs461x_codec_write(codec, AC97_POWERDOWN, 0x300);

	/*
	 *  Power down the PLL.
	 */
	snd_cs461x_pokeBA0(codec, BA0_CLKCR1, 0);

	/*
	 *  Turn off the Processor by turning off the software clock enable flag in 
	 *  the clock control register.
	 */
	tmp = snd_cs461x_peekBA0(codec, BA0_CLKCR1) & ~CLKCR1_SWCE;
	snd_cs461x_pokeBA0(codec, BA0_CLKCR1, tmp);

	if (codec == NULL)
		return;
	if (codec->ba0)
		iounmap(codec->ba0);
	if (codec->ba1.name.data0)
		iounmap(codec->ba1.name.data0);
	if (codec->ba1.name.data1)
		iounmap(codec->ba1.name.data1);
	if (codec->ba1.name.pmem)
		iounmap(codec->ba1.name.pmem);
	if (codec->ba1.name.reg)
		iounmap(codec->ba1.name.reg);

	snd_free(codec, sizeof(*codec));
}

/*
 *  INIT part
 */

#ifndef LINUX_2_1
extern struct symbol_table snd_symbol_table_cs461x_export;
#endif

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

void cleanup_module(void)
{
}
