ALSA: hda - Add support for CX20952
[pandora-kernel.git] / sound / pci / hda / patch_conexant.c
index 0de2119..843d9f3 100644 (file)
@@ -139,6 +139,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;
 
@@ -591,24 +592,12 @@ static int conexant_build_controls(struct hda_codec *codec)
        return 0;
 }
 
-#ifdef CONFIG_SND_HDA_POWER_SAVE
-static int conexant_suspend(struct hda_codec *codec, pm_message_t state)
-{
-       snd_hda_shutup_pins(codec);
-       return 0;
-}
-#endif
-
 static const struct hda_codec_ops conexant_patch_ops = {
        .build_controls = conexant_build_controls,
        .build_pcms = conexant_build_pcms,
        .init = conexant_init,
        .free = conexant_free,
        .set_power_state = conexant_set_power,
-#ifdef CONFIG_SND_HDA_POWER_SAVE
-       .suspend = conexant_suspend,
-#endif
-       .reboot_notify = snd_hda_shutup_pins,
 };
 
 #ifdef CONFIG_SND_HDA_INPUT_BEEP
@@ -1120,8 +1109,6 @@ static const char * const cxt5045_models[CXT5045_MODELS] = {
 
 static const struct snd_pci_quirk cxt5045_cfg_tbl[] = {
        SND_PCI_QUIRK(0x103c, 0x30d5, "HP 530", CXT5045_LAPTOP_HP530),
-       SND_PCI_QUIRK_MASK(0x103c, 0xff00, 0x3000, "HP DV Series",
-                          CXT5045_LAPTOP_HPSENSE),
        SND_PCI_QUIRK(0x1179, 0xff31, "Toshiba P105", CXT5045_LAPTOP_MICSENSE),
        SND_PCI_QUIRK(0x152d, 0x0753, "Benq R55E", CXT5045_BENQ),
        SND_PCI_QUIRK(0x1734, 0x10ad, "Fujitsu Si1520", CXT5045_LAPTOP_MICSENSE),
@@ -1249,7 +1236,7 @@ static int patch_cxt5045(struct hda_codec *codec)
        }
 
        if (spec->beep_amp)
-               snd_hda_attach_beep_device(codec, spec->beep_amp);
+               snd_hda_attach_beep_device(codec, get_amp_nid_(spec->beep_amp));
 
        return 0;
 }
@@ -1901,6 +1888,10 @@ static void cxt5051_init_mic_port(struct hda_codec *codec, hda_nid_t nid,
        snd_hda_codec_write(codec, nid, 0,
                            AC_VERB_SET_UNSOLICITED_ENABLE,
                            AC_USRSP_EN | event);
+}
+
+static void cxt5051_init_mic_jack(struct hda_codec *codec, hda_nid_t nid)
+{
        snd_hda_input_jack_add(codec, nid, SND_JACK_MICROPHONE, NULL);
        snd_hda_input_jack_report(codec, nid);
 }
@@ -1918,7 +1909,6 @@ static int cxt5051_init(struct hda_codec *codec)
        struct conexant_spec *spec = codec->spec;
 
        conexant_init(codec);
-       conexant_init_jacks(codec);
 
        if (spec->auto_mic & AUTO_MIC_PORTB)
                cxt5051_init_mic_port(codec, 0x17, CXT5051_PORTB_EVENT);
@@ -2037,7 +2027,13 @@ static int patch_cxt5051(struct hda_codec *codec)
        }
 
        if (spec->beep_amp)
-               snd_hda_attach_beep_device(codec, spec->beep_amp);
+               snd_hda_attach_beep_device(codec, get_amp_nid_(spec->beep_amp));
+
+       conexant_init_jacks(codec);
+       if (spec->auto_mic & AUTO_MIC_PORTB)
+               cxt5051_init_mic_jack(codec, 0x17);
+       if (spec->auto_mic & AUTO_MIC_PORTC)
+               cxt5051_init_mic_jack(codec, 0x18);
 
        return 0;
 }
