📄 cs46xx_lib.c
字号:
/* * 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_DSPstatic 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;#endifstatic 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. */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -