[alsa-devel] [PATCH] 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 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
At Mon, 2 Apr 2012 15:40:27 +0200, David Henningsson wrote:
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
Thanks, applied now to topic/hda branch of sound git tree.
Takashi
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].subdevice = HDA_SUBDEV_AMP_FLAG; knew[i].index = cidx;knew[i].private_value = HDA_COMPOSE_AMP_VAL(nid, chs, amp_idx, hda_dir);
@@ -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++) {
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;char rightch_label[44];
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);
} return 0;cidx, adc_nid, HDA_INPUT, idx, 3);
} @@ -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
-- 1.7.9.1
participants (2)
-
David Henningsson
-
Takashi Iwai