@@ -3052,7 +3048,6 @@ static const struct snd_pci_quirk cxt5066_cfg_tbl[] = {
        SND_PCI_QUIRK(0x1028, 0x02d8, "Dell Vostro", CXT5066_DELL_VOSTRO),
        SND_PCI_QUIRK(0x1028, 0x02f5, "Dell Vostro 320", CXT5066_IDEAPAD),
        SND_PCI_QUIRK(0x1028, 0x0401, "Dell Vostro 1014", CXT5066_DELL_VOSTRO),
-       SND_PCI_QUIRK(0x1028, 0x0402, "Dell Vostro", CXT5066_DELL_VOSTRO),
        SND_PCI_QUIRK(0x1028, 0x0408, "Dell Inspiron One 19T", CXT5066_IDEAPAD),
        SND_PCI_QUIRK(0x1028, 0x050f, "Dell Inspiron", CXT5066_IDEAPAD),
        SND_PCI_QUIRK(0x1028, 0x0510, "Dell Vostro", CXT5066_IDEAPAD),
@@ -3230,7 +3225,7 @@ static int patch_cxt5066(struct hda_codec *codec)
        }
 
        if (spec->beep_amp)
-               snd_hda_attach_beep_device(codec, spec->beep_amp);
+               snd_hda_attach_beep_device(codec, get_amp_nid_(spec->beep_amp));
 
        return 0;
 }
@@ -3996,9 +3991,14 @@ static void cx_auto_init_output(struct hda_codec *codec)
        int i;
 
        mute_outputs(codec, spec->multiout.num_dacs, spec->multiout.dac_nids);
-       for (i = 0; i < cfg->hp_outs; i++)
+       for (i = 0; i < cfg->hp_outs; i++) {
+               unsigned int val = PIN_OUT;
+               if (snd_hda_query_pin_caps(codec, cfg->hp_pins[i]) &
+                   AC_PINCAP_HP_DRV)
+                       val |= AC_PINCTL_HP_EN;
                snd_hda_codec_write(codec, cfg->hp_pins[i], 0,
-                                   AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP);
+                                   AC_VERB_SET_PIN_WIDGET_CONTROL, val);
+       }
        mute_outputs(codec, cfg->hp_outs, cfg->hp_pins);
        mute_outputs(codec, cfg->line_outs, cfg->line_out_pins);
        mute_outputs(codec, cfg->speaker_outs, cfg->speaker_pins);
@@ -4102,9 +4102,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),
@@ -4114,7 +4114,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;
@@ -4125,14 +4125,15 @@ static int cx_auto_add_volume_idx(struct hda_codec *codec, const char *basename,
                err = snd_hda_ctl_add(codec, nid, kctl);
                if (err < 0)
                        return err;
-               if (!(query_amp_caps(codec, nid, hda_dir) & AC_AMPCAP_MUTE))
+               if (!(query_amp_caps(codec, nid, hda_dir) &
+                     (AC_AMPCAP_MUTE | AC_AMPCAP_MIN_MUTE)))
                        break;
        }
        return 0;
 }
 
 #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)
@@ -4202,6 +4203,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)
@@ -4210,14 +4241,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;
 }
