📄 ac97_codec.c
字号:
/* * Copyright (c) by Jaroslav Kysela <perex@suse.cz> * Universal interface for Audio Codec '97 * * For more details look to AC '97 component specification revision 2.2 * by Intel Corporation (http://developer.intel.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/init.h>#include <linux/slab.h>#include <linux/pci.h>#include <linux/moduleparam.h>#include <sound/core.h>#include <sound/pcm.h>#include <sound/ac97_codec.h>#include <sound/asoundef.h>#include <sound/initval.h>#include "ac97_local.h"#include "ac97_id.h"#include "ac97_patch.h"MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");MODULE_DESCRIPTION("Universal interface for Audio Codec '97");MODULE_LICENSE("GPL");static int enable_loopback;module_param(enable_loopback, bool, 0444);MODULE_PARM_DESC(enable_loopback, "Enable AC97 ADC/DAC Loopback Control");/* */typedef struct { unsigned int id; unsigned int mask; const char *name; int (*patch)(ac97_t *ac97); int (*mpatch)(ac97_t *ac97); unsigned int flags;} ac97_codec_id_t;static const ac97_codec_id_t snd_ac97_codec_id_vendors[] = {{ 0x414b4d00, 0xffffff00, "Asahi Kasei", NULL, NULL },{ 0x41445300, 0xffffff00, "Analog Devices", NULL, NULL },{ 0x414c4300, 0xffffff00, "Realtek", NULL, NULL },{ 0x414c4700, 0xffffff00, "Realtek", NULL, NULL },{ 0x434d4900, 0xffffff00, "C-Media Electronics", NULL, NULL },{ 0x43525900, 0xffffff00, "Cirrus Logic", NULL, NULL },{ 0x43585400, 0xffffff00, "Conexant", NULL, NULL },{ 0x44543000, 0xffffff00, "Diamond Technology", NULL, NULL },{ 0x454d4300, 0xffffff00, "eMicro", NULL, NULL },{ 0x45838300, 0xffffff00, "ESS Technology", NULL, NULL },{ 0x48525300, 0xffffff00, "Intersil", NULL, NULL },{ 0x49434500, 0xffffff00, "ICEnsemble", NULL, NULL },{ 0x49544500, 0xffffff00, "ITE Tech.Inc", NULL, NULL },{ 0x4e534300, 0xffffff00, "National Semiconductor", NULL, NULL },{ 0x50534300, 0xffffff00, "Philips", NULL, NULL },{ 0x53494c00, 0xffffff00, "Silicon Laboratory", NULL, NULL },{ 0x54524100, 0xffffff00, "TriTech", NULL, NULL },{ 0x54584e00, 0xffffff00, "Texas Instruments", NULL, NULL },{ 0x56494100, 0xffffff00, "VIA Technologies", NULL, NULL },{ 0x57454300, 0xffffff00, "Winbond", NULL, NULL },{ 0x574d4c00, 0xffffff00, "Wolfson", NULL, NULL },{ 0x594d4800, 0xffffff00, "Yamaha", NULL, NULL },{ 0x83847600, 0xffffff00, "SigmaTel", NULL, NULL },{ 0, 0, NULL, NULL, NULL }};static const ac97_codec_id_t snd_ac97_codec_ids[] = {{ 0x414b4d00, 0xffffffff, "AK4540", NULL, NULL },{ 0x414b4d01, 0xffffffff, "AK4542", NULL, NULL },{ 0x414b4d02, 0xffffffff, "AK4543", NULL, NULL },{ 0x414b4d06, 0xffffffff, "AK4544A", NULL, NULL },{ 0x414b4d07, 0xffffffff, "AK4545", NULL, NULL },{ 0x41445303, 0xffffffff, "AD1819", patch_ad1819, NULL },{ 0x41445340, 0xffffffff, "AD1881", patch_ad1881, NULL },{ 0x41445348, 0xffffffff, "AD1881A", patch_ad1881, NULL },{ 0x41445360, 0xffffffff, "AD1885", patch_ad1885, NULL },{ 0x41445361, 0xffffffff, "AD1886", patch_ad1886, NULL },{ 0x41445362, 0xffffffff, "AD1887", patch_ad1881, NULL },{ 0x41445363, 0xffffffff, "AD1886A", patch_ad1881, NULL },{ 0x41445368, 0xffffffff, "AD1888", patch_ad1888, NULL },{ 0x41445370, 0xffffffff, "AD1980", patch_ad1980, NULL },{ 0x41445372, 0xffffffff, "AD1981A", patch_ad1981a, NULL },{ 0x41445374, 0xffffffff, "AD1981B", patch_ad1981b, NULL },{ 0x41445375, 0xffffffff, "AD1985", patch_ad1985, NULL },{ 0x41445378, 0xffffffff, "AD1986", patch_ad1985, NULL },{ 0x414c4300, 0xffffff00, "ALC100,100P", NULL, NULL },{ 0x414c4710, 0xfffffff0, "ALC200,200P", NULL, NULL },{ 0x414c4721, 0xffffffff, "ALC650D", NULL, NULL }, /* already patched */{ 0x414c4722, 0xffffffff, "ALC650E", NULL, NULL }, /* already patched */{ 0x414c4723, 0xffffffff, "ALC650F", NULL, NULL }, /* already patched */{ 0x414c4720, 0xfffffff0, "ALC650", patch_alc650, NULL },{ 0x414c4760, 0xfffffff0, "ALC655", patch_alc655, NULL },{ 0x414c4781, 0xffffffff, "ALC658D", NULL, NULL }, /* already patched */{ 0x414c4780, 0xfffffff0, "ALC658", patch_alc655, NULL },{ 0x414c4790, 0xfffffff0, "ALC850", patch_alc850, NULL },{ 0x414c4730, 0xffffffff, "ALC101", NULL, NULL },{ 0x414c4740, 0xfffffff0, "ALC202", NULL, NULL },{ 0x414c4750, 0xfffffff0, "ALC250", NULL, NULL },{ 0x414c4770, 0xfffffff0, "ALC203", NULL, NULL },{ 0x434d4941, 0xffffffff, "CMI9738", patch_cm9738, NULL },{ 0x434d4961, 0xffffffff, "CMI9739", patch_cm9739, NULL },{ 0x434d4969, 0xffffffff, "CMI9780", patch_cm9780, NULL },{ 0x434d4978, 0xffffffff, "CMI9761", patch_cm9761, NULL },{ 0x434d4982, 0xffffffff, "CMI9761", patch_cm9761, NULL },{ 0x434d4983, 0xffffffff, "CMI9761", patch_cm9761, NULL },{ 0x43525900, 0xfffffff8, "CS4297", NULL, NULL },{ 0x43525910, 0xfffffff8, "CS4297A", patch_cirrus_spdif, NULL },{ 0x43525920, 0xfffffff8, "CS4298", patch_cirrus_spdif, NULL },{ 0x43525928, 0xfffffff8, "CS4294", NULL, NULL },{ 0x43525930, 0xfffffff8, "CS4299", patch_cirrus_cs4299, NULL },{ 0x43525948, 0xfffffff8, "CS4201", NULL, NULL },{ 0x43525958, 0xfffffff8, "CS4205", patch_cirrus_spdif, NULL },{ 0x43525960, 0xfffffff8, "CS4291", NULL, NULL },{ 0x43525970, 0xfffffff8, "CS4202", NULL, NULL },{ 0x43585421, 0xffffffff, "HSD11246", NULL, NULL }, // SmartMC II{ 0x43585428, 0xfffffff8, "Cx20468", patch_conexant, NULL }, // SmartAMC fixme: the mask might be different{ 0x44543031, 0xfffffff0, "DT0398", NULL, NULL },{ 0x454d4328, 0xffffffff, "28028", NULL, NULL }, // same as TR28028?{ 0x45838308, 0xffffffff, "ESS1988", NULL, NULL },{ 0x48525300, 0xffffff00, "HMP9701", NULL, NULL },{ 0x49434501, 0xffffffff, "ICE1230", NULL, NULL },{ 0x49434511, 0xffffffff, "ICE1232", NULL, NULL }, // alias VIA VT1611A?{ 0x49434514, 0xffffffff, "ICE1232A", NULL, NULL },{ 0x49434551, 0xffffffff, "VT1616", patch_vt1616, NULL }, { 0x49434552, 0xffffffff, "VT1616i", patch_vt1616, NULL }, // VT1616 compatible (chipset integrated){ 0x49544520, 0xffffffff, "IT2226E", NULL, NULL },{ 0x49544561, 0xffffffff, "IT2646E", patch_it2646, NULL },{ 0x4e534300, 0xffffffff, "LM4540,43,45,46,48", NULL, NULL }, // only guess --jk{ 0x4e534331, 0xffffffff, "LM4549", NULL, NULL },{ 0x4e534350, 0xffffffff, "LM4550", NULL, NULL },{ 0x50534304, 0xffffffff, "UCB1400", NULL, NULL },{ 0x53494c20, 0xffffffe0, "Si3036,8", mpatch_si3036, mpatch_si3036, AC97_MODEM_PATCH },{ 0x54524102, 0xffffffff, "TR28022", NULL, NULL },{ 0x54524106, 0xffffffff, "TR28026", NULL, NULL },{ 0x54524108, 0xffffffff, "TR28028", patch_tritech_tr28028, NULL }, // added by xin jin [07/09/99]{ 0x54524123, 0xffffffff, "TR28602", NULL, NULL }, // only guess --jk [TR28023 = eMicro EM28023 (new CT1297)]{ 0x54584e20, 0xffffffff, "TLC320AD9xC", NULL, NULL },{ 0x56494161, 0xffffffff, "VIA1612A", NULL, NULL }, // modified ICE1232 with S/PDIF{ 0x56494170, 0xffffffff, "VIA1617A", patch_vt1617a, NULL }, // modified VT1616 with S/PDIF{ 0x57454301, 0xffffffff, "W83971D", NULL, NULL },{ 0x574d4c00, 0xffffffff, "WM9701A", NULL, NULL },{ 0x574d4C03, 0xffffffff, "WM9703,WM9707,WM9708,WM9717", patch_wolfson03, NULL},{ 0x574d4C04, 0xffffffff, "WM9704M,WM9704Q", patch_wolfson04, NULL},{ 0x574d4C05, 0xffffffff, "WM9705,WM9710", patch_wolfson05, NULL},{ 0x574d4C09, 0xffffffff, "WM9709", NULL, NULL},{ 0x574d4C12, 0xffffffff, "WM9711,WM9712", patch_wolfson11, NULL},{ 0x574d4c13, 0xffffffff, "WM9713,WM9714", patch_wolfson13, NULL, AC97_DEFAULT_POWER_OFF},{ 0x594d4800, 0xffffffff, "YMF743", NULL, NULL },{ 0x594d4802, 0xffffffff, "YMF752", NULL, NULL },{ 0x594d4803, 0xffffffff, "YMF753", patch_yamaha_ymf753, NULL },{ 0x83847600, 0xffffffff, "STAC9700,83,84", patch_sigmatel_stac9700, NULL },{ 0x83847604, 0xffffffff, "STAC9701,3,4,5", NULL, NULL },{ 0x83847605, 0xffffffff, "STAC9704", NULL, NULL },{ 0x83847608, 0xffffffff, "STAC9708,11", patch_sigmatel_stac9708, NULL },{ 0x83847609, 0xffffffff, "STAC9721,23", patch_sigmatel_stac9721, NULL },{ 0x83847644, 0xffffffff, "STAC9744", patch_sigmatel_stac9744, NULL },{ 0x83847650, 0xffffffff, "STAC9750,51", NULL, NULL }, // patch?{ 0x83847652, 0xffffffff, "STAC9752,53", NULL, NULL }, // patch?{ 0x83847656, 0xffffffff, "STAC9756,57", patch_sigmatel_stac9756, NULL },{ 0x83847658, 0xffffffff, "STAC9758,59", patch_sigmatel_stac9758, NULL },{ 0x83847666, 0xffffffff, "STAC9766,67", NULL, NULL }, // patch?{ 0, 0, NULL, NULL, NULL }};const char *snd_ac97_stereo_enhancements[] ={ /* 0 */ "No 3D Stereo Enhancement", /* 1 */ "Analog Devices Phat Stereo", /* 2 */ "Creative Stereo Enhancement", /* 3 */ "National Semi 3D Stereo Enhancement", /* 4 */ "YAMAHA Ymersion", /* 5 */ "BBE 3D Stereo Enhancement", /* 6 */ "Crystal Semi 3D Stereo Enhancement", /* 7 */ "Qsound QXpander", /* 8 */ "Spatializer 3D Stereo Enhancement", /* 9 */ "SRS 3D Stereo Enhancement", /* 10 */ "Platform Tech 3D Stereo Enhancement", /* 11 */ "AKM 3D Audio", /* 12 */ "Aureal Stereo Enhancement", /* 13 */ "Aztech 3D Enhancement", /* 14 */ "Binaura 3D Audio Enhancement", /* 15 */ "ESS Technology Stereo Enhancement", /* 16 */ "Harman International VMAx", /* 17 */ "Nvidea/IC Ensemble/KS Waves 3D Stereo Enhancement", /* 18 */ "Philips Incredible Sound", /* 19 */ "Texas Instruments 3D Stereo Enhancement", /* 20 */ "VLSI Technology 3D Stereo Enhancement", /* 21 */ "TriTech 3D Stereo Enhancement", /* 22 */ "Realtek 3D Stereo Enhancement", /* 23 */ "Samsung 3D Stereo Enhancement", /* 24 */ "Wolfson Microelectronics 3D Enhancement", /* 25 */ "Delta Integration 3D Enhancement", /* 26 */ "SigmaTel 3D Enhancement", /* 27 */ "IC Ensemble/KS Waves", /* 28 */ "Rockwell 3D Stereo Enhancement", /* 29 */ "Reserved 29", /* 30 */ "Reserved 30", /* 31 */ "Reserved 31"};/* * I/O routines */static int snd_ac97_valid_reg(ac97_t *ac97, unsigned short reg){ if (ac97->limited_regs && ! test_bit(reg, ac97->reg_accessed)) return 0; /* filter some registers for buggy codecs */ switch (ac97->id) { case AC97_ID_AK4540: case AC97_ID_AK4542: if (reg <= 0x1c || reg == 0x20 || reg == 0x26 || reg >= 0x7c) return 1; return 0; case AC97_ID_AD1819: /* AD1819 */ case AC97_ID_AD1881: /* AD1881 */ case AC97_ID_AD1881A: /* AD1881A */ if (reg >= 0x3a && reg <= 0x6e) /* 0x59 */ return 0; return 1; case AC97_ID_AD1885: /* AD1885 */ case AC97_ID_AD1886: /* AD1886 */ case AC97_ID_AD1886A: /* AD1886A - !!verify!! --jk */ case AC97_ID_AD1887: /* AD1887 - !!verify!! --jk */ if (reg == 0x5a) return 1; if (reg >= 0x3c && reg <= 0x6e) /* 0x59 */ return 0; return 1; case AC97_ID_STAC9700: case AC97_ID_STAC9704: case AC97_ID_STAC9705: case AC97_ID_STAC9708: case AC97_ID_STAC9721: case AC97_ID_STAC9744: case AC97_ID_STAC9756: if (reg <= 0x3a || reg >= 0x5a) return 1; return 0; } return 1;}/** * snd_ac97_write - write a value on the given register * @ac97: the ac97 instance * @reg: the register to change * @value: the value to set * * Writes a value on the given register. This will invoke the write * callback directly after the register check. * This function doesn't change the register cache unlike * #snd_ca97_write_cache(), so use this only when you don't want to * reflect the change to the suspend/resume state. */void snd_ac97_write(ac97_t *ac97, unsigned short reg, unsigned short value){ if (!snd_ac97_valid_reg(ac97, reg)) return; if ((ac97->id & 0xffffff00) == AC97_ID_ALC100) { /* Fix H/W bug of ALC100/100P */ if (reg == AC97_MASTER || reg == AC97_HEADPHONE) ac97->bus->ops->write(ac97, AC97_RESET, 0); /* reset audio codec */ } ac97->bus->ops->write(ac97, reg, value);}/** * snd_ac97_read - read a value from the given register * * @ac97: the ac97 instance * @reg: the register to read * * Reads a value from the given register. This will invoke the read * callback directly after the register check. * * Returns the read value. */unsigned short snd_ac97_read(ac97_t *ac97, unsigned short reg){ if (!snd_ac97_valid_reg(ac97, reg)) return 0; return ac97->bus->ops->read(ac97, reg);}/* read a register - return the cached value if already read */static inline unsigned short snd_ac97_read_cache(ac97_t *ac97, unsigned short reg){ if (! test_bit(reg, ac97->reg_accessed)) { ac97->regs[reg] = ac97->bus->ops->read(ac97, reg); // set_bit(reg, ac97->reg_accessed); } return ac97->regs[reg];}/** * snd_ac97_write_cache - write a value on the given register and update the cache * @ac97: the ac97 instance * @reg: the register to change * @value: the value to set * * Writes a value on the given register and updates the register * cache. The cached values are used for the cached-read and the * suspend/resume. */void snd_ac97_write_cache(ac97_t *ac97, unsigned short reg, unsigned short value){ if (!snd_ac97_valid_reg(ac97, reg)) return; down(&ac97->reg_mutex); ac97->regs[reg] = value; ac97->bus->ops->write(ac97, reg, value); set_bit(reg, ac97->reg_accessed); up(&ac97->reg_mutex);}/** * snd_ac97_update - update the value on the given register * @ac97: the ac97 instance * @reg: the register to change * @value: the value to set * * Compares the value with the register cache and updates the value * only when the value is changed. * * Returns 1 if the value is changed, 0 if no change, or a negative * code on failure. */int snd_ac97_update(ac97_t *ac97, unsigned short reg, unsigned short value){ int change; if (!snd_ac97_valid_reg(ac97, reg)) return -EINVAL; down(&ac97->reg_mutex); change = ac97->regs[reg] != value; if (change) { ac97->regs[reg] = value; ac97->bus->ops->write(ac97, reg, value); } set_bit(reg, ac97->reg_accessed); up(&ac97->reg_mutex); return change;}/** * snd_ac97_update_bits - update the bits on the given register * @ac97: the ac97 instance * @reg: the register to change * @mask: the bit-mask to change * @value: the value to set * * Updates the masked-bits on the given register only when the value * is changed. * * Returns 1 if the bits are changed, 0 if no change, or a negative * code on failure. */int snd_ac97_update_bits(ac97_t *ac97, unsigned short reg, unsigned short mask, unsigned short value){ int change; if (!snd_ac97_valid_reg(ac97, reg)) return -EINVAL; down(&ac97->reg_mutex);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -