aboutsummaryrefslogtreecommitdiff
path: root/sound/pci/cs46xx/cs46xx_lib.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /sound/pci/cs46xx/cs46xx_lib.c
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'sound/pci/cs46xx/cs46xx_lib.c')
-rw-r--r--sound/pci/cs46xx/cs46xx_lib.c3922
1 files changed, 3922 insertions, 0 deletions
diff --git a/sound/pci/cs46xx/cs46xx_lib.c b/sound/pci/cs46xx/cs46xx_lib.c
new file mode 100644
index 00000000000..5f2ffb7efa0
--- /dev/null
+++ b/sound/pci/cs46xx/cs46xx_lib.c
@@ -0,0 +1,3922 @@
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ * Abramo Bagnara <abramo@alsa-project.org>
+ * Cirrus Logic, Inc.
+ * Routines for control of Cirrus Logic CS461x chips
+ *
+ * KNOWN BUGS:
+ * - Sometimes the SPDIF input DSP tasks get's unsynchronized
+ * and the SPDIF get somewhat "distorcionated", or/and left right channel
+ * are swapped. To get around this problem when it happens, mute and unmute
+ * the SPDIF input mixer controll.
+ * - On the Hercules Game Theater XP the amplifier are sometimes turned
+ * off on inadecuate moments which causes distorcions on sound.
+ *
+ * TODO:
+ * - Secondary CODEC on some soundcards
+ * - SPDIF input support for other sample rates then 48khz
+ * - Posibility to mix the SPDIF output with analog sources.
+ * - PCM channels for Center and LFE on secondary codec
+ *
+ * NOTE: with CONFIG_SND_CS46XX_NEW_DSP unset uses old DSP image (which
+ * is default configuration), no SPDIF, no secondary codec, no
+ * multi channel PCM. But known to work.
+ *
+ * FINALLY: A credit to the developers Tom and Jordan
+ * at Cirrus for have helping me out with the DSP, however we
+ * still don't have sufficient documentation and technical
+ * references to be able to implement all fancy feutures
+ * supported by the cs46xx DSP's.
+ * Benny <benny@hostmobility.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/pm.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/gameport.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/cs46xx.h>
+
+#include <asm/io.h>
+
+#include "cs46xx_lib.h"
+#include "dsp_spos.h"
+
+static void amp_voyetra(cs46xx_t *chip, int change);
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+static snd_pcm_ops_t snd_cs46xx_playback_rear_ops;
+static snd_pcm_ops_t snd_cs46xx_playback_indirect_rear_ops;
+static snd_pcm_ops_t snd_cs46xx_playback_clfe_ops;
+static snd_pcm_ops_t snd_cs46xx_playback_indirect_clfe_ops;
+static snd_pcm_ops_t snd_cs46xx_playback_iec958_ops;
+static snd_pcm_ops_t snd_cs46xx_playback_indirect_iec958_ops;
+#endif
+
+static snd_pcm_ops_t snd_cs46xx_playback_ops;
+static snd_pcm_ops_t snd_cs46xx_playback_indirect_ops;
+static snd_pcm_ops_t snd_cs46xx_capture_ops;
+static snd_pcm_ops_t snd_cs46xx_capture_indirect_ops;
+
+static unsigned short snd_cs46xx_codec_read(cs46xx_t *chip,
+ unsigned short reg,
+ int codec_index)
+{
+ int count;
+ unsigned short result,tmp;
+ u32 offset = 0;
+ snd_assert ( (codec_index == CS46XX_PRIMARY_CODEC_INDEX) ||
+ (codec_index == CS46XX_SECONDARY_CODEC_INDEX),
+ return -EINVAL);
+
+ chip->active_ctrl(chip, 1);
+
+ if (codec_index == CS46XX_SECONDARY_CODEC_INDEX)
+ offset = CS46XX_SECONDARY_CODEC_OFFSET;
+
+ /*
+ * 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 write7---55
+ * 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
+ */
+
+ snd_cs46xx_peekBA0(chip, BA0_ACSDA + offset);
+
+ tmp = snd_cs46xx_peekBA0(chip, BA0_ACCTL);
+ if ((tmp & ACCTL_VFRM) == 0) {
+ snd_printk(KERN_WARNING "cs46xx: ACCTL_VFRM not set 0x%x\n",tmp);
+ snd_cs46xx_pokeBA0(chip, BA0_ACCTL, (tmp & (~ACCTL_ESYN)) | ACCTL_VFRM );
+ msleep(50);
+ tmp = snd_cs46xx_peekBA0(chip, BA0_ACCTL + offset);
+ snd_cs46xx_pokeBA0(chip, BA0_ACCTL, tmp | ACCTL_ESYN | ACCTL_VFRM );
+
+ }
+
+ /*
+ * 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_cs46xx_pokeBA0(chip, BA0_ACCAD, reg);
+ snd_cs46xx_pokeBA0(chip, BA0_ACCDA, 0);
+ if (codec_index == CS46XX_PRIMARY_CODEC_INDEX) {
+ snd_cs46xx_pokeBA0(chip, BA0_ACCTL,/* clear ACCTL_DCV */ ACCTL_CRW |
+ ACCTL_VFRM | ACCTL_ESYN |
+ ACCTL_RSTN);
+ snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_DCV | ACCTL_CRW |
+ ACCTL_VFRM | ACCTL_ESYN |
+ ACCTL_RSTN);
+ } else {
+ snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_DCV | ACCTL_TC |
+ ACCTL_CRW | ACCTL_VFRM | ACCTL_ESYN |
+ ACCTL_RSTN);
+ }
+
+ /*
+ * Wait for the read to occur.
+ */
+ for (count = 0; count < 1000; count++) {
+ /*
+ * First, we want to wait for a short time.
+ */
+ udelay(10);
+ /*
+ * Now, check to see if the read has completed.
+ * ACCTL = 460h, DCV should be reset by now and 460h = 17h
+ */
+ if (!(snd_cs46xx_peekBA0(chip, BA0_ACCTL) & ACCTL_DCV))
+ goto ok1;
+ }
+
+ snd_printk("AC'97 read problem (ACCTL_DCV), reg = 0x%x\n", reg);
+ result = 0xffff;
+ goto end;
+
+ ok1:
+ /*
+ * 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_cs46xx_peekBA0(chip, BA0_ACSTS + offset) & ACSTS_VSTS)
+ goto ok2;
+ udelay(10);
+ }
+
+ snd_printk("AC'97 read problem (ACSTS_VSTS), codec_index %d, reg = 0x%x\n", codec_index, reg);
+ result = 0xffff;
+ goto end;
+
+ ok2:
+ /*
+ * 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_cs46xx_peekBA0(chip, BA0_ACSDA),
+ snd_cs46xx_peekBA0(chip, BA0_ACCAD));
+#endif
+
+ //snd_cs46xx_peekBA0(chip, BA0_ACCAD);
+ result = snd_cs46xx_peekBA0(chip, BA0_ACSDA + offset);
+ end:
+ chip->active_ctrl(chip, -1);
+ return result;
+}
+
+static unsigned short snd_cs46xx_ac97_read(ac97_t * ac97,
+ unsigned short reg)
+{
+ cs46xx_t *chip = ac97->private_data;
+ unsigned short val;
+ int codec_index = ac97->num;
+
+ snd_assert(codec_index == CS46XX_PRIMARY_CODEC_INDEX ||
+ codec_index == CS46XX_SECONDARY_CODEC_INDEX,
+ return 0xffff);
+
+ val = snd_cs46xx_codec_read(chip, reg, codec_index);
+
+ return val;
+}
+
+
+static void snd_cs46xx_codec_write(cs46xx_t *chip,
+ unsigned short reg,
+ unsigned short val,
+ int codec_index)
+{
+ int count;
+
+ snd_assert ((codec_index == CS46XX_PRIMARY_CODEC_INDEX) ||
+ (codec_index == CS46XX_SECONDARY_CODEC_INDEX),
+ return);
+
+ chip->active_ctrl(chip, 1);
+
+ /*
+ * 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
+ */
+
+ /*
+ * 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_cs46xx_pokeBA0(chip, BA0_ACCAD , reg);
+ snd_cs46xx_pokeBA0(chip, BA0_ACCDA , val);
+ snd_cs46xx_peekBA0(chip, BA0_ACCTL);
+
+ if (codec_index == CS46XX_PRIMARY_CODEC_INDEX) {
+ snd_cs46xx_pokeBA0(chip, BA0_ACCTL, /* clear ACCTL_DCV */ ACCTL_VFRM |
+ ACCTL_ESYN | ACCTL_RSTN);
+ snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_DCV | ACCTL_VFRM |
+ ACCTL_ESYN | ACCTL_RSTN);
+ } else {
+ snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_DCV | ACCTL_TC |
+ ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN);
+ }
+
+ for (count = 0; count < 4000; count++) {
+ /*
+ * First, we want to wait for a short time.
+ */
+ udelay(10);
+ /*
+ * Now, check to see if the write has completed.
+ * ACCTL = 460h, DCV should be reset by now and 460h = 07h
+ */
+ if (!(snd_cs46xx_peekBA0(chip, BA0_ACCTL) & ACCTL_DCV)) {
+ goto end;
+ }
+ }
+ snd_printk("AC'97 write problem, codec_index = %d, reg = 0x%x, val = 0x%x\n", codec_index, reg, val);
+ end:
+ chip->active_ctrl(chip, -1);
+}
+
+static void snd_cs46xx_ac97_write(ac97_t *ac97,
+ unsigned short reg,
+ unsigned short val)
+{
+ cs46xx_t *chip = ac97->private_data;
+ int codec_index = ac97->num;
+
+ snd_assert(codec_index == CS46XX_PRIMARY_CODEC_INDEX ||
+ codec_index == CS46XX_SECONDARY_CODEC_INDEX,
+ return);
+
+ snd_cs46xx_codec_write(chip, reg, val, codec_index);
+}
+
+
+/*
+ * Chip initialization
+ */
+
+int snd_cs46xx_download(cs46xx_t *chip,
+ u32 *src,
+ unsigned long offset,
+ unsigned long len)
+{
+ void __iomem *dst;
+ unsigned int bank = offset >> 16;
+ offset = offset & 0xffff;
+
+ snd_assert(!(offset & 3) && !(len & 3), return -EINVAL);
+ dst = chip->region.idx[bank+1].remap_addr + offset;
+ len /= sizeof(u32);
+
+ /* writel already converts 32-bit value to right endianess */
+ while (len-- > 0) {
+ writel(*src++, dst);
+ dst += sizeof(u32);
+ }
+ return 0;
+}
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+
+#include "imgs/cwc4630.h"
+#include "imgs/cwcasync.h"
+#include "imgs/cwcsnoop.h"
+#include "imgs/cwcbinhack.h"
+#include "imgs/cwcdma.h"
+
+int snd_cs46xx_clear_BA1(cs46xx_t *chip,
+ unsigned long offset,
+ unsigned long len)
+{
+ void __iomem *dst;
+ unsigned int bank = offset >> 16;
+ offset = offset & 0xffff;
+
+ snd_assert(!(offset & 3) && !(len & 3), return -EINVAL);
+ dst = chip->region.idx[bank+1].remap_addr + offset;
+ len /= sizeof(u32);
+
+ /* writel already converts 32-bit value to right endianess */
+ while (len-- > 0) {
+ writel(0, dst);
+ dst += sizeof(u32);
+ }
+ return 0;
+}
+
+#else /* old DSP image */
+
+#include "cs46xx_image.h"
+
+int snd_cs46xx_download_image(cs46xx_t *chip)
+{
+ int idx, err;
+ unsigned long offset = 0;
+
+ for (idx = 0; idx < BA1_MEMORY_COUNT; idx++) {
+ if ((err = snd_cs46xx_download(chip,
+ &BA1Struct.map[offset],
+ BA1Struct.memory[idx].offset,
+ BA1Struct.memory[idx].size)) < 0)
+ return err;
+ offset += BA1Struct.memory[idx].size >> 2;
+ }
+ return 0;
+}
+#endif /* CONFIG_SND_CS46XX_NEW_DSP */
+
+/*
+ * Chip reset
+ */
+
+static void snd_cs46xx_reset(cs46xx_t *chip)
+{
+ int idx;
+
+ /*
+ * Write the reset bit of the SP control register.
+ */
+ snd_cs46xx_poke(chip, BA1_SPCR, SPCR_RSTSP);
+
+ /*
+ * Write the control register.
+ */
+ snd_cs46xx_poke(chip, BA1_SPCR, SPCR_DRQEN);
+
+ /*
+ * Clear the trap registers.
+ */
+ for (idx = 0; idx < 8; idx++) {
+ snd_cs46xx_poke(chip, BA1_DREG, DREG_REGID_TRAP_SELECT + idx);
+ snd_cs46xx_poke(chip, BA1_TWPR, 0xFFFF);
+ }
+ snd_cs46xx_poke(chip, BA1_DREG, 0);
+
+ /*
+ * Set the frame timer to reflect the number of cycles per frame.
+ */
+ snd_cs46xx_poke(chip, BA1_FRMT, 0xadf);
+}
+
+static int cs46xx_wait_for_fifo(cs46xx_t * chip,int retry_timeout)
+{
+ u32 i, status = 0;
+ /*
+ * Make sure the previous FIFO write operation has completed.
+ */
+ for(i = 0; i < 50; i++){
+ status = snd_cs46xx_peekBA0(chip, BA0_SERBST);
+
+ if( !(status & SERBST_WBSY) )
+ break;
+
+ mdelay(retry_timeout);
+ }
+
+ if(status & SERBST_WBSY) {
+ snd_printk( KERN_ERR "cs46xx: failure waiting for FIFO command to complete\n");
+
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void snd_cs46xx_clear_serial_FIFOs(cs46xx_t *chip)
+{
+ int idx, 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.
+ */
+ tmp = snd_cs46xx_peekBA0(chip, BA0_CLKCR1);
+ if (!(tmp & CLKCR1_SWCE)) {
+ snd_cs46xx_pokeBA0(chip, 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_cs46xx_pokeBA0(chip, BA0_SERBWP, 0);
+
+ /*
+ * Fill all 256 sample FIFO locations.
+ */
+ for (idx = 0; idx < 0xFF; idx++) {
+ /*
+ * Make sure the previous FIFO write operation has completed.
+ */
+ if (cs46xx_wait_for_fifo(chip,1)) {
+ snd_printdd ("failed waiting for FIFO at addr (%02X)\n",idx);
+
+ if (powerdown)
+ snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, tmp);
+
+ break;
+ }
+ /*
+ * Write the serial port FIFO index.
+ */
+ snd_cs46xx_pokeBA0(chip, BA0_SERBAD, idx);
+ /*
+ * Tell the serial port to load the new value into the FIFO location.
+ */
+ snd_cs46xx_pokeBA0(chip, 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_cs46xx_pokeBA0(chip, BA0_CLKCR1, tmp);
+}
+
+static void snd_cs46xx_proc_start(cs46xx_t *chip)
+{
+ int cnt;
+
+ /*
+ * Set the frame timer to reflect the number of cycles per frame.
+ */
+ snd_cs46xx_poke(chip, BA1_FRMT, 0xadf);
+ /*
+ * Turn on the run, run at frame, and DMA enable bits in the local copy of
+ * the SP control register.
+ */
+ snd_cs46xx_poke(chip, 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++) {
+ udelay(50);
+ if (!(snd_cs46xx_peek(chip, BA1_SPCR) & SPCR_RUNFR))
+ break;
+ }
+
+ if (snd_cs46xx_peek(chip, BA1_SPCR) & SPCR_RUNFR)
+ snd_printk("SPCR_RUNFR never reset\n");
+}
+
+static void snd_cs46xx_proc_stop(cs46xx_t *chip)
+{
+ /*
+ * Turn off the run, run at frame, and DMA enable bits in the local copy of
+ * the SP control register.
+ */
+ snd_cs46xx_poke(chip, BA1_SPCR, 0);
+}
+
+/*
+ * Sample rate routines
+ */
+
+#define GOF_PER_SEC 200
+
+static void snd_cs46xx_set_play_sample_rate(cs46xx_t *chip, 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.
+ */
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_cs46xx_poke(chip, BA1_PSRC,
+ ((correctionPerSec << 16) & 0xFFFF0000) | (correctionPerGOF & 0xFFFF));
+ snd_cs46xx_poke(chip, BA1_PPI, phiIncr);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static void snd_cs46xx_set_capture_sample_rate(cs46xx_t *chip, 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.
+ */
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_cs46xx_poke(chip, BA1_CSRC,
+ ((correctionPerSec << 16) & 0xFFFF0000) | (correctionPerGOF & 0xFFFF));
+ snd_cs46xx_poke(chip, BA1_CCI, coeffIncr);
+ snd_cs46xx_poke(chip, BA1_CD,
+ (((BA1_VARIDEC_BUF_1 + (initialDelay << 2)) << 16) & 0xFFFF0000) | 0x80);
+ snd_cs46xx_poke(chip, BA1_CPI, phiIncr);
+ spin_unlock_irqrestore(&chip->reg_lock, 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.
+ */
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ snd_cs46xx_poke(chip, BA1_CFG1, frameGroupLength);
+ snd_cs46xx_poke(chip, BA1_CFG2, (0x00800000 | frameGroupLength));
+ snd_cs46xx_poke(chip, BA1_CCST, 0x0000FFFF);
+ snd_cs46xx_poke(chip, BA1_CSPB, ((65536 * rate) / 24000));
+ snd_cs46xx_poke(chip, (BA1_CSPB + 4), 0x0000FFFF);
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+/*
+ * PCM part
+ */
+
+static void snd_cs46xx_pb_trans_copy(snd_pcm_substream_t *substream,
+ snd_pcm_indirect_t *rec, size_t bytes)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ cs46xx_pcm_t * cpcm = runtime->private_data;
+ memcpy(cpcm->hw_buf.area + rec->hw_data, runtime->dma_area + rec->sw_data, bytes);
+}
+
+static int snd_cs46xx_playback_transfer(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ cs46xx_pcm_t * cpcm = runtime->private_data;
+ snd_pcm_indirect_playback_transfer(substream, &cpcm->pcm_rec, snd_cs46xx_pb_trans_copy);
+ return 0;
+}
+
+static void snd_cs46xx_cp_trans_copy(snd_pcm_substream_t *substream,
+ snd_pcm_indirect_t *rec, size_t bytes)
+{
+ cs46xx_t *chip = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ memcpy(runtime->dma_area + rec->sw_data,
+ chip->capt.hw_buf.area + rec->hw_data, bytes);
+}
+
+static int snd_cs46xx_capture_transfer(snd_pcm_substream_t *substream)
+{
+ cs46xx_t *chip = snd_pcm_substream_chip(substream);
+ snd_pcm_indirect_capture_transfer(substream, &chip->capt.pcm_rec, snd_cs46xx_cp_trans_copy);
+ return 0;
+}
+
+static snd_pcm_uframes_t snd_cs46xx_playback_direct_pointer(snd_pcm_substream_t * substream)
+{
+ cs46xx_t *chip = snd_pcm_substream_chip(substream);
+ size_t ptr;
+ cs46xx_pcm_t *cpcm = substream->runtime->private_data;
+ snd_assert (cpcm->pcm_channel,return -ENXIO);
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+ ptr = snd_cs46xx_peek(chip, (cpcm->pcm_channel->pcm_reader_scb->address + 2) << 2);
+#else
+ ptr = snd_cs46xx_peek(chip, BA1_PBA);
+#endif
+ ptr -= cpcm->hw_buf.addr;
+ return ptr >> cpcm->shift;
+}
+
+static snd_pcm_uframes_t snd_cs46xx_playback_indirect_pointer(snd_pcm_substream_t * substream)
+{
+ cs46xx_t *chip = snd_pcm_substream_chip(substream);
+ size_t ptr;
+ cs46xx_pcm_t *cpcm = substream->runtime->private_data;
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+ snd_assert (cpcm->pcm_channel,return -ENXIO);
+ ptr = snd_cs46xx_peek(chip, (cpcm->pcm_channel->pcm_reader_scb->address + 2) << 2);
+#else
+ ptr = snd_cs46xx_peek(chip, BA1_PBA);
+#endif
+ ptr -= cpcm->hw_buf.addr;
+ return snd_pcm_indirect_playback_pointer(substream, &cpcm->pcm_rec, ptr);
+}
+
+static snd_pcm_uframes_t snd_cs46xx_capture_direct_pointer(snd_pcm_substream_t * substream)
+{
+ cs46xx_t *chip = snd_pcm_substream_chip(substream);
+ size_t ptr = snd_cs46xx_peek(chip, BA1_CBA) - chip->capt.hw_buf.addr;
+ return ptr >> chip->capt.shift;
+}
+
+static snd_pcm_uframes_t snd_cs46xx_capture_indirect_pointer(snd_pcm_substream_t * substream)
+{
+ cs46xx_t *chip = snd_pcm_substream_chip(substream);
+ size_t ptr = snd_cs46xx_peek(chip, BA1_CBA) - chip->capt.hw_buf.addr;
+ return snd_pcm_indirect_capture_pointer(substream, &chip->capt.pcm_rec, ptr);
+}
+
+static int snd_cs46xx_playback_trigger(snd_pcm_substream_t * substream,
+ int cmd)
+{
+ cs46xx_t *chip = snd_pcm_substream_chip(substream);
+ /*snd_pcm_runtime_t *runtime = substream->runtime;*/
+ int result = 0;
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+ cs46xx_pcm_t *cpcm = substream->runtime->private_data;
+ if (! cpcm->pcm_channel) {
+ return -ENXIO;
+ }
+#endif
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+ /* magic value to unmute PCM stream playback volume */
+ snd_cs46xx_poke(chip, (cpcm->pcm_channel->pcm_reader_scb->address +
+ SCBVolumeCtrl) << 2, 0x80008000);
+
+ if (cpcm->pcm_channel->unlinked)
+ cs46xx_dsp_pcm_link(chip,cpcm->pcm_channel);
+
+ if (substream->runtime->periods != CS46XX_FRAGS)
+ snd_cs46xx_playback_transfer(substream);
+#else
+ spin_lock(&chip->reg_lock);
+ if (substream->runtime->periods != CS46XX_FRAGS)
+ snd_cs46xx_playback_transfer(substream);
+ { unsigned int tmp;
+ tmp = snd_cs46xx_peek(chip, BA1_PCTL);
+ tmp &= 0x0000ffff;
+ snd_cs46xx_poke(chip, BA1_PCTL, chip->play_ctl | tmp);
+ }
+ spin_unlock(&chip->reg_lock);
+#endif
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+ /* magic mute channel */
+ snd_cs46xx_poke(chip, (cpcm->pcm_channel->pcm_reader_scb->address +
+ SCBVolumeCtrl) << 2, 0xffffffff);
+
+ if (!cpcm->pcm_channel->unlinked)
+ cs46xx_dsp_pcm_unlink(chip,cpcm->pcm_channel);
+#else
+ spin_lock(&chip->reg_lock);
+ { unsigned int tmp;
+ tmp = snd_cs46xx_peek(chip, BA1_PCTL);
+ tmp &= 0x0000ffff;
+ snd_cs46xx_poke(chip, BA1_PCTL, tmp);
+ }
+ spin_unlock(&chip->reg_lock);
+#endif
+ break;
+ default:
+ result = -EINVAL;
+ break;
+ }
+
+ return result;
+}
+
+static int snd_cs46xx_capture_trigger(snd_pcm_substream_t * substream,
+ int cmd)
+{
+ cs46xx_t *chip = snd_pcm_substream_chip(substream);
+ unsigned int tmp;
+ int result = 0;
+
+ spin_lock(&chip->reg_lock);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ tmp = snd_cs46xx_peek(chip, BA1_CCTL);
+ tmp &= 0xffff0000;
+ snd_cs46xx_poke(chip, BA1_CCTL, chip->capt.ctl | tmp);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ tmp = snd_cs46xx_peek(chip, BA1_CCTL);
+ tmp &= 0xffff0000;
+ snd_cs46xx_poke(chip, BA1_CCTL, tmp);
+ break;
+ default:
+ result = -EINVAL;
+ break;
+ }
+ spin_unlock(&chip->reg_lock);
+
+ return result;
+}
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+static int _cs46xx_adjust_sample_rate (cs46xx_t *chip, cs46xx_pcm_t *cpcm,
+ int sample_rate)
+{
+
+ /* If PCMReaderSCB and SrcTaskSCB not created yet ... */
+ if ( cpcm->pcm_channel == NULL) {
+ cpcm->pcm_channel = cs46xx_dsp_create_pcm_channel (chip, sample_rate,
+ cpcm, cpcm->hw_buf.addr,cpcm->pcm_channel_id);
+ if (cpcm->pcm_channel == NULL) {
+ snd_printk(KERN_ERR "cs46xx: failed to create virtual PCM channel\n");
+ return -ENOMEM;
+ }
+ cpcm->pcm_channel->sample_rate = sample_rate;
+ } else
+ /* if sample rate is changed */
+ if ((int)cpcm->pcm_channel->sample_rate != sample_rate) {
+ int unlinked = cpcm->pcm_channel->unlinked;
+ cs46xx_dsp_destroy_pcm_channel (chip,cpcm->pcm_channel);
+
+ if ( (cpcm->pcm_channel = cs46xx_dsp_create_pcm_channel (chip, sample_rate, cpcm,
+ cpcm->hw_buf.addr,
+ cpcm->pcm_channel_id)) == NULL) {
+ snd_printk(KERN_ERR "cs46xx: failed to re-create virtual PCM channel\n");
+ return -ENOMEM;
+ }
+
+ if (!unlinked) cs46xx_dsp_pcm_link (chip,cpcm->pcm_channel);
+ cpcm->pcm_channel->sample_rate = sample_rate;
+ }
+
+ return 0;
+}
+#endif
+
+
+static int snd_cs46xx_playback_hw_params(snd_pcm_substream_t * substream,
+ snd_pcm_hw_params_t * hw_params)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ cs46xx_pcm_t *cpcm;
+ int err;
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+ cs46xx_t *chip = snd_pcm_substream_chip(substream);
+ int sample_rate = params_rate(hw_params);
+ int period_size = params_period_bytes(hw_params);
+#endif
+ cpcm = runtime->private_data;
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+ snd_assert (sample_rate != 0, return -ENXIO);
+
+ down (&chip->spos_mutex);
+
+ if (_cs46xx_adjust_sample_rate (chip,cpcm,sample_rate)) {
+ up (&chip->spos_mutex);
+ return -ENXIO;
+ }
+
+ snd_assert (cpcm->pcm_channel != NULL);
+ if (!cpcm->pcm_channel) {
+ up (&chip->spos_mutex);
+ return -ENXIO;
+ }
+
+
+ if (cs46xx_dsp_pcm_channel_set_period (chip,cpcm->pcm_channel,period_size)) {
+ up (&chip->spos_mutex);
+ return -EINVAL;
+ }
+
+ snd_printdd ("period_size (%d), periods (%d) buffer_size(%d)\n",
+ period_size, params_periods(hw_params),
+ params_buffer_bytes(hw_params));
+#endif
+
+ if (params_periods(hw_params) == CS46XX_FRAGS) {
+ if (runtime->dma_area != cpcm->hw_buf.area)
+ snd_pcm_lib_free_pages(substream);
+ runtime->dma_area = cpcm->hw_buf.area;
+ runtime->dma_addr = cpcm->hw_buf.addr;
+ runtime->dma_bytes = cpcm->hw_buf.bytes;
+
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+ if (cpcm->pcm_channel_id == DSP_PCM_MAIN_CHANNEL) {
+ substream->ops = &snd_cs46xx_playback_ops;
+ } else if (cpcm->pcm_channel_id == DSP_PCM_REAR_CHANNEL) {
+ substream->ops = &snd_cs46xx_playback_rear_ops;
+ } else if (cpcm->pcm_channel_id == DSP_PCM_CENTER_LFE_CHANNEL) {
+ substream->ops = &snd_cs46xx_playback_clfe_ops;
+ } else if (cpcm->pcm_channel_id == DSP_IEC958_CHANNEL) {
+ substream->ops = &snd_cs46xx_playback_iec958_ops;
+ } else {
+ snd_assert(0);
+ }
+#else
+ substream->ops = &snd_cs46xx_playback_ops;
+#endif
+
+ } else {
+ if (runtime->dma_area == cpcm->hw_buf.area) {
+ runtime->dma_area = NULL;
+ runtime->dma_addr = 0;
+ runtime->dma_bytes = 0;
+ }
+ if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) {
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+ up (&chip->spos_mutex);
+#endif
+ return err;
+ }
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+ if (cpcm->pcm_channel_id == DSP_PCM_MAIN_CHANNEL) {
+ substream->ops = &snd_cs46xx_playback_indirect_ops;
+ } else if (cpcm->pcm_channel_id == DSP_PCM_REAR_CHANNEL) {
+ substream->ops = &snd_cs46xx_playback_indirect_rear_ops;
+ } else if (cpcm->pcm_channel_id == DSP_PCM_CENTER_LFE_CHANNEL) {
+ substream->ops = &snd_cs46xx_playback_indirect_clfe_ops;
+ } else if (cpcm->pcm_channel_id == DSP_IEC958_CHANNEL) {
+ substream->ops = &snd_cs46xx_playback_indirect_iec958_ops;
+ } else {
+ snd_assert(0);
+ }
+#else
+ substream->ops = &snd_cs46xx_playback_indirect_ops;
+#endif
+
+ }
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+ up (&chip->spos_mutex);
+#endif
+
+ return 0;
+}
+
+static int snd_cs46xx_playback_hw_free(snd_pcm_substream_t * substream)
+{
+ /*cs46xx_t *chip = snd_pcm_substream_chip(substream);*/
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ cs46xx_pcm_t *cpcm;
+
+ cpcm = runtime->private_data;
+
+ /* if play_back open fails, then this function
+ is called and cpcm can actually be NULL here */
+ if (!cpcm) return -ENXIO;
+
+ if (runtime->dma_area != cpcm->hw_buf.area)
+ snd_pcm_lib_free_pages(substream);
+
+ runtime->dma_area = NULL;
+ runtime->dma_addr = 0;
+ runtime->dma_bytes = 0;
+
+ return 0;
+}
+
+static int snd_cs46xx_playback_prepare(snd_pcm_substream_t * substream)
+{
+ unsigned int tmp;
+ unsigned int pfie;
+ cs46xx_t *chip = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ cs46xx_pcm_t *cpcm;
+
+ cpcm = runtime->private_data;
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+ snd_assert (cpcm->pcm_channel != NULL, return -ENXIO);
+
+ pfie = snd_cs46xx_peek(chip, (cpcm->pcm_channel->pcm_reader_scb->address + 1) << 2 );
+ pfie &= ~0x0000f03f;
+#else
+ /* old dsp */
+ pfie = snd_cs46xx_peek(chip, BA1_PFIE);
+ pfie &= ~0x0000f03f;
+#endif
+
+ cpcm->shift = 2;
+ /* if to convert from stereo to mono */
+ if (runtime->channels == 1) {
+ cpcm->shift--;
+ pfie |= 0x00002000;
+ }
+ /* if to convert from 8 bit to 16 bit */
+ if (snd_pcm_format_width(runtime->format) == 8) {
+ cpcm->shift--;
+ pfie |= 0x00001000;
+ }
+ /* if to convert to unsigned */
+ if (snd_pcm_format_unsigned(runtime->format))
+ pfie |= 0x00008000;
+
+ /* Never convert byte order when sample stream is 8 bit */
+ if (snd_pcm_format_width(runtime->format) != 8) {
+ /* convert from big endian to little endian */
+ if (snd_pcm_format_big_endian(runtime->format))
+ pfie |= 0x00004000;
+ }
+
+ memset(&cpcm->pcm_rec, 0, sizeof(cpcm->pcm_rec));
+ cpcm->pcm_rec.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream);
+ cpcm->pcm_rec.hw_buffer_size = runtime->period_size * CS46XX_FRAGS << cpcm->shift;
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+
+ tmp = snd_cs46xx_peek(chip, (cpcm->pcm_channel->pcm_reader_scb->address) << 2);
+ tmp &= ~0x000003ff;
+ tmp |= (4 << cpcm->shift) - 1;
+ /* playback transaction count register */
+ snd_cs46xx_poke(chip, (cpcm->pcm_channel->pcm_reader_scb->address) << 2, tmp);
+
+ /* playback format && interrupt enable */
+ snd_cs46xx_poke(chip, (cpcm->pcm_channel->pcm_reader_scb->address + 1) << 2, pfie | cpcm->pcm_channel->pcm_slot);
+#else
+ snd_cs46xx_poke(chip, BA1_PBA, cpcm->hw_buf.addr);
+ tmp = snd_cs46xx_peek(chip, BA1_PDTC);
+ tmp &= ~0x000003ff;
+ tmp |= (4 << cpcm->shift) - 1;
+ snd_cs46xx_poke(chip, BA1_PDTC, tmp);
+ snd_cs46xx_poke(chip, BA1_PFIE, pfie);
+ snd_cs46xx_set_play_sample_rate(chip, runtime->rate);
+#endif
+
+ return 0;
+}
+
+static int snd_cs46xx_capture_hw_params(snd_pcm_substream_t * substream,
+ snd_pcm_hw_params_t * hw_params)
+{
+ cs46xx_t *chip = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int err;
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+ cs46xx_dsp_pcm_ostream_set_period (chip, params_perio