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 david.henningsson@canonical.com --- sound/pci/hda/patch_conexant.c | 88 ++++++++++++++++++++++++++++++++++------ 1 files changed, 75 insertions(+), 13 deletions(-)
diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index e6eafb1..c18e3b1 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