RFC: big ac97_codec audio update

From: Jeff Garzik (jgarzik@mandrakesoft.com)
Date: Thu Oct 19 2000 - 14:26:06 EST


It's a big update, but I think it is necessary.
We need the new codec-specific init functions.
We need the new ac97-valid-reg checking.
We need the dynamic bit resolution detection.

Full change description, and tested patch against 2.4.0-test10-pre4,
follows. This includes some interface changes that may be
objectionable, but I consider it to be fixing up an interface before
final 2.4.0 release...

Comments and feedback very much appreciated. This code is tested on via
audio.

Judging from comments already received, it looks like we need to do
logarithmic instead of linear scaling for the OSS mixer values (0 -
100).

This change affects a bunch of drivers, and needs lots of testing:
maestro (when patched to use ac97_codec), trident, via, ymfpci, nm256
(when patched), and others I am probably forgetting.

(Linus, this is CC'd as an FYI, not to be applied...)

        Jeff

-- 
Jeff Garzik                    | The difference between laziness and
Building 1024                  | prioritization is the end result.
MandrakeSoft                   |

Improvements for the kernel AC97 codec support

++ AC97 codec vendor/device info table

* Add some new codec ids from ALSA * Merge wolfson_init and tritech_init from ALSA * Numerically sort by codec id

++ New codec read/write interface

* Imported check for valid AC97 registers from ALSA (hopefully solves problems w/ AK codecs) * Add functions ac97_codec_{read,write}, which first check for a valid register, and then proceed with the register read/write. * Use ac97_codec_{read,write} throughout the code. * Export ac97_codec_{read,write} to the world. * Note - The old interface continues to work just fine, you just don't have valid-reg checking.

++ Mixer channel initialization

* Auto-detect channel existence, and bit resolution. w00t! * New method of using codec->supported_mixers: fill it in for a custom mask, or set to zero to autodetect all responding mixer channels. * Using the register bit info table in the AC97 specification, populate new ac97_hw[] fields 'offset', 'stereo', 'mute', and 'bits'. * Remove mixer_defaults[], no longer needed. * ac97_def_mix_val is now used instead of mixer_defaults[] to set a global default mixer value. If you want to set per-channel mixer defaults, change ac97_hw[x].def_mix_val from zero to the desired number.

++ Mixer channel read / write

* Use bit resolution detected in mixer init step to always properly scale mixer channels, regardless of register size. * Use 'offset', 'stereo' data from ac97_hw[] for ease of ac97 register reads. * Use 'mute' data from ac97_hw[] for proper muting control * Bug fix: preserve, not trample, the Mic loudness bit * Export ac97_read_mixer to the world. ac97_write_mixer was exported, so why not this too. * Because ac97_write_mixer was already exported, and ac97_read_mixer now is, delete ac97_codec::{read,write}_mixer. (breaks trident build I think, but easy fix)

++ AC97 modem stuff gone

* It seemed like a half-hearted attempt. The -only- modem related code that existed was a check for audio/modem codec (which was output with printk), and an unused modem_ioctl member of struct ac97_codec. Gone.

++ Miscellaneous

* Provide URLs for AC97 specification.

++ Code cleanup

* Remove unnecessary function prototypes. * Use ARRAY_SIZE macro from kernel.h, not non-standard arraysize * Const-ify ac97_rm2oss[], ac97_oss_rm[] * Const-ify ac97_codec::name. * Use 'u32' type for those members that we bitmask. * Move mixer_state into a new ac97_mixer_entry structure * Fix up debug code to be correct for the above changes, add some debug code for the new resolution autodetect stuff. * Add comments to some members of struct ac97_codec.

Index: include/linux/ac97_codec.h =================================================================== RCS file: /g/cvslan/linux_2_3/include/linux/Attic/ac97_codec.h,v retrieving revision 1.1.1.8 retrieving revision 1.1.1.8.16.2 diff -u -r1.1.1.8 -r1.1.1.8.16.2 --- include/linux/ac97_codec.h 2000/10/02 08:25:59 1.1.1.8 +++ include/linux/ac97_codec.h 2000/10/19 04:20:12 1.1.1.8.16.2 @@ -135,14 +135,20 @@ #define supported_mixer(CODEC,FOO) ((CODEC)->supported_mixers & (1<<FOO) ) +struct ac97_mixer_entry { + unsigned int bits : 4; /* bit resolution of mixer register */ + unsigned int mixer_state; /* saved OSS mixer state */ +}; + struct ac97_codec { /* AC97 controller connected with */ void *private_data; - char *name; - int id; - int dev_mixer; - int type; + const char *name; /* codec vendor+device name */ + int id; /* codec id, 0==primary, 1==secondary, etc. */ + int dev_mixer; /* Mixer device minor, from register_sound_mixer() */ + u32 type; /* AC97 codec vendor+device id */ + int idx; /* index into ac97_codec_id[] array */ /* codec specific init/reset routines, used mainly for 4 or 6 channel support */ int (*codec_init) (struct ac97_codec *codec); @@ -156,28 +162,28 @@ /* OSS mixer masks */ int modcnt; - int supported_mixers; - int stereo_mixers; - int record_sources; + u32 supported_mixers; + u32 stereo_mixers; + u32 record_sources; - int bit_resolution; - /* OSS mixer interface */ - int (*read_mixer) (struct ac97_codec *codec, int oss_channel); - void (*write_mixer)(struct ac97_codec *codec, int oss_channel, - unsigned int left, unsigned int right); int (*recmask_io) (struct ac97_codec *codec, int rw, int mask); int (*mixer_ioctl)(struct ac97_codec *codec, unsigned int cmd, unsigned long arg); /* saved OSS mixer states */ - unsigned int mixer_state[SOUND_MIXER_NRDEVICES]; - - /* Software Modem interface */ - int (*modem_ioctl)(struct ac97_codec *codec, unsigned int cmd, unsigned long arg); + struct ac97_mixer_entry mixer[SOUND_MIXER_NRDEVICES]; }; extern int ac97_read_proc (char *page_out, char **start, off_t off, int count, int *eof, void *data); -extern int ac97_probe_codec(struct ac97_codec *); +extern int ac97_probe_codec(struct ac97_codec *codec); + +extern int ac97_read_mixer(struct ac97_codec *codec, int oss_channel); +extern void ac97_write_mixer(struct ac97_codec *codec, int oss_channel, + unsigned int left, unsigned int right); + +u16 ac97_codec_read (struct ac97_codec *codec, u8 reg); +void ac97_codec_write (struct ac97_codec *codec, u8 reg, u16 val); + #endif /* _AC97_CODEC_H_ */ Index: drivers/sound/ac97_codec.c =================================================================== RCS file: /g/cvslan/linux_2_3/drivers/sound/ac97_codec.c,v retrieving revision 1.1.1.20 retrieving revision 1.1.1.20.6.2 diff -u -r1.1.1.20 -r1.1.1.20.6.2 --- drivers/sound/ac97_codec.c 2000/10/12 21:49:20 1.1.1.20 +++ drivers/sound/ac97_codec.c 2000/10/19 04:20:09 1.1.1.20.6.2 @@ -20,18 +20,30 @@ * 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/audio/ac97r21.pdf + * + ************************************************************************** + * * History + * v0.5 Oct 15 2000 Jeff Garzik + * Codec init sequence update, detect bit resolution. * 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> + * 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> @@ -40,50 +52,55 @@ #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); +#undef DEBUG 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 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; +/* sorted by vendor/device id */ +static const struct { + u32 id; + const 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}, + {0x41445303, "Analog Devices AD1819", NULL}, + {0x41445340, "Analog Devices AD1881", NULL}, + {0x41445348, "Analog Devices AD1881A", NULL}, + {0x41445460, "Analog Devices AD1885", enable_eapd}, + {0x414B4D00, "Asahi Kasei AK4540", NULL}, + {0x414B4D01, "Asahi Kasei AK4542", NULL}, + {0x414B4D02, "Asahi Kasei AK4543", NULL}, + {0x414C4710, "ALC200/200P", NULL}, + {0x43525900, "Cirrus Logic CS4297", NULL}, + {0x43525903, "Cirrus Logic CS4297", NULL}, + {0x43525913, "Cirrus Logic CS4297A rev A", NULL}, + {0x43525914, "Cirrus Logic CS4297A rev B", NULL}, + {0x43525923, "Cirrus Logic CS4298", NULL}, + {0x4352592B, "Cirrus Logic CS4294", NULL}, + {0x43525931, "Cirrus Logic CS4299 rev A", NULL}, + {0x43525933, "Cirrus Logic CS4299 rev C", NULL}, + {0x43525934, "Cirrus Logic CS4299 rev D", NULL}, + {0x45838308, "ESS Allegro ES1988", NULL}, + {0x4e534331, "National Semiconductor LM4549", NULL}, + {0x53494c22, "Silicon Laboratory Si3036", NULL}, + {0x53494c23, "Silicon Laboratory Si3038", NULL}, + {0x54524103, "TriTech TR?????", NULL}, + {0x54524106, "TriTech TR28026", NULL}, + {0x54524108, "TriTech TR28028", tritech_init}, + {0x54524123, "TriTech TR?????", NULL}, + {0x574D4C00, "Wolfson WM9704", wolfson_init}, + {0x574D4C03, "Wolfson WM9703/9704", NULL}, + {0x574D4C04, "Wolfson WM9704 (quad)", 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} + {0x83847605, "SigmaTel STAC9704", NULL}, + {0x83847608, "SigmaTel STAC9708", NULL}, + {0x83847609, "SigmaTel STAC9721/23", sigmatel_init}, + {0,} }; static const char *ac97_stereo_enhancements[] = @@ -122,48 +139,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, 0x4343}, - {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} -}; +static const int ac97_def_mix_val = 0x4343; -/* table to scale scale from OSS mixer value to AC97 mixer register value */ -static struct ac97_mixer_hw { - unsigned char offset; - int scale; +static const struct ac97_mixer_hw { + unsigned char reg; /* AC97 mixer register */ + int offset; /* bit offset from start of register to value */ + int def_mix_val; /* 0 == use 'ac97_def_mix_val', >0, use this val */ + int stereo : 1; /* 1 == stereo (total_res == bit_res * 2) */ + int mute : 1; /* 1 == can use bit 15 (mute) */ + int bits; /* maximum bit resolution (from AC97 docs) */ } 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}, + [SOUND_MIXER_VOLUME] = {AC97_MASTER_VOL_STEREO, 0, 0, 1, 1, 6}, + [SOUND_MIXER_BASS] = {AC97_MASTER_TONE, 8, 0, 0, 0, 4}, + [SOUND_MIXER_TREBLE] = {AC97_MASTER_TONE, 0, 0, 0, 0, 4}, + [SOUND_MIXER_PCM] = {AC97_PCMOUT_VOL, 0, 0, 1, 1, 5}, + [SOUND_MIXER_SPEAKER] = {AC97_PCBEEP_VOL, 1, 0, 0, 0, 4}, + [SOUND_MIXER_LINE] = {AC97_LINEIN_VOL, 0, 0, 1, 1, 5}, + [SOUND_MIXER_MIC] = {AC97_MIC_VOL, 0, 0, 0, 1, 5}, + [SOUND_MIXER_CD] = {AC97_CD_VOL, 0, 0, 1, 1, 5}, + [SOUND_MIXER_ALTPCM] = {AC97_HEADPHONE_VOL, 0, 0, 1, 1, 6}, + [SOUND_MIXER_IGAIN] = {AC97_RECORD_GAIN, 0, 0, 1, 1, 4}, + [SOUND_MIXER_LINE1] = {AC97_AUX_VOL, 0, 0, 1, 1, 5}, + [SOUND_MIXER_PHONEIN] = {AC97_PHONE_VOL, 0, 0, 0, 1, 5}, + [SOUND_MIXER_PHONEOUT] = {AC97_MASTER_VOL_MONO, 0, 0, 0, 1, 6}, + [SOUND_MIXER_VIDEO] = {AC97_VIDEO_VOL, 0, 0, 1, 1, 5}, }; /* the following tables allow us to go from OSS <-> ac97 quickly. */ @@ -175,10 +174,10 @@ AC97_REC_LINE, AC97_REC_STEREO, /* combination of all enabled outputs.. */ AC97_REC_MONO, /*.. or the mono equivalent */ - AC97_REC_PHONE + AC97_REC_PHONE }; -static unsigned int ac97_rm2oss[] = { +static const unsigned int ac97_rm2oss[] = { [AC97_REC_MIC] = SOUND_MIXER_MIC, [AC97_REC_CD] = SOUND_MIXER_CD, [AC97_REC_VIDEO] = SOUND_MIXER_VIDEO, @@ -189,7 +188,7 @@ }; /* indexed by bit position */ -static unsigned int ac97_oss_rm[] = { +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, @@ -199,62 +198,88 @@ [SOUND_MIXER_PHONEIN] = AC97_REC_PHONE }; + +static int ac97_valid_reg (struct ac97_codec *codec, u8 reg) +{ + switch (codec->type) { + case 0x414B4D00: /* AK4540 */ + case 0x414B4D01: /* AK4542 */ + if (reg <= 0x1c || reg == 0x20 || reg == 0x26 || reg >= 0x7c) + return 1; + return 0; + case 0x41445303: /* AD1819 */ + case 0x41445340: /* AD1881 */ + case 0x41445348: /* AD1881A */ + case 0x41445460: /* AD1885 */ + if (reg >= 0x3a && reg <= 0x59) + return 0; + return 1; + } + return 1; +} + + +u16 ac97_codec_read (struct ac97_codec *codec, u8 reg) +{ + if (!ac97_valid_reg(codec, reg) || !codec->codec_read) + return 0; + return codec->codec_read (codec, reg); +} + + +void ac97_codec_write (struct ac97_codec *codec, u8 reg, u16 val) +{ + if (!ac97_valid_reg(codec, reg) || !codec->codec_write) + return; + codec->codec_write (codec, reg, val); +} + + /* 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) +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]; + const struct ac97_mixer_hw *mh = &ac97_hw[oss_channel]; - val = codec->codec_read(codec , mh->offset); + val = ac97_codec_read(codec , mh->reg); - if (val & AC97_MUTE) { + /* muted channels are easy */ + if (mh->mute && (val & AC97_MUTE)) ret = 0; - } else if (AC97_STEREO_MASK & (1 << oss_channel)) { - /* nice stereo mixers .. */ + + /* stereo channels */ + else if (mh->stereo) { int left,right; + if (mh->offset > 0) + BUG(); + 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; + scale = (1 << codec->mixer[oss_channel].bits); - right = 100 - ((right * 100) / scale); - left = 100 - ((left * 100) / 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); + } + + /* mono channels */ + else { + scale = (1 << codec->mixer[oss_channel].bits); + val >>= mh->offset; 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); + mh->reg, val, ret); #endif return ret; @@ -262,76 +287,76 @@ /* 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, +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]; + const struct ac97_mixer_hw *mh = &ac97_hw[oss_channel]; #ifdef DEBUG - printk("ac97_codec: wrote OSS mixer %2d (%s ac97 register 0x%02x), " + printk("ac97_codec: wrote OSS mixer %2d (%s ac97 register 0x%02x bitres %d), " "left vol:%2d, right vol:%2d:", oss_channel, codec->id ? "Secondary" : "Primary", - mh->offset, left, right); + mh->reg, codec->mixer[oss_channel].bits, left, right); #endif + + scale = (1 << codec->mixer[oss_channel].bits); + + /* mic vol has a special bit to preserve, special case */ + if (oss_channel == SOUND_MIXER_MIC) { + val = ac97_codec_read(codec, mh->reg) & (1<<6); + if (left == 0) + val |= AC97_MUTE; + else + val |= (((100 - left) * scale) / 100); + } - if (AC97_STEREO_MASK & (1 << oss_channel)) { - /* stereo mixers */ - if (left == 0 && right == 0) { + /* bass shares a register with treble, special case */ + else if (oss_channel == SOUND_MIXER_BASS) { + val = ac97_codec_read(codec, mh->reg) & 0x000F; + val |= ((((100 - left) * scale) / 100) << 8) & 0x0E00; + } + + /* treble shares a register with bass, special case */ + else if (oss_channel == SOUND_MIXER_TREBLE) { + val = ac97_codec_read(codec, mh->reg) & 0x0F00; + val |= (((100 - left) * scale) / 100) & 0x000E; + } + + /* handle stereo channel */ + else if (mh->stereo) { + 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; - } 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; - } + else { + right = ((100 - right) * scale) / 100; + left = ((100 - left) * scale) / 100; val = (left << 8) | right; } - } else if (oss_channel == SOUND_MIXER_BASS) { - val = codec->codec_read(codec , mh->offset) & ~0x0f00; - val |= ((((100 - left) * mh->scale) / 100) << 8) & 0x0e00; - } else if (oss_channel == SOUND_MIXER_TREBLE) { - val = codec->codec_read(codec , mh->offset) & ~0x000f; - val |= (((100 - left) * mh->scale) / 100) & 0x000e; - } else if(left == 0) { - val = AC97_MUTE; - } else if (oss_channel == SOUND_MIXER_SPEAKER) { - val = (((100 - left) * mh->scale) / 100) << 1; - } else if (oss_channel == SOUND_MIXER_PHONEIN) { - val = (((100 - left) * mh->scale) / 100); - } else if (oss_channel == SOUND_MIXER_PHONEOUT) { - scale = (1 << codec->bit_resolution); - val = (((100 - left) * scale) / 100); - } else if (oss_channel == SOUND_MIXER_MIC) { - val = codec->codec_read(codec , mh->offset) & ~0x801f; - val |= (((100 - left) * mh->scale) / 100); - /* the low bit is optional in the tone sliders and masking - it lets us avoid the 0xf 'bypass'.. */ } + + /* handle mono channel */ + else { + if (left == 0) + val = AC97_MUTE; + else + val = (((100 - left) * scale) / 100); + } + #ifdef DEBUG printk(" 0x%04x", val); #endif - codec->codec_write(codec, mh->offset, val); + ac97_codec_write(codec, mh->reg, val); #ifdef DEBUG - val = codec->codec_read(codec, mh->offset); + val = ac97_codec_read(codec, mh->reg); 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 ) +static void ac97_set_mixer(struct ac97_codec *codec, unsigned int oss_mixer, unsigned int val) { unsigned int left,right; @@ -342,21 +367,21 @@ 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); + codec->mixer[oss_mixer].mixer_state = (right << 8) | left; + ac97_write_mixer(codec, oss_mixer, left, right); } /* read or write the recmask, the ac97 can really have left and right recording inputs independantly set, but OSS doesn't seem to want us to express that to the user. the caller guarantees that we have a supported bit set, and they must be holding the card's spinlock */ -static int ac97_recmask_io(struct ac97_codec *codec, int rw, int mask) +static int ac97_recmask_io(struct ac97_codec *codec, int rw, int mask) { unsigned int val; if (rw) { /* read it from the card */ - val = codec->codec_read(codec, AC97_RECORD_SELECT); + val = ac97_codec_read(codec, AC97_RECORD_SELECT); #ifdef DEBUG printk("ac97_codec: ac97 recmask to set to 0x%04x\n", val); #endif @@ -364,13 +389,13 @@ } /* else, write the first set in the mask as the - output */ + output */ /* clear out current set value first (AC97 supports only 1 input!) */ - val = (1 << ac97_rm2oss[codec->codec_read(codec, AC97_RECORD_SELECT) & 0x07]); + val = (1 << ac97_rm2oss[ac97_codec_read(codec, AC97_RECORD_SELECT) & 0x07]); if (mask != val) mask &= ~val; - - val = ffs(mask); + + val = ffs(mask); val = ac97_oss_rm[val-1]; val |= val << 8; /* set both channels */ @@ -378,7 +403,7 @@ printk("ac97_codec: setting ac97 recmask to 0x%04x\n", val); #endif - codec->codec_write(codec, AC97_RECORD_SELECT, val); + ac97_codec_write(codec, AC97_RECORD_SELECT, val); return 0; }; @@ -440,12 +465,12 @@ default: /* read a specific mixer */ i = _IOC_NR(cmd); - if (!supported_mixer(codec, i)) + if (!supported_mixer(codec, i)) return -EINVAL; /* do we ever want to touch the hardware? */ - /* val = codec->read_mixer(codec, i); */ - val = codec->mixer_state[i]; + /* val = ac97_read_mixer(codec, i); */ + val = codec->mixer[i].mixer_state; break; } return put_user(val, (int *)arg); @@ -468,7 +493,7 @@ default: /* write a specific mixer */ i = _IOC_NR(cmd); - if (!supported_mixer(codec, i)) + if (!supported_mixer(codec, i)) return -EINVAL; ac97_set_mixer(codec, i, val); @@ -490,18 +515,18 @@ if ((codec = data) == NULL) return -ENODEV; - id1 = codec->codec_read(codec, AC97_VENDOR_ID1); - id2 = codec->codec_read(codec, AC97_VENDOR_ID2); + id1 = ac97_codec_read(codec, AC97_VENDOR_ID1); + id2 = ac97_codec_read(codec, AC97_VENDOR_ID2); len += sprintf (page+len, "Vendor name : %s\n", codec->name); len += sprintf (page+len, "Vendor id : %04X %04X\n", id1, id2); - extid = codec->codec_read(codec, AC97_EXTENDED_ID); + extid = ac97_codec_read(codec, AC97_EXTENDED_ID); extid &= ~((1<<2)|(1<<4)|(1<<5)|(1<<10)|(1<<11)|(1<<12)|(1<<13)); len += sprintf (page+len, "AC97 Version : %s\n", extid ? "2.0 or later" : "1.0"); if (extid) is_ac97_20 = 1; - cap = codec->codec_read(codec, AC97_RESET); + cap = ac97_codec_read(codec, AC97_RESET); len += sprintf (page+len, "Capabilities :%s%s%s%s%s%s\n", cap & 0x0001 ? " -dedicated MIC PCM IN channel-" : "", cap & 0x0002 ? " -reserved1-" : "", @@ -522,7 +547,7 @@ len += sprintf (page+len, "3D enhancement : %s\n", ac97_stereo_enhancements[(cap >> 10) & 0x1f]); - val = codec->codec_read(codec, AC97_GENERAL_PURPOSE); + val = ac97_codec_read(codec, AC97_GENERAL_PURPOSE); len += sprintf (page+len, "POP path : %s 3D\n" "Sim. stereo : %s\n" "3D enhancement : %s\n" @@ -538,7 +563,7 @@ val & 0x0100 ? "MIC2" : "MIC1", val & 0x0080 ? "on" : "off"); - extid = codec->codec_read(codec, AC97_EXTENDED_ID); + extid = ac97_codec_read(codec, AC97_EXTENDED_ID); cap = extid; len += sprintf (page+len, "Ext Capabilities :%s%s%s%s%s%s%s\n", cap & 0x0001 ? " -var rate PCM audio-" : "", @@ -550,7 +575,7 @@ cap & 0x0200 ? " -slot/DAC mappings-" : ""); if (is_ac97_20) { len += sprintf (page+len, "Front DAC rate : %d\n", - codec->codec_read(codec, AC97_PCM_FRONT_DAC_RATE)); + ac97_codec_read(codec, AC97_PCM_FRONT_DAC_RATE)); } return len; @@ -574,48 +599,42 @@ * codec-ready state. If codec_wait is %NULL, then * the default behavior is to call schedule_timeout. * Currently codec_wait is used to wait for AC97 codec - * reset to complete. + * reset to complete. * * Returns 1 (true) on success, or 0 (false) on failure. */ - + int ac97_probe_codec(struct ac97_codec *codec) { u16 id1, id2; - u16 audio, modem; int i; - /* probing AC97 codec, AC97 2.0 says that bit 15 of register 0x00 (reset) should - * be read zero. - * - * FIXME: is the following comment outdated? -jgarzik - * Probing of AC97 in this way is not reliable, it is not even SAFE !! - */ - codec->codec_write(codec, AC97_RESET, 0L); + ac97_codec_write(codec, AC97_RESET, 0L); - /* also according to spec, we wait for codec-ready state */ + /* according to spec, we wait for codec-ready state */ if (codec->codec_wait) codec->codec_wait(codec); - else - udelay(10); + else { + current->state = TASK_UNINTERRUPTIBLE; + schedule_timeout((5*HZ)/100); + } - if ((audio = codec->codec_read(codec, AC97_RESET)) & 0x8000) { - printk(KERN_ERR "ac97_codec: %s ac97 codec not present\n", - codec->id ? "Secondary" : "Primary"); + /* spec sez, we can always mute Master Vol */ + ac97_codec_write(codec, AC97_MASTER_VOL_STEREO, 0x8000); + i = ac97_codec_read(codec, AC97_MASTER_VOL_STEREO); + if (i != 0x8000) { + printk(KERN_ERR "AC97 codec does not respond (0x%04X)\n", i); return 0; } - /* probe for Modem Codec */ - codec->codec_write(codec, AC97_EXTENDED_MODEM_ID, 0L); - modem = codec->codec_read(codec, AC97_EXTENDED_MODEM_ID); - codec->name = NULL; codec->codec_init = NULL; - id1 = codec->codec_read(codec, AC97_VENDOR_ID1); - id2 = codec->codec_read(codec, AC97_VENDOR_ID2); - for (i = 0; i < arraysize(ac97_codec_ids); i++) { + id1 = ac97_codec_read(codec, AC97_VENDOR_ID1); + id2 = ac97_codec_read(codec, AC97_VENDOR_ID2); + for (i = 0; i < ARRAY_SIZE(ac97_codec_ids); i++) { if (ac97_codec_ids[i].id == ((id1 << 16) | id2)) { + codec->idx = i; codec->type = ac97_codec_ids[i].id; codec->name = ac97_codec_ids[i].name; codec->codec_init = ac97_codec_ids[i].init; @@ -624,9 +643,8 @@ } if (codec->name == NULL) codec->name = "Unknown"; - printk(KERN_INFO "ac97_codec: AC97 %s codec, id: 0x%04x:" - "0x%04x (%s)\n", audio ? "Audio" : (modem ? "Modem" : ""), - id1, id2, codec->name); + printk(KERN_INFO "ac97_codec: AC97 codec, id: 0x%04x:" + "0x%04x (%s)\n", id1, id2, codec->name); return ac97_init_mixer(codec); } @@ -634,45 +652,95 @@ static int ac97_init_mixer(struct ac97_codec *codec) { u16 cap; + u32 val; int i; - cap = codec->codec_read(codec, AC97_RESET); + cap = ac97_codec_read(codec, AC97_RESET); /* mixer masks */ - codec->supported_mixers = AC97_SUPPORTED_MASK; codec->stereo_mixers = AC97_STEREO_MASK; codec->record_sources = AC97_RECORD_MASK; + + val = 0; + for (i = SOUND_MIXER_NRDEVICES; i >= 0; i--) + val |= (1 << i); + + if (codec->supported_mixers == 0) /* 0 == autodetect */ + codec->supported_mixers = val; if (!(cap & 0x04)) codec->supported_mixers &= ~(SOUND_MASK_BASS|SOUND_MASK_TREBLE); if (!(cap & 0x10)) codec->supported_mixers &= ~SOUND_MASK_ALTPCM; - /* detect bit resolution */ - codec->codec_write(codec, AC97_MASTER_VOL_STEREO, 0x2020); - if(codec->codec_read(codec, AC97_MASTER_VOL_STEREO) == 0x1f1f) - codec->bit_resolution = 5; - else - codec->bit_resolution = 6; - /* generic OSS to AC97 wrapper */ - codec->read_mixer = ac97_read_mixer; - codec->write_mixer = ac97_write_mixer; codec->recmask_io = ac97_recmask_io; codec->mixer_ioctl = ac97_mixer_ioctl; /* codec specific initialization for 4-6 channel output or secondary codec stuff */ - if (codec->codec_init != NULL) { - codec->codec_init(codec); + if (codec->codec_init) { + i = codec->codec_init(codec); + if (!i) + return i; } - /* initilize mixer channel volumes */ + /* detect mixer channels, initilize mixer channel volumes */ for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { - struct mixer_defaults *md = &mixer_defaults[i]; - if (md->mixer == -1) - break; - if (!supported_mixer(codec, md->mixer)) + const struct ac97_mixer_hw *mhw = &ac97_hw[i]; + u16 orig, test; + +#ifdef DEBUG + { + char *oss_mix_names[] = SOUND_DEVICE_NAMES; + printk("ac97_codec: init OSS mixer %d (%s): ", i, oss_mix_names[i]); + } +#endif + + if (!supported_mixer(codec, i)) { +#ifdef DEBUG + printk("not supported\n"); +#endif continue; - ac97_set_mixer(codec, md->mixer, md->value); + } + + if (!mhw->reg) { +#ifdef DEBUG + printk("not supported by AC97\n"); +#endif + codec->supported_mixers &= ~(1 << i); + continue; + } + + /* detect mixer channel existence and bit resolution */ + orig = ac97_codec_read(codec, mhw->reg); + ac97_codec_write(codec, mhw->reg, 0x3F); + test = ac97_codec_read(codec, mhw->reg); + ac97_codec_write(codec, mhw->reg, orig); + + if (test) { + int bitcnt = 1; + + while ((bitcnt < mhw->bits) && ((1 << bitcnt) & test)) + bitcnt++; + codec->mixer[i].bits = bitcnt; + +#ifdef DEBUG + printk("bit res. %d\n", bitcnt); +#endif + + if (mhw->def_mix_val) + ac97_set_mixer(codec, i, mhw->def_mix_val); + else + ac97_set_mixer(codec, i, ac97_def_mix_val); + + codec->supported_mixers |= (1 << i); + } else { +#ifdef DEBUG + printk("mixer read failed, ignoring\n"); +#endif + + codec->mixer[i].bits = 0; + codec->supported_mixers &= ~(1 << i); + } } return 1; @@ -684,35 +752,67 @@ if (codec->id == 0) return 1; - codec->codec_write(codec, AC97_SURROUND_MASTER, 0L); + ac97_codec_write(codec, AC97_SURROUND_MASTER, 0L); /* initialize SigmaTel STAC9721/23 as secondary codec, decoding AC link sloc 3,4 = 0x01, slot 7,8 = 0x00, */ - codec->codec_write(codec, 0x74, 0x00); + ac97_codec_write(codec, 0x74, 0x00); /* we don't have the crystal when we are on an AMR card, so use BIT_CLK as our clock source. Write the magic word ABBA and read back to enable register 0x78 */ - codec->codec_write(codec, 0x76, 0xabba); - codec->codec_read(codec, 0x76); + ac97_codec_write(codec, 0x76, 0xabba); + ac97_codec_read(codec, 0x76); /* sync all the clocks*/ - codec->codec_write(codec, 0x78, 0x3802); + ac97_codec_write(codec, 0x78, 0x3802); + + return 1; +} + + +static int wolfson_init(struct ac97_codec * codec) +{ + ac97_codec_write(codec, 0x72, 0x0808); + ac97_codec_write(codec, 0x74, 0x0808); + // patch for DVD noise + ac97_codec_write(codec, 0x5a, 0x0200); + + // init vol as PCM vol + ac97_codec_write(codec, 0x70, + ac97_codec_read(codec, AC97_PCMOUT_VOL)); + + ac97_codec_write(codec, AC97_SURROUND_MASTER, 0x0000); + return 1; +} + + +static int tritech_init(struct ac97_codec * codec) +{ + ac97_codec_write(codec, 0x26, 0x0300); + ac97_codec_write(codec, 0x26, 0x0000); + ac97_codec_write(codec, AC97_SURROUND_MASTER, 0x0000); + ac97_codec_write(codec, AC97_RESERVED_3A, 0x0000); return 1; } + /* * Bring up an AD1885 */ - + static int enable_eapd(struct ac97_codec * codec) { - codec->codec_write(codec, AC97_POWER_CONTROL, - codec->codec_read(codec, AC97_POWER_CONTROL)|0x8000); - return 0; + ac97_codec_write(codec, AC97_POWER_CONTROL, + ac97_codec_read(codec, AC97_POWER_CONTROL)|0x8000); + return 1; } - + EXPORT_SYMBOL(ac97_read_proc); EXPORT_SYMBOL(ac97_probe_codec); +EXPORT_SYMBOL(ac97_read_mixer); +EXPORT_SYMBOL(ac97_write_mixer); +EXPORT_SYMBOL(ac97_codec_read); +EXPORT_SYMBOL(ac97_codec_write);

- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org Please read the FAQ at http://www.tux.org/lkml/



This archive was generated by hypermail 2b29 : Mon Oct 23 2000 - 21:00:15 EST