📄 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. * * 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 sigmatel_init(struct ac97_codec *codec);static int enable_eapd(struct ac97_codec *codec);#define arraysize(x) (sizeof(x)/sizeof((x)[0]))static struct { unsigned int id; char *name; int (*init) (struct ac97_codec *codec);} ac97_codec_ids[] = { {0x414B4D00, "Asahi Kasei AK4540 rev 0", NULL}, {0x414B4D01, "Asahi Kasei AK4540 rev 1", NULL}, {0x41445340, "Analog Devices AD1881" , NULL}, {0x41445360, "Analog Devices AD1885" , enable_eapd}, {0x43525900, "Cirrus Logic CS4297" , NULL}, {0x43525903, "Cirrus Logic CS4297" , NULL}, {0x43525913, "Cirrus Logic CS4297A" , NULL}, {0x43525923, "Cirrus Logic CS4298" , NULL}, {0x4352592B, "Cirrus Logic CS4294" , NULL}, {0x43525931, "Cirrus Logic CS4299" , NULL}, {0x43525934, "Cirrus Logic CS4299" , NULL}, {0x4e534331, "National Semiconductor LM4549" , NULL}, {0x53494c22, "Silicon Laboratory Si3036" , NULL}, {0x53494c23, "Silicon Laboratory Si3038" , NULL}, {0x83847600, "SigmaTel STAC????" , NULL}, {0x83847604, "SigmaTel STAC9701/3/4/5", NULL}, {0x83847605, "SigmaTel STAC9704" , NULL}, {0x83847608, "SigmaTel STAC9708" , NULL}, {0x83847609, "SigmaTel STAC9721/23" , sigmatel_init}, {0x54524103, "TriTech TR?????" , NULL}, {0x54524106, "TriTech TR28026" , NULL}, {0x54524108, "TriTech TR28028" , NULL}, {0x54524123, "TriTech TR?????" , NULL}, {0x574D4C00, "Wolfson WM9704" , NULL}, {0x00000000, NULL, NULL}};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 */ "Reserved 27", /* 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 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 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; } 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) * scale) / 100; left = ((100 - left) * scale) / 100; if (right >= scale) right = scale-1; if (left >= scale) left = scale-1; } val = (left << 8) | right; } } else if (oss_channel == SOUND_MIXER_BASS) { val = codec->codec_read(codec , mh->offset) & ~0x0f00; left = ((100 - left) * mh->scale) / 100; if (left >= mh->scale) left = mh->scale-1; val |= (left << 8) & 0x0e00; } else if (oss_channel == SOUND_MIXER_TREBLE) { val = codec->codec_read(codec , mh->offset) & ~0x000f; left = ((100 - left) * mh->scale) / 100; if (left >= mh->scale) left = mh->scale-1; val |= left & 0x000e; } else if(left == 0) { val = AC97_MUTE; } else if (oss_channel == SOUND_MIXER_SPEAKER) { left = ((100 - left) * mh->scale) / 100; if (left >= mh->scale) left = mh->scale-1; val = left << 1; } else if (oss_channel == SOUND_MIXER_PHONEIN) { left = ((100 - left) * mh->scale) / 100; if (left >= mh->scale) left = mh->scale-1; val = left; } else if (oss_channel == SOUND_MIXER_PHONEOUT) { scale = (1 << codec->bit_resolution); left = ((100 - left) * scale) / 100; if (left >= mh->scale) left = mh->scale-1; val = left; } else if (oss_channel == SOUND_MIXER_MIC) { val = codec->codec_read(codec , mh->offset) & ~0x801f; left = ((100 - left) * mh->scale) / 100; if (left >= mh->scale) left = mh->scale-1; val |= left; /* the low bit is optional in the tone sliders and masking it lets us avoid the 0xf 'bypass'.. */ }#ifdef DEBUG printk(" 0x%04x", val);#endif codec->codec_write(codec, mh->offset, val);#ifdef DEBUG val = codec->codec_read(codec, mh->offset); printk(" -> 0x%04x\n", val);#endif}/* a thin wrapper for write_mixer */static void ac97_set_mixer(struct ac97_codec *codec, unsigned int oss_mixer, unsigned int val ) { unsigned int left,right; /* cleanse input a little */ right = ((val >> 8) & 0xff) ; left = (val & 0xff) ; if (right > 100) right = 100; if (left > 100) left = 100; codec->mixer_state[oss_mixer] = (right << 8) | left; codec->write_mixer(codec, oss_mixer, left, right);}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -