--- /dev/null
+From 18dcd3044e4c4b3ab6341c98e8d0e81e0d58d5e3 Mon Sep 17 00:00:00 2001
+From: David Henningsson <david.henningsson@canonical.com>
+Date: Mon, 2 Apr 2012 15:40:27 +0200
+Subject: ALSA: hda - Fix internal mic for Lenovo Ideapad U300s
+
+From: David Henningsson <david.henningsson@canonical.com>
+
+commit 18dcd3044e4c4b3ab6341c98e8d0e81e0d58d5e3 upstream.
+
+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>
+Signed-off-by: Takashi Iwai <tiwai@suse.de>
+[wml: Backported to 3.4:
+ - Adjust context
+ - one more enum value CXT_PINCFG_LENOVO_TP410
+ - Change both invocations of apply_pin_fixup()]
+Signed-off-by: Weng Meiling <wengmeiling.weng@huawei.com>
+---
+ sound/pci/hda/patch_conexant.c | 92 ++++++++++++++++++++++++++++++++++-------
+ 1 file changed, 77 insertions(+), 15 deletions(-)
+
+--- a/sound/pci/hda/patch_conexant.c
++++ b/sound/pci/hda/patch_conexant.c
+@@ -141,6 +141,7 @@ struct conexant_spec {
+ unsigned int hp_laptop:1;
+ unsigned int asus:1;
+ unsigned int pin_eapd_ctrls:1;
++ unsigned int fixup_stereo_dmic:1;
+
+ unsigned int adc_switching:1;
+
+@@ -4071,9 +4072,9 @@ static int cx_auto_init(struct hda_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),
+@@ -4083,7 +4084,7 @@ static int cx_auto_add_volume_idx(struct
+
+ 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;
+@@ -4102,7 +4103,7 @@ static int cx_auto_add_volume_idx(struct
+ }
+
+ #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)
+@@ -4172,6 +4173,36 @@ static int cx_auto_build_output_controls
+ 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)
+@@ -4180,14 +4211,25 @@ static int cx_auto_add_capture_volume(st
+ 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 (codec->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;
+ }
+@@ -4200,9 +4242,19 @@ static int cx_auto_add_boost_volume(stru
+ 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)
+@@ -4365,23 +4417,31 @@ static void apply_pincfg(struct hda_code
+
+ }
+
+-static void apply_pin_fixup(struct hda_codec *codec,
++enum {
++ CXT_PINCFG_LENOVO_X200,
++ CXT_PINCFG_LENOVO_TP410,
++ 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,
+- CXT_PINCFG_LENOVO_TP410,
+-};
+-
+ /* ThinkPad X200 & co with cxt5051 */
+ static const struct cxt_pincfg cxt_pincfg_lenovo_x200[] = {
+ { 0x16, 0x042140ff }, /* HP (seq# overridden) */
+@@ -4402,6 +4462,7 @@ static const struct cxt_pincfg cxt_pincf
+ static const struct cxt_pincfg *cxt_pincfg_tbl[] = {
+ [CXT_PINCFG_LENOVO_X200] = cxt_pincfg_lenovo_x200,
+ [CXT_PINCFG_LENOVO_TP410] = cxt_pincfg_lenovo_tp410,
++ [CXT_FIXUP_STEREO_DMIC] = NULL,
+ };
+
+ static const struct snd_pci_quirk cxt5051_fixups[] = {
+@@ -4415,6 +4476,7 @@ static const struct snd_pci_quirk cxt506
+ SND_PCI_QUIRK(0x17aa, 0x215f, "Lenovo T510", CXT_PINCFG_LENOVO_TP410),
+ SND_PCI_QUIRK(0x17aa, 0x21ce, "Lenovo T420", CXT_PINCFG_LENOVO_TP410),
+ SND_PCI_QUIRK(0x17aa, 0x21cf, "Lenovo T520", CXT_PINCFG_LENOVO_TP410),
++ SND_PCI_QUIRK(0x17aa, 0x3975, "Lenovo U300s", CXT_FIXUP_STEREO_DMIC),
+ {}
+ };
+
+@@ -4454,11 +4516,11 @@ static int patch_conexant_auto(struct hd
+ case 0x14f15051:
+ add_cx5051_fake_mutes(codec);
+ codec->pin_amp_workaround = 1;
+- apply_pin_fixup(codec, cxt5051_fixups, cxt_pincfg_tbl);
++ apply_fixup(codec, cxt5051_fixups, cxt_pincfg_tbl);
+ break;
+ default:
+ codec->pin_amp_workaround = 1;
+- apply_pin_fixup(codec, cxt5066_fixups, cxt_pincfg_tbl);
++ apply_fixup(codec, cxt5066_fixups, cxt_pincfg_tbl);
+ }
+
+ /* Show mute-led control only on HP laptops