📄 ac97_codec.c
字号:
/* * ac97_codec.c: Generic AC97 mixer/modem module * * Derived from ac97 mixer in maestro and trident driver. * * Copyright 2000 Silicon Integrated System Corporation * * 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. * ************************************************************************** * * The Intel Audio Codec '97 specification is available at the Intel * audio homepage: http://developer.intel.com/ial/scalableplatforms/audio/ * * The specification itself is currently available at: * ftp://download.intel.com/ial/scalableplatforms/ac97r22.pdf * ************************************************************************** * * History * v0.4 Mar 15 2000 Ollie Lho * dual codecs support verified with 4 channels output * v0.3 Feb 22 2000 Ollie Lho * bug fix for record mask setting * v0.2 Feb 10 2000 Ollie Lho * add ac97_read_proc for /proc/driver/{vendor}/ac97 * v0.1 Jan 14 2000 Ollie Lho <ollie@sis.com.tw> * Isolated from trident.c to support multiple ac97 codec */#include <linux/module.h>#include <linux/version.h>#include <linux/kernel.h>#include <linux/string.h>#include <linux/errno.h>#include <linux/bitops.h>#include <linux/delay.h>#include <linux/ac97_codec.h>#include <asm/uaccess.h>static int ac97_read_mixer(struct ac97_codec *codec, int oss_channel);static void ac97_write_mixer(struct ac97_codec *codec, int oss_channel, unsigned int left, unsigned int right);static void ac97_set_mixer(struct ac97_codec *codec, unsigned int oss_mixer, unsigned int val );static int ac97_recmask_io(struct ac97_codec *codec, int rw, int mask);static int ac97_mixer_ioctl(struct ac97_codec *codec, unsigned int cmd, unsigned long arg);static int ac97_init_mixer(struct ac97_codec *codec);static int wolfson_init(struct ac97_codec * codec);static int tritech_init(struct ac97_codec * codec);static int tritech_maestro_init(struct ac97_codec * codec);static int sigmatel_9708_init(struct ac97_codec *codec);static int sigmatel_9721_init(struct ac97_codec *codec);static int sigmatel_9744_init(struct ac97_codec *codec);static int eapd_control(struct ac97_codec *codec, int);static int crystal_digital_control(struct ac97_codec *codec, int mode);/* * AC97 operations. * * If you are adding a codec then you should be able to use * eapd_ops - any codec that supports EAPD amp control (most) * null_ops - any ancient codec that supports nothing * * The three functions are * init - used for non AC97 standard initialisation * amplifier - used to do amplifier control (1=on 0=off) * digital - switch to digital modes (0 = analog) * * Not all codecs support all features, not all drivers use all the * operations yet */ static struct ac97_ops null_ops = { NULL, NULL, NULL };static struct ac97_ops default_ops = { NULL, eapd_control, NULL };static struct ac97_ops wolfson_ops = { wolfson_init, NULL, NULL };static struct ac97_ops tritech_ops = { tritech_init, NULL, NULL };static struct ac97_ops tritech_m_ops = { tritech_maestro_init, NULL, NULL };static struct ac97_ops sigmatel_9708_ops = { sigmatel_9708_init, NULL, NULL };static struct ac97_ops sigmatel_9721_ops = { sigmatel_9721_init, NULL, NULL };static struct ac97_ops sigmatel_9744_ops = { sigmatel_9744_init, NULL, NULL };static struct ac97_ops crystal_digital_ops = { NULL, eapd_control, crystal_digital_control };/* sorted by vendor/device id */static const struct { u32 id; char *name; struct ac97_ops *ops;} ac97_codec_ids[] = { {0x41445303, "Analog Devices AD1819", &null_ops}, {0x41445340, "Analog Devices AD1881", &null_ops}, {0x41445348, "Analog Devices AD1881A", &null_ops}, {0x41445360, "Analog Devices AD1885", &default_ops}, {0x41445460, "Analog Devices AD1885", &default_ops}, {0x414B4D00, "Asahi Kasei AK4540", &null_ops}, {0x414B4D01, "Asahi Kasei AK4542", &null_ops}, {0x414B4D02, "Asahi Kasei AK4543", &null_ops}, {0x414C4710, "ALC200/200P", &null_ops}, {0x43525900, "Cirrus Logic CS4297", &default_ops}, {0x43525903, "Cirrus Logic CS4297", &default_ops}, {0x43525913, "Cirrus Logic CS4297A rev A", &default_ops}, {0x43525914, "Cirrus Logic CS4297A rev B", &default_ops}, {0x43525923, "Cirrus Logic CS4298", &null_ops}, {0x4352592B, "Cirrus Logic CS4294", &null_ops}, {0x4352592D, "Cirrus Logic CS4294", &null_ops}, {0x43525931, "Cirrus Logic CS4299 rev A", &crystal_digital_ops}, {0x43525933, "Cirrus Logic CS4299 rev C", &crystal_digital_ops}, {0x43525934, "Cirrus Logic CS4299 rev D", &crystal_digital_ops}, {0x4352594d, "Cirrus Logic CS4201" , &null_ops}, {0x45838308, "ESS Allegro ES1988", &null_ops}, {0x49434511, "ICE1232", &null_ops}, /* I hope --jk */ {0x4e534331, "National Semiconductor LM4549", &null_ops}, {0x50534304, "Philips UCB1400", &default_ops}, {0x53494c22, "Silicon Laboratory Si3036", &null_ops}, {0x53494c23, "Silicon Laboratory Si3038", &null_ops}, {0x545200FF, "TriTech TR?????", &tritech_m_ops}, {0x54524102, "TriTech TR28022", &null_ops}, {0x54524103, "TriTech TR28023", &null_ops}, {0x54524106, "TriTech TR28026", &null_ops}, {0x54524108, "TriTech TR28028", &tritech_ops}, {0x54524123, "TriTech TR A5", &null_ops}, {0x574D4C00, "Wolfson WM9704", &wolfson_ops}, {0x574D4C03, "Wolfson WM9703/9704", &wolfson_ops}, {0x574D4C04, "Wolfson WM9704 (quad)", &wolfson_ops}, {0x83847600, "SigmaTel STAC????", &null_ops}, {0x83847604, "SigmaTel STAC9701/3/4/5", &null_ops}, {0x83847605, "SigmaTel STAC9704", &null_ops}, {0x83847608, "SigmaTel STAC9708", &sigmatel_9708_ops}, {0x83847609, "SigmaTel STAC9721/23", &sigmatel_9721_ops}, {0x83847644, "SigmaTel STAC9744/45", &sigmatel_9744_ops}, {0x83847656, "SigmaTel STAC9756/57", &sigmatel_9744_ops}, {0x83847684, "SigmaTel STAC9783/84?", &null_ops}, {0x57454301, "Winbond 83971D", &null_ops},};static const char *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 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 */ "Winbond 3D Stereo Enhancement", /* 28 */ "Rockwell 3D Stereo Enhancement", /* 29 */ "Reserved 29", /* 30 */ "Reserved 30", /* 31 */ "Reserved 31"};/* this table has default mixer values for all OSS mixers. */static struct mixer_defaults { int mixer; unsigned int value;} mixer_defaults[SOUND_MIXER_NRDEVICES] = { /* all values 0 -> 100 in bytes */ {SOUND_MIXER_VOLUME, 0x4343}, {SOUND_MIXER_BASS, 0x4343}, {SOUND_MIXER_TREBLE, 0x4343}, {SOUND_MIXER_PCM, 0x4343}, {SOUND_MIXER_SPEAKER, 0x4343}, {SOUND_MIXER_LINE, 0x4343}, {SOUND_MIXER_MIC, 0x0000}, {SOUND_MIXER_CD, 0x4343}, {SOUND_MIXER_ALTPCM, 0x4343}, {SOUND_MIXER_IGAIN, 0x4343}, {SOUND_MIXER_LINE1, 0x4343}, {SOUND_MIXER_PHONEIN, 0x4343}, {SOUND_MIXER_PHONEOUT, 0x4343}, {SOUND_MIXER_VIDEO, 0x4343}, {-1,0}};/* table to scale scale from OSS mixer value to AC97 mixer register value */ static struct ac97_mixer_hw { unsigned char offset; int scale;} ac97_hw[SOUND_MIXER_NRDEVICES]= { [SOUND_MIXER_VOLUME] = {AC97_MASTER_VOL_STEREO,64}, [SOUND_MIXER_BASS] = {AC97_MASTER_TONE, 16}, [SOUND_MIXER_TREBLE] = {AC97_MASTER_TONE, 16}, [SOUND_MIXER_PCM] = {AC97_PCMOUT_VOL, 32}, [SOUND_MIXER_SPEAKER] = {AC97_PCBEEP_VOL, 16}, [SOUND_MIXER_LINE] = {AC97_LINEIN_VOL, 32}, [SOUND_MIXER_MIC] = {AC97_MIC_VOL, 32}, [SOUND_MIXER_CD] = {AC97_CD_VOL, 32}, [SOUND_MIXER_ALTPCM] = {AC97_HEADPHONE_VOL, 64}, [SOUND_MIXER_IGAIN] = {AC97_RECORD_GAIN, 16}, [SOUND_MIXER_LINE1] = {AC97_AUX_VOL, 32}, [SOUND_MIXER_PHONEIN] = {AC97_PHONE_VOL, 32}, [SOUND_MIXER_PHONEOUT] = {AC97_MASTER_VOL_MONO, 64}, [SOUND_MIXER_VIDEO] = {AC97_VIDEO_VOL, 32},};/* the following tables allow us to go from OSS <-> ac97 quickly. */enum ac97_recsettings { AC97_REC_MIC=0, AC97_REC_CD, AC97_REC_VIDEO, AC97_REC_AUX, AC97_REC_LINE, AC97_REC_STEREO, /* combination of all enabled outputs.. */ AC97_REC_MONO, /*.. or the mono equivalent */ AC97_REC_PHONE};static const unsigned int ac97_rm2oss[] = { [AC97_REC_MIC] = SOUND_MIXER_MIC, [AC97_REC_CD] = SOUND_MIXER_CD, [AC97_REC_VIDEO] = SOUND_MIXER_VIDEO, [AC97_REC_AUX] = SOUND_MIXER_LINE1, [AC97_REC_LINE] = SOUND_MIXER_LINE, [AC97_REC_STEREO]= SOUND_MIXER_IGAIN, [AC97_REC_PHONE] = SOUND_MIXER_PHONEIN};/* indexed by bit position */static const unsigned int ac97_oss_rm[] = { [SOUND_MIXER_MIC] = AC97_REC_MIC, [SOUND_MIXER_CD] = AC97_REC_CD, [SOUND_MIXER_VIDEO] = AC97_REC_VIDEO, [SOUND_MIXER_LINE1] = AC97_REC_AUX, [SOUND_MIXER_LINE] = AC97_REC_LINE, [SOUND_MIXER_IGAIN] = AC97_REC_STEREO, [SOUND_MIXER_PHONEIN] = AC97_REC_PHONE};/* reads the given OSS mixer from the ac97 the caller must have insured that the ac97 knows about that given mixer, and should be holding a spinlock for the card */static int ac97_read_mixer(struct ac97_codec *codec, int oss_channel) { u16 val; int ret = 0; int scale; struct ac97_mixer_hw *mh = &ac97_hw[oss_channel]; val = codec->codec_read(codec , mh->offset); if (val & AC97_MUTE) { ret = 0; } else if (AC97_STEREO_MASK & (1 << oss_channel)) { /* nice stereo mixers .. */ int left,right; left = (val >> 8) & 0x7f; right = val & 0x7f; if (oss_channel == SOUND_MIXER_IGAIN) { right = (right * 100) / mh->scale; left = (left * 100) / mh->scale; } else { /* these may have 5 or 6 bit resolution */ if(oss_channel == SOUND_MIXER_VOLUME || oss_channel == SOUND_MIXER_ALTPCM) scale = (1 << codec->bit_resolution); else scale = mh->scale; right = 100 - ((right * 100) / scale); left = 100 - ((left * 100) / scale); } ret = left | (right << 8); } else if (oss_channel == SOUND_MIXER_SPEAKER) { ret = 100 - ((((val & 0x1e)>>1) * 100) / mh->scale); } else if (oss_channel == SOUND_MIXER_PHONEIN) { ret = 100 - (((val & 0x1f) * 100) / mh->scale); } else if (oss_channel == SOUND_MIXER_PHONEOUT) { scale = (1 << codec->bit_resolution); ret = 100 - (((val & 0x1f) * 100) / scale); } else if (oss_channel == SOUND_MIXER_MIC) { ret = 100 - (((val & 0x1f) * 100) / mh->scale); /* the low bit is optional in the tone sliders and masking it lets us avoid the 0xf 'bypass'.. */ } else if (oss_channel == SOUND_MIXER_BASS) { ret = 100 - ((((val >> 8) & 0xe) * 100) / mh->scale); } else if (oss_channel == SOUND_MIXER_TREBLE) { ret = 100 - (((val & 0xe) * 100) / mh->scale); }#ifdef DEBUG printk("ac97_codec: read OSS mixer %2d (%s ac97 register 0x%02x), " "0x%04x -> 0x%04x\n", oss_channel, codec->id ? "Secondary" : "Primary", mh->offset, val, ret);#endif return ret;}/* write the OSS encoded volume to the given OSS encoded mixer, again caller's job to make sure all is well in arg land, call with spinlock held */static void ac97_write_mixer(struct ac97_codec *codec, int oss_channel, unsigned int left, unsigned int right){ u16 val = 0; int scale; struct ac97_mixer_hw *mh = &ac97_hw[oss_channel];#ifdef DEBUG printk("ac97_codec: wrote OSS mixer %2d (%s ac97 register 0x%02x), " "left vol:%2d, right vol:%2d:", oss_channel, codec->id ? "Secondary" : "Primary", mh->offset, left, right);#endif if (AC97_STEREO_MASK & (1 << oss_channel)) { /* stereo mixers */ if (left == 0 && right == 0) { val = AC97_MUTE; } else { if (oss_channel == SOUND_MIXER_IGAIN) { right = (right * mh->scale) / 100; left = (left * mh->scale) / 100; if (right >= mh->scale) right = mh->scale-1; if (left >= mh->scale) left = mh->scale-1;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -