From 18dcd3044e4c4b3ab6341c98e8d0e81e0d58d5e3 Mon Sep 17 00:00:00 2001 From: David Henningsson Date: Mon, 2 Apr 2012 15:40:27 +0200 Subject: [PATCH] ALSA: hda - Fix internal mic for Lenovo Ideapad U300s The internal mic input is phase inverted on one channel. To avoid people in userspace summing the channels together and get zero result, use a separate mixer control for the inverted channel. BugLink: https://bugs.launchpad.net/bugs/903853 Signed-off-by: David Henningsson Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_conexant.c | 88 +++++++++++++++++++++++++++++----- 1 file changed, 75 insertions(+), 13 deletions(-) diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index 8c6523bbc797..213fb80c11f5 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -142,6 +142,7 @@ struct conexant_spec { unsigned int asus:1; unsigned int pin_eapd_ctrls:1; unsigned int single_adc_amp:1; + unsigned int fixup_stereo_dmic:1; unsigned int adc_switching:1; @@ -4107,9 +4108,9 @@ static int cx_auto_init(struct hda_codec *codec) static int cx_auto_add_volume_idx(struct hda_codec *codec, const char *basename, const char *dir, int cidx, - hda_nid_t nid, int hda_dir, int amp_idx) + hda_nid_t nid, int hda_dir, int amp_idx, int chs) { - static char name[32]; + static char name[44]; static struct snd_kcontrol_new knew[] = { HDA_CODEC_VOLUME(name, 0, 0, 0), HDA_CODEC_MUTE(name, 0, 0, 0), @@ -4119,7 +4120,7 @@ static int cx_auto_add_volume_idx(struct hda_codec *codec, const char *basename, for (i = 0; i < 2; i++) { struct snd_kcontrol *kctl; - knew[i].private_value = HDA_COMPOSE_AMP_VAL(nid, 3, amp_idx, + knew[i].private_value = HDA_COMPOSE_AMP_VAL(nid, chs, amp_idx, hda_dir); knew[i].subdevice = HDA_SUBDEV_AMP_FLAG; knew[i].index = cidx; @@ -4138,7 +4139,7 @@ static int cx_auto_add_volume_idx(struct hda_codec *codec, const char *basename, } #define cx_auto_add_volume(codec, str, dir, cidx, nid, hda_dir) \ - cx_auto_add_volume_idx(codec, str, dir, cidx, nid, hda_dir, 0) + cx_auto_add_volume_idx(codec, str, dir, cidx, nid, hda_dir, 0, 3) #define cx_auto_add_pb_volume(codec, nid, str, idx) \ cx_auto_add_volume(codec, str, " Playback", idx, nid, HDA_OUTPUT) @@ -4208,6 +4209,36 @@ static int cx_auto_build_output_controls(struct hda_codec *codec) return 0; } +/* Returns zero if this is a normal stereo channel, and non-zero if it should + be split in two independent channels. + dest_label must be at least 44 characters. */ +static int cx_auto_get_rightch_label(struct hda_codec *codec, const char *label, + char *dest_label, int nid) +{ + struct conexant_spec *spec = codec->spec; + int i; + + if (!spec->fixup_stereo_dmic) + return 0; + + for (i = 0; i < AUTO_CFG_MAX_INS; i++) { + int def_conf; + if (spec->autocfg.inputs[i].pin != nid) + continue; + + if (spec->autocfg.inputs[i].type != AUTO_PIN_MIC) + return 0; + def_conf = snd_hda_codec_get_pincfg(codec, nid); + if (snd_hda_get_input_pin_attr(def_conf) != INPUT_PIN_ATTR_INT) + return 0; + + /* Finally found the inverted internal mic! */ + snprintf(dest_label, 44, "Inverted %s", label); + return 1; + } + return 0; +} + static int cx_auto_add_capture_volume(struct hda_codec *codec, hda_nid_t nid, const char *label, const char *pfx, int cidx) @@ -4216,14 +4247,25 @@ static int cx_auto_add_capture_volume(struct hda_codec *codec, hda_nid_t nid, int i; for (i = 0; i < spec->num_adc_nids; i++) { + char rightch_label[44]; hda_nid_t adc_nid = spec->adc_nids[i]; int idx = get_input_connection(codec, adc_nid, nid); if (idx < 0) continue; if (spec->single_adc_amp) idx = 0; + + if (cx_auto_get_rightch_label(codec, label, rightch_label, nid)) { + /* Make two independent kcontrols for left and right */ + int err = cx_auto_add_volume_idx(codec, label, pfx, + cidx, adc_nid, HDA_INPUT, idx, 1); + if (err < 0) + return err; + return cx_auto_add_volume_idx(codec, rightch_label, pfx, + cidx, adc_nid, HDA_INPUT, idx, 2); + } return cx_auto_add_volume_idx(codec, label, pfx, - cidx, adc_nid, HDA_INPUT, idx); + cidx, adc_nid, HDA_INPUT, idx, 3); } return 0; } @@ -4236,9 +4278,19 @@ static int cx_auto_add_boost_volume(struct hda_codec *codec, int idx, int i, con; nid = spec->imux_info[idx].pin; - if (get_wcaps(codec, nid) & AC_WCAP_IN_AMP) + if (get_wcaps(codec, nid) & AC_WCAP_IN_AMP) { + char rightch_label[44]; + if (cx_auto_get_rightch_label(codec, label, rightch_label, nid)) { + int err = cx_auto_add_volume_idx(codec, label, " Boost", + cidx, nid, HDA_INPUT, 0, 1); + if (err < 0) + return err; + return cx_auto_add_volume_idx(codec, rightch_label, " Boost", + cidx, nid, HDA_INPUT, 0, 2); + } return cx_auto_add_volume(codec, label, " Boost", cidx, nid, HDA_INPUT); + } con = __select_input_connection(codec, spec->imux_info[idx].adc, nid, &mux, false, 0); if (con < 0) @@ -4405,22 +4457,30 @@ static void apply_pincfg(struct hda_codec *codec, const struct cxt_pincfg *cfg) } -static void apply_pin_fixup(struct hda_codec *codec, +enum { + CXT_PINCFG_LENOVO_X200, + CXT_FIXUP_STEREO_DMIC, +}; + +static void apply_fixup(struct hda_codec *codec, const struct snd_pci_quirk *quirk, const struct cxt_pincfg **table) { + struct conexant_spec *spec = codec->spec; + quirk = snd_pci_quirk_lookup(codec->bus->pci, quirk); - if (quirk) { + if (quirk && table[quirk->value]) { snd_printdd(KERN_INFO "hda_codec: applying pincfg for %s\n", quirk->name); apply_pincfg(codec, table[quirk->value]); } + if (quirk->value == CXT_FIXUP_STEREO_DMIC) { + snd_printdd(KERN_INFO "hda_codec: applying internal mic workaround for %s\n", + quirk->name); + spec->fixup_stereo_dmic = 1; + } } -enum { - CXT_PINCFG_LENOVO_X200, -}; - static const struct cxt_pincfg cxt_pincfg_lenovo_x200[] = { { 0x16, 0x042140ff }, /* HP (seq# overridden) */ { 0x17, 0x21a11000 }, /* dock-mic */ @@ -4431,10 +4491,12 @@ static const struct cxt_pincfg cxt_pincfg_lenovo_x200[] = { static const struct cxt_pincfg *cxt_pincfg_tbl[] = { [CXT_PINCFG_LENOVO_X200] = cxt_pincfg_lenovo_x200, + [CXT_FIXUP_STEREO_DMIC] = NULL, }; static const struct snd_pci_quirk cxt_fixups[] = { SND_PCI_QUIRK(0x17aa, 0x20f2, "Lenovo X200", CXT_PINCFG_LENOVO_X200), + SND_PCI_QUIRK(0x17aa, 0x3975, "Lenovo U300s", CXT_FIXUP_STEREO_DMIC), {} }; @@ -4477,7 +4539,7 @@ static int patch_conexant_auto(struct hda_codec *codec) break; } - apply_pin_fixup(codec, cxt_fixups, cxt_pincfg_tbl); + apply_fixup(codec, cxt_fixups, cxt_pincfg_tbl); /* Show mute-led control only on HP laptops * This is a sort of white-list: on HP laptops, EAPD corresponds -- 2.20.1