diff options
| author | Jaroslav Kysela <perex@suse.cz> | 2005-03-11 10:57:50 +0100 |
|---|---|---|
| committer | Jaroslav Kysela <perex@suse.cz> | 2005-03-11 10:57:50 +0100 |
| commit | 4eaf05ae2cba578ce00d92c1c274d18d256bc919 (patch) | |
| tree | 64363fb653d0cd937a9af1dacfb016207d2e3fb6 | |
| parent | 72815e1fa1c279bf7bfe1c9a4f6c0a97a8320317 (diff) | |
[ALSA] AC97 wm9713 support
AC97 Codec
This patch series adds support for the WM9713/WM9714 family of AC97
codecs. This family is different from 'standard' AC97 codecs in that the
default codec power state is 'off'. i.e. performing a register reset
will power the device down.
This patch also adds better support for larger single/double channel
enumerated mixer types.
Signed-off-by: Liam Girdwood <liam.girdwood@wolfsonmicro.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
| -rw-r--r-- | include/sound/ac97_codec.h | 14 | ||||
| -rw-r--r-- | sound/pci/ac97/ac97_codec.c | 279 | ||||
| -rw-r--r-- | sound/pci/ac97/ac97_local.h | 16 | ||||
| -rw-r--r-- | sound/pci/ac97/ac97_patch.c | 130 | ||||
| -rw-r--r-- | sound/pci/ac97/ac97_patch.h | 1 |
5 files changed, 290 insertions, 150 deletions
diff --git a/include/sound/ac97_codec.h b/include/sound/ac97_codec.h index d421d964cf63..a841033c1c76 100644 --- a/include/sound/ac97_codec.h +++ b/include/sound/ac97_codec.h @@ -366,6 +366,13 @@ #define AC97_DOUBLE_RATE (1<<5) /* supports double rate playback */ #define AC97_HAS_NO_MASTER_VOL (1<<6) /* no Master volume */ #define AC97_HAS_NO_PCM_VOL (1<<7) /* no PCM volume */ +#define AC97_DEFAULT_POWER_OFF (1<<8) /* no RESET write */ +#define AC97_MODEM_PATCH (1<<9) /* modem patch */ +#define AC97_HAS_NO_REC_GAIN (1<<10) /* no Record gain */ +#define AC97_HAS_NO_PHONE (1<<11) /* no PHONE volume */ +#define AC97_HAS_NO_PC_BEEP (1<<12) /* no PC Beep volume */ +#define AC97_HAS_NO_VIDEO (1<<13) /* no Video volume */ +#define AC97_HAS_NO_CD (1<<14) /* no CD volume */ /* rates indexes */ #define AC97_RATES_FRONT_DAC 0 @@ -580,4 +587,11 @@ int snd_ac97_pcm_open(struct ac97_pcm *pcm, unsigned int rate, int snd_ac97_pcm_close(struct ac97_pcm *pcm); int snd_ac97_pcm_double_rate_rules(snd_pcm_runtime_t *runtime); +struct ac97_enum { + unsigned char reg; + unsigned char shift_l; + unsigned char shift_r; + unsigned short mask; + const char **texts; +}; #endif /* __SOUND_AC97_CODEC_H */ diff --git a/sound/pci/ac97/ac97_codec.c b/sound/pci/ac97/ac97_codec.c index 78d370abcbd8..c2c9d0a5466c 100644 --- a/sound/pci/ac97/ac97_codec.c +++ b/sound/pci/ac97/ac97_codec.c @@ -56,6 +56,7 @@ typedef struct { 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[] = { @@ -162,6 +163,7 @@ static const ac97_codec_id_t snd_ac97_codec_ids[] = { { 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 }, @@ -442,108 +444,52 @@ static int snd_ac97_ad18xx_update_pcm_bits(ac97_t *ac97, int codec, unsigned sho * Controls */ -/* input mux */ -static int snd_ac97_info_mux(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +int snd_ac97_info_enum_double(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) { - static char *texts[8] = { - "Mic", "CD", "Video", "Aux", "Line", - "Mix", "Mix Mono", "Phone" - }; - + struct ac97_enum *e = (struct ac97_enum *)kcontrol->private_value; + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; - uinfo->count = 2; - uinfo->value.enumerated.items = 8; - if (uinfo->value.enumerated.item > 7) - uinfo->value.enumerated.item = 7; - strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]); - return 0; -} - -static int snd_ac97_get_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) -{ - ac97_t *ac97 = snd_kcontrol_chip(kcontrol); - unsigned short val; + uinfo->count = e->shift_l == e->shift_r ? 1 : 2; + uinfo->value.enumerated.items = e->mask; - val = snd_ac97_read_cache(ac97, AC97_REC_SEL); - ucontrol->value.enumerated.item[0] = (val >> 8) & 7; - ucontrol->value.enumerated.item[1] = (val >> 0) & 7; + if (uinfo->value.enumerated.item > e->mask - 1) + uinfo->value.enumerated.item = e->mask - 1; + strcpy(uinfo->value.enumerated.name, e->texts[uinfo->value.enumerated.item]); return 0; } -static int snd_ac97_put_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +int snd_ac97_get_enum_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) { ac97_t *ac97 = snd_kcontrol_chip(kcontrol); + struct ac97_enum *e = (struct ac97_enum *)kcontrol->private_value; unsigned short val; - if (ucontrol->value.enumerated.item[0] > 7 || - ucontrol->value.enumerated.item[1] > 7) - return -EINVAL; - val = (ucontrol->value.enumerated.item[0] << 8) | - (ucontrol->value.enumerated.item[1] << 0); - return snd_ac97_update(ac97, AC97_REC_SEL, val); -} - -/* standard stereo enums */ -#define AC97_ENUM_DOUBLE(xname, reg, shift, invert) \ -{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_ac97_info_enum_double, \ - .get = snd_ac97_get_enum_double, .put = snd_ac97_put_enum_double, \ - .private_value = reg | (shift << 8) | (invert << 24) } - -static int snd_ac97_info_enum_double(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) -{ - static char *texts1[2] = { "pre 3D", "post 3D" }; - static char *texts2[2] = { "Mix", "Mic" }; - static char *texts3[2] = { "Mic1", "Mic2" }; - char **texts = NULL; - int reg = kcontrol->private_value & 0xff; - int shift = (kcontrol->private_value >> 8) & 0xff; - - switch (reg) { - case AC97_GENERAL_PURPOSE: - switch (shift) { - case 15: texts = texts1; break; - case 9: texts = texts2; break; - case 8: texts = texts3; break; - } - } - uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; - uinfo->count = 1; - uinfo->value.enumerated.items = 2; - if (uinfo->value.enumerated.item > 1) - uinfo->value.enumerated.item = 1; - strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]); - return 0; -} + val = snd_ac97_read_cache(ac97, e->reg); + ucontrol->value.enumerated.item[0] = (val >> e->shift_l) & (e->mask - 1); + if (e->shift_l != e->shift_r) + ucontrol->value.enumerated.item[1] = (val >> e->shift_r) & (e->mask - 1); -static int snd_ac97_get_enum_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) -{ - ac97_t *ac97 = snd_kcontrol_chip(kcontrol); - unsigned short val; - int reg = kcontrol->private_value & 0xff; - int shift = (kcontrol->private_value >> 8) & 0xff; - int invert = (kcontrol->private_value >> 24) & 0xff; - - val = (snd_ac97_read_cache(ac97, reg) >> shift) & 1; - if (invert) - val ^= 1; - ucontrol->value.enumerated.item[0] = val; return 0; } -static int snd_ac97_put_enum_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +int snd_ac97_put_enum_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) { ac97_t *ac97 = snd_kcontrol_chip(kcontrol); + struct ac97_enum *e = (struct ac97_enum *)kcontrol->private_value; unsigned short val; - int reg = kcontrol->private_value & 0xff; - int shift = (kcontrol->private_value >> 8) & 0xff; - int invert = (kcontrol->private_value >> 24) & 0xff; + unsigned short mask; - if (ucontrol->value.enumerated.item[0] > 1) + if (ucontrol->value.enumerated.item[0] > e->mask - 1) return -EINVAL; - val = !!ucontrol->value.enumerated.item[0]; - if (invert) - val = !val; - return snd_ac97_update_bits(ac97, reg, 1 << shift, val << shift); + val = ucontrol->value.enumerated.item[0] << e->shift_l; + mask = (e->mask - 1) << e->shift_l; + if (e->shift_l != e->shift_r) { + if (ucontrol->value.enumerated.item[1] > e->mask - 1) + return -EINVAL; + val |= ucontrol->value.enumerated.item[1] << e->shift_r; + mask |= (e->mask - 1) << e->shift_r; + } + return snd_ac97_update_bits(ac97, e->reg, mask, val); } /* save/restore ac97 v2.3 paging */ @@ -635,11 +581,6 @@ int snd_ac97_put_volsw(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontro return err; } -#define AC97_DOUBLE(xname, reg, shift_left, shift_right, mask, invert) \ -{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), .info = snd_ac97_info_volsw, \ - .get = snd_ac97_get_volsw, .put = snd_ac97_put_volsw, \ - .private_value = (reg) | ((shift_left) << 8) | ((shift_right) << 12) | ((mask) << 16) | ((invert) << 24) } - static const snd_kcontrol_new_t snd_ac97_controls_master_mono[2] = { AC97_SINGLE("Master Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1), AC97_SINGLE("Master Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1) @@ -659,14 +600,21 @@ static const snd_kcontrol_new_t snd_ac97_controls_mic_boost = AC97_SINGLE("Mic Boost (+20dB)", AC97_MIC, 6, 1, 0); -static const snd_kcontrol_new_t snd_ac97_control_capture_src = { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "Capture Source", - .info = snd_ac97_info_mux, - .get = snd_ac97_get_mux, - .put = snd_ac97_put_mux, +static const char* std_rec_sel[] = {"Mic", "CD", "Video", "Aux", "Line", "Mix", "Mix Mono", "Phone"}; +static const char* std_3d_path[] = {"pre 3D", "post 3D"}; +static const char* std_mix[] = {"Mix", "Mic"}; +static const char* std_mic[] = {"Mic1", "Mic2"}; + +static const struct ac97_enum std_enum[] = { +AC97_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 8, std_rec_sel), +AC97_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, std_3d_path), +AC97_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 9, 2, std_mix), +AC97_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 8, 2, std_mic), }; +static const snd_kcontrol_new_t snd_ac97_control_capture_src = +AC97_ENUM("Capture Source", std_enum[0]); + static const snd_kcontrol_new_t snd_ac97_control_capture_vol = AC97_DOUBLE("Capture Volume", AC97_REC_GAIN, 8, 0, 15, 0); @@ -686,12 +634,12 @@ typedef enum { } ac97_general_index_t; static const snd_kcontrol_new_t snd_ac97_controls_general[7] = { -AC97_ENUM_DOUBLE("PCM Out Path & Mute", AC97_GENERAL_PURPOSE, 15, 0), +AC97_ENUM("PCM Out Path & Mute", std_enum[1]), AC97_SINGLE("Simulated Stereo Enhancement", AC97_GENERAL_PURPOSE, 14, 1, 0), AC97_SINGLE("3D Control - Switch", AC97_GENERAL_PURPOSE, 13, 1, 0), AC97_SINGLE("Loudness (bass boost)", AC97_GENERAL_PURPOSE, 12, 1, 0), -AC97_ENUM_DOUBLE("Mono Output Select", AC97_GENERAL_PURPOSE, 9, 0), -AC97_ENUM_DOUBLE("Mic Select", AC97_GENERAL_PURPOSE, 8, 0), +AC97_ENUM("Mono Output Select", std_enum[2]), +AC97_ENUM("Mic Select", std_enum[3]), AC97_SINGLE("ADC/DAC Loopback", AC97_GENERAL_PURPOSE, 7, 1, 0) }; @@ -1360,8 +1308,9 @@ static int snd_ac97_mixer_build(ac97_t * ac97) } /* build PC Speaker controls */ - if ((ac97->flags & AC97_HAS_PC_BEEP) || - snd_ac97_try_volume_mix(ac97, AC97_PC_BEEP)) { + if (!(ac97->flags & AC97_HAS_NO_PC_BEEP) && + ((ac97->flags & AC97_HAS_PC_BEEP) || + snd_ac97_try_volume_mix(ac97, AC97_PC_BEEP))) { for (idx = 0; idx < 2; idx++) if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_pc_beep[idx], ac97))) < 0) return err; @@ -1370,9 +1319,11 @@ static int snd_ac97_mixer_build(ac97_t * ac97) } /* build Phone controls */ - if (snd_ac97_try_volume_mix(ac97, AC97_PHONE)) { - if ((err = snd_ac97_cmix_new(card, "Phone Playback", AC97_PHONE, ac97)) < 0) - return err; + if (!(ac97->flags & AC97_HAS_NO_PHONE)) { + if (snd_ac97_try_volume_mix(ac97, AC97_PHONE)) { + if ((err = snd_ac97_cmix_new(card, "Phone Playback", AC97_PHONE, ac97)) < 0) + return err; + } } /* build MIC controls */ @@ -1390,15 +1341,19 @@ static int snd_ac97_mixer_build(ac97_t * ac97) } /* build CD controls */ - if (snd_ac97_try_volume_mix(ac97, AC97_CD)) { - if ((err = snd_ac97_cmix_new(card, "CD Playback", AC97_CD, ac97)) < 0) - return err; + if (!(ac97->flags & AC97_HAS_NO_CD)) { + if (snd_ac97_try_volume_mix(ac97, AC97_CD)) { + if ((err = snd_ac97_cmix_new(card, "CD Playback", AC97_CD, ac97)) < 0) + return err; + } } /* build Video controls */ - if (snd_ac97_try_volume_mix(ac97, AC97_VIDEO)) { - if ((err = snd_ac97_cmix_new(card, "Video Playback", AC97_VIDEO, ac97)) < 0) - return err; + if (!(ac97->flags & AC97_HAS_NO_VIDEO)) { + if (snd_ac97_try_volume_mix(ac97, AC97_VIDEO)) { + if ((err = snd_ac97_cmix_new(card, "Video Playback", AC97_VIDEO, ac97)) < 0) + return err; + } } /* build Aux controls */ @@ -1444,17 +1399,18 @@ static int snd_ac97_mixer_build(ac97_t * ac97) } /* build Capture controls */ - if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_control_capture_src, ac97))) < 0) - return err; - if (snd_ac97_try_bit(ac97, AC97_REC_GAIN, 15)) { - if ((err = snd_ac97_cmute_new(card, "Capture Switch", AC97_REC_GAIN, ac97)) < 0) + if (!(ac97->flags & AC97_HAS_NO_REC_GAIN)) { + if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_control_capture_src, ac97))) < 0) return err; + if (snd_ac97_try_bit(ac97, AC97_REC_GAIN, 15)) { + if ((err = snd_ac97_cmute_new(card, "Capture Switch", AC97_REC_GAIN, ac97)) < 0) + return err; + } + if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_control_capture_vol, ac97))) < 0) + return err; + snd_ac97_write_cache(ac97, AC97_REC_SEL, 0x0000); + snd_ac97_write_cache(ac97, AC97_REC_GAIN, 0x0000); } - if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_control_capture_vol, ac97))) < 0) - return err; - snd_ac97_write_cache(ac97, AC97_REC_SEL, 0x0000); - snd_ac97_write_cache(ac97, AC97_REC_GAIN, 0x0000); - /* build MIC Capture controls */ if (snd_ac97_try_volume_mix(ac97, AC97_REC_GAIN_MIC)) { for (idx = 0; idx < 2; idx++) @@ -1672,6 +1628,18 @@ static unsigned int snd_ac97_determine_spdif_rates(ac97_t *ac97) return result; } +/* look for the codec id table matching with the given id */ +static const ac97_codec_id_t *look_for_codec_id(const ac97_codec_id_t *table, + unsigned int id) +{ + const ac97_codec_id_t *pid; + + for (pid = table; pid->id; pid++) + if (pid->id == (id & pid->mask)) + return pid; + return NULL; +} + void snd_ac97_get_name(ac97_t *ac97, unsigned int id, char *name, int modem) { const ac97_codec_id_t *pid; @@ -1680,35 +1648,30 @@ void snd_ac97_get_name(ac97_t *ac97, unsigned int id, char *name, int modem) printable(id >> 24), printable(id >> 16), printable(id >> 8)); - for (pid = snd_ac97_codec_id_vendors; pid->id; pid++) - if (pid->id == (id & pid->mask)) { - strcpy(name, pid->name); - if (ac97) { - if (!modem && pid->patch) - pid->patch(ac97); - else if (modem && pid->mpatch) - pid->mpatch(ac97); - } - goto __vendor_ok; - } - return; + pid = look_for_codec_id(snd_ac97_codec_id_vendors, id); + if (! pid) + return; - __vendor_ok: - for (pid = snd_ac97_codec_ids; pid->id; pid++) - if (pid->id == (id & pid->mask)) { - strcat(name, " "); - strcat(name, pid->name); - if (pid->mask != 0xffffffff) - sprintf(name + strlen(name), " rev %d", id & ~pid->mask); - if (ac97) { - if (!modem && pid->patch) - pid->patch(ac97); - else if (modem && pid->mpatch) - pid->mpatch(ac97); - } - return; + strcpy(name, pid->name); + if (ac97 && pid->patch) { + if ((modem && (pid->flags & AC97_MODEM_PATCH)) || + (! modem && ! (pid->flags & AC97_MODEM_PATCH))) + pid->patch(ac97); + } + + pid = look_for_codec_id(snd_ac97_codec_ids, id); + if (pid) { + strcat(name, " "); + strcat(name, pid->name); + if (pid->mask != 0xffffffff) + sprintf(name + strlen(name), " rev %d", id & ~pid->mask); + if (ac97 && pid->patch) { + if ((modem && (pid->flags & AC97_MODEM_PATCH)) || + (! modem && ! (pid->flags & AC97_MODEM_PATCH))) + pid->patch(ac97); } - sprintf(name + strlen(name), " id %x", id & 0xff); + } else + sprintf(name + strlen(name), " id %x", id & 0xff); } /** @@ -1850,6 +1813,7 @@ int snd_ac97_mixer(ac97_bus_t *bus, ac97_template_t *template, ac97_t **rac97) char name[64]; unsigned long end_time; unsigned int reg; + const ac97_codec_id_t *pid; static snd_device_ops_t ops = { .dev_free = snd_ac97_dev_free, }; @@ -1900,6 +1864,14 @@ int snd_ac97_mixer(ac97_bus_t *bus, ac97_template_t *template, ac97_t **rac97) goto __access_ok; } + ac97->id = snd_ac97_read(ac97, AC97_VENDOR_ID1) << 16; + ac97->id |= snd_ac97_read(ac97, AC97_VENDOR_ID2); + if (ac97->id && ac97->id != (unsigned int)-1) { + pid = look_for_codec_id(snd_ac97_codec_ids, ac97->id); + if (pid && (pid->flags & AC97_DEFAULT_POWER_OFF)) + goto __access_ok; + } + snd_ac97_write(ac97, AC97_RESET, 0); /* reset to defaults */ if (bus->ops->wait) bus->ops->wait(ac97); @@ -1926,6 +1898,9 @@ int snd_ac97_mixer(ac97_bus_t *bus, ac97_template_t *template, ac97_t **rac97) snd_ac97_free(ac97); return -EIO; } + pid = look_for_codec_id(snd_ac97_codec_ids, ac97->id); + if (pid) + ac97->flags |= pid->flags; /* test for AC'97 */ if (!(ac97->scaps & AC97_SCAP_SKIP_AUDIO) && !(ac97->scaps & AC97_SCAP_AUDIO)) { @@ -1964,10 +1939,12 @@ int snd_ac97_mixer(ac97_bus_t *bus, ac97_template_t *template, ac97_t **rac97) if (ac97_is_audio(ac97)) { /* nothing should be in powerdown mode */ snd_ac97_write_cache(ac97, AC97_POWERDOWN, 0); - snd_ac97_write_cache(ac97, AC97_RESET, 0); /* reset to defaults */ - udelay(100); + if (! (ac97->flags & AC97_DEFAULT_POWER_OFF)) { + snd_ac97_write_cache(ac97, AC97_RESET, 0); /* reset to defaults */ + udelay(100); + snd_ac97_write_cache(ac97, AC97_POWERDOWN, 0); + } /* nothing should be in powerdown mode */ - snd_ac97_write_cache(ac97, AC97_POWERDOWN, 0); snd_ac97_write_cache(ac97, AC97_GENERAL_PURPOSE, 0); end_time = jiffies + (HZ / 10); do { @@ -2234,9 +2211,11 @@ void snd_ac97_resume(ac97_t *ac97) } snd_ac97_write(ac97, AC97_POWERDOWN, 0); - snd_ac97_write(ac97, AC97_RESET, 0); - udelay(100); - snd_ac97_write(ac97, AC97_POWERDOWN, 0); + if (! (ac97->flags & AC97_DEFAULT_POWER_OFF)) { + snd_ac97_write(ac97, AC97_RESET, 0); + udelay(100); + snd_ac97_write(ac97, AC97_POWERDOWN, 0); + } snd_ac97_write(ac97, AC97_GENERAL_PURPOSE, 0); snd_ac97_write(ac97, AC97_POWERDOWN, ac97->regs[AC97_POWERDOWN]); diff --git a/sound/pci/ac97/ac97_local.h b/sound/pci/ac97/ac97_local.h index fe41f9c3d818..536a4d4793af 100644 --- a/sound/pci/ac97/ac97_local.h +++ b/sound/pci/ac97/ac97_local.h @@ -32,6 +32,19 @@ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_ac97_info_volsw, \ .get = snd_ac97_get_volsw, .put = snd_ac97_put_volsw, \ .private_value = AC97_PAGE_SINGLE_VALUE(reg, shift, mask, invert, page) } +#define AC97_DOUBLE(xname, reg, shift_left, shift_right, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), .info = snd_ac97_info_volsw, \ + .get = snd_ac97_get_volsw, .put = snd_ac97_put_volsw, \ + .private_value = (reg) | ((shift_left) << 8) | ((shift_right) << 12) | ((mask) << 16) | ((invert) << 24) } +#define AC97_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmask, xtexts) \ +{ .reg = xreg, .shift_l = xshift_l, .shift_r = xshift_r, \ + .mask = xmask, .texts = xtexts } +#define AC97_ENUM_SINGLE(xreg, xshift, xmask, xtexts) \ + AC97_ENUM_DOUBLE(xreg, xshift, xshift, xmask, xtexts) +#define AC97_ENUM(xname, xenum) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_ac97_info_enum_double, \ + .get = snd_ac97_get_enum_double, .put = snd_ac97_put_enum_double, \ + .private_value = (unsigned long)&xenum } /* ac97_codec.c */ extern const char *snd_ac97_stereo_enhancements[]; @@ -49,6 +62,9 @@ int snd_ac97_swap_ctl(ac97_t *ac97, const char *s1, const char *s2, const char * void snd_ac97_rename_vol_ctl(ac97_t *ac97, const char *src, const char *dst); void snd_ac97_restore_status(ac97_t *ac97); void snd_ac97_restore_iec958(ac97_t *ac97); +int snd_ac97_info_enum_double(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo); +int snd_ac97_get_enum_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol); +int snd_ac97_put_enum_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol); int snd_ac97_update_bits_nolock(ac97_t *ac97, unsigned short reg, unsigned short mask, unsigned short value); diff --git a/sound/pci/ac97/ac97_patch.c b/sound/pci/ac97/ac97_patch.c index 3df9bef91f1e..65e6b2149d86 100644 --- a/sound/pci/ac97/ac97_patch.c +++ b/sound/pci/ac97/ac97_patch.c @@ -305,6 +305,136 @@ int patch_wolfson11(ac97_t * ac97) return 0; } +static const char* wm9713_mic_mixer[] = {"Stereo", "Mic1", "Mic2", "Mute"}; +static const char* wm9713_rec_mux[] = {"Stereo", "Left", "Right", "Mute"}; +static const char* wm9713_rec_src_l[] = {"Mic1", "Mic2", "Line L", "Mono In", "HP Mix L", "Spk Mix", "Mono Mix", "Zh"}; +static const char* wm9713_rec_src_r[] = {"Mic1", "Mic2", "Line R", "Mono In", "HP Mix R", "Spk Mix", "Mono Mix", "Zh"}; + +static const struct ac97_enum wm9713_enum[] = { +AC97_ENUM_SINGLE(AC97_LINE, 3, 4, wm9713_mic_mixer), +AC97_ENUM_SINGLE(AC97_VIDEO, 14, 4, wm9713_rec_mux), +AC97_ENUM_SINGLE(AC97_VIDEO, 9, 4, wm9713_rec_mux), +AC97_ENUM_SINGLE(AC97_VIDEO, 3, 8, wm9713_rec_src_l), +AC97_ENUM_SINGLE(AC97_VIDEO, 0, 8, wm9713_rec_src_r), +}; + +static const snd_kcontrol_new_t wm13_snd_ac97_controls_line_in[] = { +AC97_DOUBLE("Line In Volume", AC97_PC_BEEP, 8, 0, 31, 1), +AC97_SINGLE("Line In to Headphone Mute", AC97_PC_BEEP, 15, 1, 1), +AC97_SINGLE("Line In to Speaker Mute", AC97_PC_BEEP, 14, 1, 1), +AC97_SINGLE("Line In to Mono Mute", AC97_PC_BEEP, 13, 1, 1), +}; + +static const snd_kcontrol_new_t wm13_snd_ac97_controls_dac[] = { +AC97_DOUBLE("DAC Volume", AC97_PHONE, 8, 0, 31, 1), +AC97_SINGLE("DAC to Headphone Mute", AC97_PHONE, 15, 1, 1), +AC97_SINGLE("DAC to Speaker Mute", AC97_PHONE, 14, 1, 1), +AC97_SINGLE("DAC to Mono Mute", AC97_PHONE, 13, 1, 1), +}; + +static const snd_kcontrol_new_t wm13_snd_ac97_controls_mic[] = { +AC97_SINGLE("MICA Volume", AC97_MIC, 8, 31, 1), +AC97_SINGLE("MICB Volume", AC97_MIC, 0, 31, 1), +AC97_SINGLE("MICA to Mono Mute", AC97_LINE, 7, 1, 1), +AC97_SINGLE("MICB to Mono Mute", AC97_LINE, 6, 1, 1), +AC97_SINGLE("MIC Boost (+20dB)", AC97_LINE, 5, 1, 1), +AC97_ENUM("MIC Headphone Routing", wm9713_enum[0]), +AC97_SINGLE("MIC Headphone Mixer Volume", AC97_LINE, 0, 7, 1) +}; + +static const snd_kcontrol_new_t wm13_snd_ac97_controls_adc[] = { +AC97_SINGLE("ADC Mute", AC97_CD, 15, 1, 1), +AC97_DOUBLE("Gain Step Size (1.5dB/0.75dB)", AC97_CD, 14, 6, 1, 1), +AC97_DOUBLE("ADC Volume",AC97_CD, 8, 0, 15, 0), +AC97_SINGLE("ADC Zero Cross", AC97_CD, 7, 1, 1), +}; + +static const snd_kcontrol_new_t wm13_snd_ac97_controls_recsel[] = { +AC97_ENUM("Record to Headphone Path", wm9713_enum[1]), +AC97_SINGLE("Record to Headphone Volume", AC97_VIDEO, 11, 7, 0), +AC97_ENUM("Record to Mono Path", wm9713_enum[2]), +AC97_SINGLE("Record to Mono Boost (+20dB)", AC97_VIDEO, 8, 1, 0), +AC97_SINGLE("Record ADC Boost (+20dB)", AC97_VIDEO, 6, 1, 0), +AC97_ENUM("Record Select Left", wm9713_enum[3]), +AC97_ENUM("Record Select Right", wm9713_enum[4]), +}; + +static int patch_wolfson_wm9713_specific(ac97_t * ac97) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm13_snd_ac97_controls_line_in); i++) { + if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm13_snd_ac97_controls_line_in[i], ac97))) < 0) + return err; + } + snd_ac97_write_cache(ac97, AC97_PC_BEEP, 0x0808); + + for (i = 0; i < ARRAY_SIZE(wm13_snd_ac97_controls_dac); i++) { + if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm13_snd_ac97_controls_dac[i], ac97))) < 0) + return err; + } + snd_ac97_write_cache(ac97, AC97_PHONE, 0x0808); + + for (i = 0; i < ARRAY_SIZE(wm13_snd_ac97_controls_mic); i++) { + if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm13_snd_ac97_controls_mic[i], ac97))) < 0) + return err; + } + snd_ac97_write_cache(ac97, AC97_MIC, 0x0808); + snd_ac97_write_cache(ac97, AC97_LINE, 0x00da); + + for (i = 0; i < ARRAY_SIZE(wm13_snd_ac97_controls_adc); i++) { + if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm13_snd_ac97_controls_adc[i], ac97))) < 0) + return err; + } + snd_ac97_write_cache(ac97, AC97_CD, 0x0808); + + for (i = 0; i < ARRAY_SIZE(wm13_snd_ac97_controls_recsel); i++) { + if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm13_snd_ac97_controls_recsel[i], ac97))) < 0) + return err; + } + snd_ac97_write_cache(ac97, AC97_VIDEO, 0xd612); + snd_ac97_write_cache(ac97, AC97_REC_GAIN, 0x1ba0); + + return 0; +} + +#ifdef CONFIG_PM +static void patch_wolfson_wm9713_suspend (ac97_t * ac97) +{ + snd_ac97_write_cache(ac97, AC97_EXTENDED_MID, 0xfeff); + snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0xffff); +} + +static void patch_wolfson_wm9713_resume (ac97_t * ac97) +{ + snd_ac97_write_cache(ac97, AC97_EXTENDED_MID, 0xda00); + snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0x3810); + snd_ac97_write_cache(ac97, AC97_POWERDOWN, 0x0); +} +#endif + +static struct snd_ac97_build_ops patch_wolfson_wm9713_ops = { + .build_specific = patch_wolfson_wm9713_specific, +#ifdef CONFIG_PM + .suspend = patch_wolfson_wm9713_suspend, + .resume = patch_wolfson_wm9713_resume +#endif +}; + +int patch_wolfson13(ac97_t * ac97) +{ + ac97->build_ops = &patch_wolfson_wm9713_ops; + + ac97->flags |= AC97_HAS_NO_REC_GAIN | AC97_STEREO_MUTES | AC97_HAS_NO_PHONE | + AC97_HAS_NO_PC_BEEP | AC97_HAS_NO_VIDEO | AC97_HAS_NO_CD; + + snd_ac97_write_cache(ac97, AC97_EXTENDED_MID, 0xda00); + snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0x3810); + snd_ac97_write_cache(ac97, AC97_POWERDOWN, 0x0); + + return 0; +} + /* * Tritech codec */ diff --git a/sound/pci/ac97/ac97_patch.h b/sound/pci/ac97/ac97_patch.h index 641b0be889ea..6db51c96f5d0 100644 --- a/sound/pci/ac97/ac97_patch.h +++ b/sound/pci/ac97/ac97_patch.h @@ -28,6 +28,7 @@ int patch_wolfson03(ac97_t * ac97); int patch_wolfson04(ac97_t * ac97); int patch_wolfson05(ac97_t * ac97); int patch_wolfson11(ac97_t * ac97); +int patch_wolfson13(ac97_t * ac97); int patch_tritech_tr28028(ac97_t * ac97); int patch_sigmatel_stac9700(ac97_t * ac97); int patch_sigmatel_stac9708(ac97_t * ac97); |