@@ -4230,9 +4272,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)
@@ -4365,10 +4417,6 @@ static const struct hda_codec_ops cx_auto_patch_ops = {
        .init = cx_auto_init,
        .free = conexant_free,
        .unsol_event = cx_auto_unsol_event,
-#ifdef CONFIG_SND_HDA_POWER_SAVE
-       .suspend = conexant_suspend,
-#endif
-       .reboot_notify = snd_hda_shutup_pins,
 };
 
 /*
@@ -4386,22 +4434,34 @@ 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_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)
+               return;
+       if (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,
-};
-
+/* ThinkPad X200 & co with cxt5051 */
 static const struct cxt_pincfg cxt_pincfg_lenovo_x200[] = {
        { 0x16, 0x042140ff }, /* HP (seq# overridden) */
        { 0x17, 0x21a11000 }, /* dock-mic */
@@ -4409,15 +4469,54 @@ static const struct cxt_pincfg cxt_pincfg_lenovo_x200[] = {
        {}
 };
 
+/* ThinkPad 410/420/510/520, X201 & co with cxt5066 */
+static const struct cxt_pincfg cxt_pincfg_lenovo_tp410[] = {
+       { 0x19, 0x042110ff }, /* HP (seq# overridden) */
+       { 0x1a, 0x21a190f0 }, /* dock-mic */
+       { 0x1c, 0x212140ff }, /* dock-HP */
+       {}
+};
+
 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 cxt_fixups[] = {
+static const struct snd_pci_quirk cxt5051_fixups[] = {
        SND_PCI_QUIRK(0x17aa, 0x20f2, "Lenovo X200", CXT_PINCFG_LENOVO_X200),
        {}
 };
 
+static const struct snd_pci_quirk cxt5066_fixups[] = {
+       SND_PCI_QUIRK(0x1025, 0x0543, "Acer Aspire One 522", CXT_FIXUP_STEREO_DMIC),
+       SND_PCI_QUIRK(0x17aa, 0x20f2, "Lenovo T400", CXT_PINCFG_LENOVO_TP410),
+       SND_PCI_QUIRK(0x17aa, 0x215e, "Lenovo T410", CXT_PINCFG_LENOVO_TP410),
+       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),
+       SND_PCI_QUIRK(0x17aa, 0x3977, "Lenovo IdeaPad U310", CXT_FIXUP_STEREO_DMIC),
+       SND_PCI_QUIRK(0x17aa, 0x397b, "Lenovo S205", CXT_FIXUP_STEREO_DMIC),
+       {}
+};
+
+/* add "fake" mute amp-caps to DACs on cx5051 so that mixer mute switches
+ * can be created (bko#42825)
+ */
+static void add_cx5051_fake_mutes(struct hda_codec *codec)
+{
+       static hda_nid_t out_nids[] = {
+               0x10, 0x11, 0
+       };
+       hda_nid_t *p;
+
+       for (p = out_nids; *p; p++)
+               snd_hda_override_amp_caps(codec, *p, HDA_OUTPUT,
+                                         AC_AMPCAP_MIN_MUTE |
+                                         query_amp_caps(codec, *p, HDA_OUTPUT));
+}
+
 static int patch_conexant_auto(struct hda_codec *codec)
 {
        struct conexant_spec *spec;
@@ -4436,10 +4535,15 @@ static int patch_conexant_auto(struct hda_codec *codec)
        case 0x14f15045:
                spec->single_adc_amp = 1;
                break;
+       case 0x14f15051:
+               add_cx5051_fake_mutes(codec);
+               apply_fixup(codec, cxt5051_fixups, cxt_pincfg_tbl);
+               break;
+       default:
+               apply_fixup(codec, cxt5066_fixups, cxt_pincfg_tbl);
+               break;
        }
 
-       apply_pin_fixup(codec, cxt_fixups, cxt_pincfg_tbl);
-
        err = cx_auto_search_adcs(codec);
        if (err < 0)
                return err;
@@ -4452,7 +4556,7 @@ static int patch_conexant_auto(struct hda_codec *codec)
        spec->capture_stream = &cx_auto_pcm_analog_capture;
        codec->patch_ops = cx_auto_patch_ops;
        if (spec->beep_amp)
-               snd_hda_attach_beep_device(codec, spec->beep_amp);
+               snd_hda_attach_beep_device(codec, get_amp_nid_(spec->beep_amp));
        return 0;
 }
 
@@ -4494,6 +4598,20 @@ static const struct hda_codec_preset snd_hda_preset_conexant[] = {
          .patch = patch_conexant_auto },
        { .id = 0x14f150b9, .name = "CX20665",
          .patch = patch_conexant_auto },
+       { .id = 0x14f1510f, .name = "CX20751/2",
+         .patch = patch_conexant_auto },
+       { .id = 0x14f15110, .name = "CX20751/2",
+         .patch = patch_conexant_auto },
+       { .id = 0x14f15111, .name = "CX20753/4",
+         .patch = patch_conexant_auto },
+       { .id = 0x14f15113, .name = "CX20755",
+         .patch = patch_conexant_auto },
+       { .id = 0x14f15114, .name = "CX20756",
+         .patch = patch_conexant_auto },
+       { .id = 0x14f15115, .name = "CX20757",
+         .patch = patch_conexant_auto },
+       { .id = 0x14f151d7, .name = "CX20952",
+         .patch = patch_conexant_auto },
        {} /* terminator */
 };
 
@@ -4514,6 +4632,13 @@ MODULE_ALIAS("snd-hda-codec-id:14f150ab");
 MODULE_ALIAS("snd-hda-codec-id:14f150ac");
 MODULE_ALIAS("snd-hda-codec-id:14f150b8");
 MODULE_ALIAS("snd-hda-codec-id:14f150b9");
+MODULE_ALIAS("snd-hda-codec-id:14f1510f");
+MODULE_ALIAS("snd-hda-codec-id:14f15110");
+MODULE_ALIAS("snd-hda-codec-id:14f15111");
+MODULE_ALIAS("snd-hda-codec-id:14f15113");
+MODULE_ALIAS("snd-hda-codec-id:14f15114");
+MODULE_ALIAS("snd-hda-codec-id:14f15115");
+MODULE_ALIAS("snd-hda-codec-id:14f151d7");
 
 MODULE_LICENSE("GPL");
 MODULE_DESCRIPTION("Conexant HD-audio codec");