ALSA: hda: Add support for another Lenovo ThinkPad Edge in conexant codec
[safe/jmp/linux-2.6] / sound / pci / hda / patch_conexant.c
index 1ab2958..2bf2cb5 100644 (file)
@@ -29,6 +29,7 @@
 
 #include "hda_codec.h"
 #include "hda_local.h"
+#include "hda_beep.h"
 
 #define CXT_PIN_DIR_IN              0x00
 #define CXT_PIN_DIR_OUT             0x01
 
 /* Conexant 5051 specific */
 
-#define CXT5051_SPDIF_OUT      0x1C
+#define CXT5051_SPDIF_OUT      0x12
 #define CXT5051_PORTB_EVENT    0x38
 #define CXT5051_PORTC_EVENT    0x39
 
+#define AUTO_MIC_PORTB         (1 << 1)
+#define AUTO_MIC_PORTC         (1 << 2)
 
 struct conexant_jack {
 
@@ -73,7 +76,7 @@ struct conexant_spec {
                                         */
        unsigned int cur_eapd;
        unsigned int hp_present;
-       unsigned int no_auto_mic;
+       unsigned int auto_mic;
        unsigned int need_dac_fix;
 
        /* capture */
@@ -110,7 +113,24 @@ struct conexant_spec {
 
        unsigned int dell_automute;
        unsigned int port_d_mode;
-       unsigned char ext_mic_bias;
+       unsigned int dell_vostro:1;
+       unsigned int ideapad:1;
+       unsigned int thinkpad:1;
+
+       unsigned int ext_mic_present;
+       unsigned int recording;
+       void (*capture_prepare)(struct hda_codec *codec);
+       void (*capture_cleanup)(struct hda_codec *codec);
+
+       /* OLPC XO-1.5 supports DC input mode (e.g. for use with analog sensors)
+        * through the microphone jack.
+        * When the user enables this through a mixer switch, both internal and
+        * external microphones are disabled. Gain is fixed at 0dB. In this mode,
+        * we also allow the bias to be configured through a separate mixer
+        * control. */
+       unsigned int dc_enable;
+       unsigned int dc_input_bias; /* offset into cxt5066_olpc_dc_bias */
+       unsigned int mic_boost; /* offset into cxt5066_analog_mic_boost */
 };
 
 static int conexant_playback_pcm_open(struct hda_pcm_stream *hinfo,
@@ -183,6 +203,8 @@ static int conexant_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
                                      struct snd_pcm_substream *substream)
 {
        struct conexant_spec *spec = codec->spec;
+       if (spec->capture_prepare)
+               spec->capture_prepare(codec);
        snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number],
                                   stream_tag, 0, format);
        return 0;
@@ -194,6 +216,8 @@ static int conexant_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
 {
        struct conexant_spec *spec = codec->spec;
        snd_hda_codec_cleanup_stream(codec, spec->adc_nids[substream->number]);
+       if (spec->capture_cleanup)
+               spec->capture_cleanup(codec);
        return 0;
 }
 
@@ -476,6 +500,7 @@ static void conexant_free(struct hda_codec *codec)
                snd_array_free(&spec->jacks);
        }
 #endif
+       snd_hda_detach_beep_device(codec);
        kfree(codec->spec);
 }
 
@@ -1171,10 +1196,12 @@ static int patch_cxt5045(struct hda_codec *codec)
 
        switch (codec->subsystem_id >> 16) {
        case 0x103c:
+       case 0x1631:
        case 0x1734:
-               /* HP & Fujitsu-Siemens laptops have really bad sound over 0dB
-                * on NID 0x17. Fix max PCM level to 0 dB
-                * (originally it has 0x2b steps with 0dB offset 0x14)
+       case 0x17aa:
+               /* HP, Packard Bell, Fujitsu-Siemens & Lenovo laptops have
+                * really bad sound over 0dB on NID 0x17. Fix max PCM level to
+                * 0 dB (originally it has 0x2b steps with 0dB offset 0x14)
                 */
                snd_hda_override_amp_caps(codec, 0x17, HDA_INPUT,
                                          (0x14 << AC_AMPCAP_OFFSET_SHIFT) |
@@ -1567,6 +1594,21 @@ static int patch_cxt5047(struct hda_codec *codec)
 #endif 
        }
        spec->vmaster_nid = 0x13;
+
+       switch (codec->subsystem_id >> 16) {
+       case 0x103c:
+               /* HP laptops have really bad sound over 0 dB on NID 0x10.
+                * Fix max PCM level to 0 dB (originally it has 0x1e steps
+                * with 0 dB offset 0x17)
+                */
+               snd_hda_override_amp_caps(codec, 0x10, HDA_INPUT,
+                                         (0x17 << AC_AMPCAP_OFFSET_SHIFT) |
+                                         (0x17 << AC_AMPCAP_NUM_STEPS_SHIFT) |
+                                         (0x05 << AC_AMPCAP_STEP_SIZE_SHIFT) |
+                                         (1 << AC_AMPCAP_MUTE_SHIFT));
+               break;
+       }
+
        return 0;
 }
 
@@ -1582,6 +1624,11 @@ static void cxt5051_update_speaker(struct hda_codec *codec)
 {
        struct conexant_spec *spec = codec->spec;
        unsigned int pinctl;
+       /* headphone pin */
+       pinctl = (spec->hp_present && spec->cur_eapd) ? PIN_HP : 0;
+       snd_hda_codec_write(codec, 0x16, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+                           pinctl);
+       /* speaker pin */
        pinctl = (!spec->hp_present && spec->cur_eapd) ? PIN_OUT : 0;
        snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
                            pinctl);
@@ -1605,7 +1652,7 @@ static void cxt5051_portb_automic(struct hda_codec *codec)
        struct conexant_spec *spec = codec->spec;
        unsigned int present;
 
-       if (spec->no_auto_mic)
+       if (!(spec->auto_mic & AUTO_MIC_PORTB))
                return;
        present = snd_hda_jack_detect(codec, 0x17);
        snd_hda_codec_write(codec, 0x14, 0,
@@ -1620,7 +1667,7 @@ static void cxt5051_portc_automic(struct hda_codec *codec)
        unsigned int present;
        hda_nid_t new_adc;
 
-       if (spec->no_auto_mic)
+       if (!(spec->auto_mic & AUTO_MIC_PORTC))
                return;
        present = snd_hda_jack_detect(codec, 0x18);
        if (present)
@@ -1666,13 +1713,7 @@ static void cxt5051_hp_unsol_event(struct hda_codec *codec,
        conexant_report_jack(codec, nid);
 }
 
-static struct snd_kcontrol_new cxt5051_mixers[] = {
-       HDA_CODEC_VOLUME("Internal Mic Volume", 0x14, 0x00, HDA_INPUT),
-       HDA_CODEC_MUTE("Internal Mic Switch", 0x14, 0x00, HDA_INPUT),
-       HDA_CODEC_VOLUME("External Mic Volume", 0x14, 0x01, HDA_INPUT),
-       HDA_CODEC_MUTE("External Mic Switch", 0x14, 0x01, HDA_INPUT),
-       HDA_CODEC_VOLUME("Docking Mic Volume", 0x15, 0x00, HDA_INPUT),
-       HDA_CODEC_MUTE("Docking Mic Switch", 0x15, 0x00, HDA_INPUT),
+static struct snd_kcontrol_new cxt5051_playback_mixers[] = {
        HDA_CODEC_VOLUME("Master Playback Volume", 0x10, 0x00, HDA_OUTPUT),
        {
                .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
@@ -1682,7 +1723,16 @@ static struct snd_kcontrol_new cxt5051_mixers[] = {
                .put = cxt5051_hp_master_sw_put,
                .private_value = 0x1a,
        },
+       {}
+};
 
+static struct snd_kcontrol_new cxt5051_capture_mixers[] = {
+       HDA_CODEC_VOLUME("Internal Mic Volume", 0x14, 0x00, HDA_INPUT),
+       HDA_CODEC_MUTE("Internal Mic Switch", 0x14, 0x00, HDA_INPUT),
+       HDA_CODEC_VOLUME("External Mic Volume", 0x14, 0x01, HDA_INPUT),
+       HDA_CODEC_MUTE("External Mic Switch", 0x14, 0x01, HDA_INPUT),
+       HDA_CODEC_VOLUME("Docking Mic Volume", 0x15, 0x00, HDA_INPUT),
+       HDA_CODEC_MUTE("Docking Mic Switch", 0x15, 0x00, HDA_INPUT),
        {}
 };
 
@@ -1691,32 +1741,26 @@ static struct snd_kcontrol_new cxt5051_hp_mixers[] = {
        HDA_CODEC_MUTE("Internal Mic Switch", 0x14, 0x00, HDA_INPUT),
        HDA_CODEC_VOLUME("External Mic Volume", 0x15, 0x00, HDA_INPUT),
        HDA_CODEC_MUTE("External Mic Switch", 0x15, 0x00, HDA_INPUT),
-       HDA_CODEC_VOLUME("Master Playback Volume", 0x10, 0x00, HDA_OUTPUT),
-       {
-               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-               .name = "Master Playback Switch",
-               .info = cxt_eapd_info,
-               .get = cxt_eapd_get,
-               .put = cxt5051_hp_master_sw_put,
-               .private_value = 0x1a,
-       },
-
        {}
 };
 
 static struct snd_kcontrol_new cxt5051_hp_dv6736_mixers[] = {
-       HDA_CODEC_VOLUME("Mic Volume", 0x14, 0x00, HDA_INPUT),
-       HDA_CODEC_MUTE("Mic Switch", 0x14, 0x00, HDA_INPUT),
-       HDA_CODEC_VOLUME("Master Playback Volume", 0x10, 0x00, HDA_OUTPUT),
-       {
-               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-               .name = "Master Playback Switch",
-               .info = cxt_eapd_info,
-               .get = cxt_eapd_get,
-               .put = cxt5051_hp_master_sw_put,
-               .private_value = 0x1a,
-       },
+       HDA_CODEC_VOLUME("Capture Volume", 0x14, 0x00, HDA_INPUT),
+       HDA_CODEC_MUTE("Capture Switch", 0x14, 0x00, HDA_INPUT),
+       {}
+};
 
+static struct snd_kcontrol_new cxt5051_f700_mixers[] = {
+       HDA_CODEC_VOLUME("Capture Volume", 0x14, 0x01, HDA_INPUT),
+       HDA_CODEC_MUTE("Capture Switch", 0x14, 0x01, HDA_INPUT),
+       {}
+};
+
+static struct snd_kcontrol_new cxt5051_toshiba_mixers[] = {
+       HDA_CODEC_VOLUME("Internal Mic Volume", 0x14, 0x00, HDA_INPUT),
+       HDA_CODEC_MUTE("Internal Mic Switch", 0x14, 0x00, HDA_INPUT),
+       HDA_CODEC_VOLUME("External Mic Volume", 0x14, 0x01, HDA_INPUT),
+       HDA_CODEC_MUTE("External Mic Switch", 0x14, 0x01, HDA_INPUT),
        {}
 };
 
@@ -1741,12 +1785,11 @@ static struct hda_verb cxt5051_init_verbs[] = {
        {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1) | 0x44},
        {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x44},
        /* SPDIF route: PCM */
+       {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
        {0x1c, AC_VERB_SET_CONNECT_SEL, 0x0},
        /* EAPD */
        {0x1a, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */ 
        {0x16, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CONEXANT_HP_EVENT},
-       {0x17, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CXT5051_PORTB_EVENT},
-       {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CXT5051_PORTC_EVENT},
        { } /* end */
 };
 
@@ -1772,7 +1815,6 @@ static struct hda_verb cxt5051_hp_dv6736_init_verbs[] = {
        /* EAPD */
        {0x1a, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */
        {0x16, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CONEXANT_HP_EVENT},
-       {0x17, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CXT5051_PORTB_EVENT},
        { } /* end */
 };
 
@@ -1800,21 +1842,65 @@ static struct hda_verb cxt5051_lenovo_x200_init_verbs[] = {
        {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1) | 0x44},
        {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x44},
        /* SPDIF route: PCM */
+       {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, /* needed for W500 Advanced Mini Dock 250410 */
        {0x1c, AC_VERB_SET_CONNECT_SEL, 0x0},
        /* EAPD */
        {0x1a, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */
        {0x16, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CONEXANT_HP_EVENT},
-       {0x17, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CXT5051_PORTB_EVENT},
-       {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CXT5051_PORTC_EVENT},
        {0x19, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CONEXANT_HP_EVENT},
        { } /* end */
 };
 
+static struct hda_verb cxt5051_f700_init_verbs[] = {
+       /* Line in, Mic */
+       {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x03},
+       {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+       {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0},
+       {0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0},
+       /* SPK  */
+       {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+       {0x1a, AC_VERB_SET_CONNECT_SEL, 0x00},
+       /* HP, Amp  */
+       {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+       {0x16, AC_VERB_SET_CONNECT_SEL, 0x00},
+       /* DAC1 */
+       {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+       /* Record selector: Int mic */
+       {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1) | 0x44},
+       {0x14, AC_VERB_SET_CONNECT_SEL, 0x1},
+       /* SPDIF route: PCM */
+       {0x1c, AC_VERB_SET_CONNECT_SEL, 0x0},
+       /* EAPD */
+       {0x1a, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */
+       {0x16, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CONEXANT_HP_EVENT},
+       { } /* end */
+};
+
+static void cxt5051_init_mic_port(struct hda_codec *codec, hda_nid_t nid,
+                                unsigned int event)
+{
+       snd_hda_codec_write(codec, nid, 0,
+                           AC_VERB_SET_UNSOLICITED_ENABLE,
+                           AC_USRSP_EN | event);
+#ifdef CONFIG_SND_HDA_INPUT_JACK
+       conexant_add_jack(codec, nid, SND_JACK_MICROPHONE);
+       conexant_report_jack(codec, nid);
+#endif
+}
+
 /* initialize jack-sensing, too */
 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);
+       if (spec->auto_mic & AUTO_MIC_PORTC)
+               cxt5051_init_mic_port(codec, 0x18, CXT5051_PORTC_EVENT);
+
        if (codec->patch_ops.unsol_event) {
                cxt5051_hp_automute(codec);
                cxt5051_portb_automic(codec);
@@ -1828,7 +1914,9 @@ enum {
        CXT5051_LAPTOP,  /* Laptops w/ EAPD support */
        CXT5051_HP,     /* no docking */
        CXT5051_HP_DV6736,      /* HP without mic switch */
-       CXT5051_LENOVO_X200,    /* Lenovo X200 laptop */
+       CXT5051_LENOVO_X200,    /* Lenovo X200 laptop, also used for Advanced Mini Dock 250410 */
+       CXT5051_F700,       /* HP Compaq Presario F700 */
+       CXT5051_TOSHIBA,        /* Toshiba M300 & co */
        CXT5051_MODELS
 };
 
@@ -1837,11 +1925,15 @@ static const char *cxt5051_models[CXT5051_MODELS] = {
        [CXT5051_HP]            = "hp",
        [CXT5051_HP_DV6736]     = "hp-dv6736",
        [CXT5051_LENOVO_X200]   = "lenovo-x200",
+       [CXT5051_F700]          = "hp-700",
+       [CXT5051_TOSHIBA]       = "toshiba",
 };
 
 static struct snd_pci_quirk cxt5051_cfg_tbl[] = {
        SND_PCI_QUIRK(0x103c, 0x30cf, "HP DV6736", CXT5051_HP_DV6736),
        SND_PCI_QUIRK(0x103c, 0x360b, "Compaq Presario CQ60", CXT5051_HP),
+       SND_PCI_QUIRK(0x103c, 0x30ea, "Compaq Presario F700", CXT5051_F700),
+       SND_PCI_QUIRK(0x1179, 0xff50, "Toshiba M30x", CXT5051_TOSHIBA),
        SND_PCI_QUIRK(0x14f1, 0x0101, "Conexant Reference board",
                      CXT5051_LAPTOP),
        SND_PCI_QUIRK(0x14f1, 0x5051, "HP Spartan 1.1", CXT5051_HP),
@@ -1869,8 +1961,9 @@ static int patch_cxt5051(struct hda_codec *codec)
        spec->multiout.dig_out_nid = CXT5051_SPDIF_OUT;
        spec->num_adc_nids = 1; /* not 2; via auto-mic switch */
        spec->adc_nids = cxt5051_adc_nids;
-       spec->num_mixers = 1;
-       spec->mixers[0] = cxt5051_mixers;
+       spec->num_mixers = 2;
+       spec->mixers[0] = cxt5051_capture_mixers;
+       spec->mixers[1] = cxt5051_playback_mixers;
        spec->num_init_verbs = 1;
        spec->init_verbs[0] = cxt5051_init_verbs;
        spec->spdif_route = 0;
@@ -1884,6 +1977,7 @@ static int patch_cxt5051(struct hda_codec *codec)
        board_config = snd_hda_check_board_config(codec, CXT5051_MODELS,
                                                  cxt5051_models,
                                                  cxt5051_cfg_tbl);
+       spec->auto_mic = AUTO_MIC_PORTB | AUTO_MIC_PORTC;
        switch (board_config) {
        case CXT5051_HP:
                spec->mixers[0] = cxt5051_hp_mixers;
@@ -1891,11 +1985,20 @@ static int patch_cxt5051(struct hda_codec *codec)
        case CXT5051_HP_DV6736:
                spec->init_verbs[0] = cxt5051_hp_dv6736_init_verbs;
                spec->mixers[0] = cxt5051_hp_dv6736_mixers;
-               spec->no_auto_mic = 1;
+               spec->auto_mic = 0;
                break;
        case CXT5051_LENOVO_X200:
                spec->init_verbs[0] = cxt5051_lenovo_x200_init_verbs;
                break;
+       case CXT5051_F700:
+               spec->init_verbs[0] = cxt5051_f700_init_verbs;
+               spec->mixers[0] = cxt5051_f700_mixers;
+               spec->auto_mic = 0;
+               break;
+       case CXT5051_TOSHIBA:
+               spec->mixers[0] = cxt5051_toshiba_mixers;
+               spec->auto_mic = AUTO_MIC_PORTB;
+               break;
        }
 
        return 0;
@@ -1933,6 +2036,9 @@ static void cxt5066_update_speaker(struct hda_codec *codec)
        /* Port D (HP/LO) */
        pinctl = ((spec->hp_present & 2) && spec->cur_eapd)
                ? spec->port_d_mode : 0;
+       /* Mute if Port A is connected on Thinkpad */
+       if (spec->thinkpad && (spec->hp_present & 1))
+               pinctl = 0;
        snd_hda_codec_write(codec, 0x1c, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
                        pinctl);
 
@@ -1963,33 +2069,117 @@ static int cxt5066_hp_master_sw_put(struct snd_kcontrol *kcontrol,
        return 1;
 }
 
+static const struct hda_input_mux cxt5066_olpc_dc_bias = {
+       .num_items = 3,
+       .items = {
+               { "Off", PIN_IN },
+               { "50%", PIN_VREF50 },
+               { "80%", PIN_VREF80 },
+       },
+};
+
+static int cxt5066_set_olpc_dc_bias(struct hda_codec *codec)
+{
+       struct conexant_spec *spec = codec->spec;
+       /* Even though port F is the DC input, the bias is controlled on port B.
+        * we also leave that port as an active input (but unselected) in DC mode
+        * just in case that is necessary to make the bias setting take effect. */
+       return snd_hda_codec_write_cache(codec, 0x1a, 0,
+               AC_VERB_SET_PIN_WIDGET_CONTROL,
+               cxt5066_olpc_dc_bias.items[spec->dc_input_bias].index);
+}
+
+/* OLPC defers mic widget control until when capture is started because the
+ * microphone LED comes on as soon as these settings are put in place. if we
+ * did this before recording, it would give the false indication that recording
+ * is happening when it is not. */
+static void cxt5066_olpc_select_mic(struct hda_codec *codec)
+{
+       struct conexant_spec *spec = codec->spec;
+       if (!spec->recording)
+               return;
+
+       if (spec->dc_enable) {
+               /* in DC mode we ignore presence detection and just use the jack
+                * through our special DC port */
+               const struct hda_verb enable_dc_mode[] = {
+                       /* disble internal mic, port C */
+                       {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+                       /* enable DC capture, port F */
+                       {0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+                       {},
+               };
+
+               snd_hda_sequence_write(codec, enable_dc_mode);
+               /* port B input disabled (and bias set) through the following call */
+               cxt5066_set_olpc_dc_bias(codec);
+               return;
+       }
+
+       /* disable DC (port F) */
+       snd_hda_codec_write(codec, 0x1e, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0);
+
+       /* external mic, port B */
+       snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+               spec->ext_mic_present ? CXT5066_OLPC_EXT_MIC_BIAS : 0);
+
+       /* internal mic, port C */
+       snd_hda_codec_write(codec, 0x1b, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+               spec->ext_mic_present ? 0 : PIN_VREF80);
+}
+
 /* toggle input of built-in and mic jack appropriately */
-static void cxt5066_automic(struct hda_codec *codec)
+static void cxt5066_olpc_automic(struct hda_codec *codec)
 {
        struct conexant_spec *spec = codec->spec;
+       unsigned int present;
+
+       if (spec->dc_enable) /* don't do presence detection in DC mode */
+               return;
+
+       present = snd_hda_codec_read(codec, 0x1a, 0,
+                                    AC_VERB_GET_PIN_SENSE, 0) & 0x80000000;
+       if (present)
+               snd_printdd("CXT5066: external microphone detected\n");
+       else
+               snd_printdd("CXT5066: external microphone absent\n");
+
+       snd_hda_codec_write(codec, 0x17, 0, AC_VERB_SET_CONNECT_SEL,
+               present ? 0 : 1);
+       spec->ext_mic_present = !!present;
+
+       cxt5066_olpc_select_mic(codec);
+}
+
+/* toggle input of built-in digital mic and mic jack appropriately */
+static void cxt5066_vostro_automic(struct hda_codec *codec)
+{
+       unsigned int present;
+
        struct hda_verb ext_mic_present[] = {
                /* enable external mic, port B */
-               {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, spec->ext_mic_bias},
+               {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
 
                /* switch to external mic input */
                {0x17, AC_VERB_SET_CONNECT_SEL, 0},
+               {0x14, AC_VERB_SET_CONNECT_SEL, 0},
 
-               /* disable internal mic, port C */
-               {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+               /* disable internal digital mic */
+               {0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
                {}
        };
        static struct hda_verb ext_mic_absent[] = {
                /* enable internal mic, port C */
-               {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+               {0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
 
                /* switch to internal mic input */
-               {0x17, AC_VERB_SET_CONNECT_SEL, 1},
+               {0x14, AC_VERB_SET_CONNECT_SEL, 2},
 
                /* disable external mic, port B */
                {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
                {}
        };
-       unsigned int present;
 
        present = snd_hda_jack_detect(codec, 0x1a);
        if (present) {
@@ -2002,39 +2192,71 @@ static void cxt5066_automic(struct hda_codec *codec)
 }
 
 /* toggle input of built-in digital mic and mic jack appropriately */
-static void cxt5066_vostro_automic(struct hda_codec *codec)
+static void cxt5066_ideapad_automic(struct hda_codec *codec)
 {
-       struct conexant_spec *spec = codec->spec;
        unsigned int present;
 
        struct hda_verb ext_mic_present[] = {
-               /* enable external mic, port B */
-               {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, spec->ext_mic_bias},
-
-               /* switch to external mic input */
-               {0x17, AC_VERB_SET_CONNECT_SEL, 0},
                {0x14, AC_VERB_SET_CONNECT_SEL, 0},
-
-               /* disable internal digital mic */
+               {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
                {0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
                {}
        };
        static struct hda_verb ext_mic_absent[] = {
-               /* enable internal mic, port C */
+               {0x14, AC_VERB_SET_CONNECT_SEL, 2},
                {0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+               {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+               {}
+       };
 
-               /* switch to internal mic input */
-               {0x14, AC_VERB_SET_CONNECT_SEL, 2},
+       present = snd_hda_jack_detect(codec, 0x1b);
+       if (present) {
+               snd_printdd("CXT5066: external microphone detected\n");
+               snd_hda_sequence_write(codec, ext_mic_present);
+       } else {
+               snd_printdd("CXT5066: external microphone absent\n");
+               snd_hda_sequence_write(codec, ext_mic_absent);
+       }
+}
 
-               /* disable external mic, port B */
+/* toggle input of built-in digital mic and mic jack appropriately
+   order is: external mic -> dock mic -> interal mic */
+static void cxt5066_thinkpad_automic(struct hda_codec *codec)
+{
+       unsigned int ext_present, dock_present;
+
+       static struct hda_verb ext_mic_present[] = {
+               {0x14, AC_VERB_SET_CONNECT_SEL, 0},
+               {0x17, AC_VERB_SET_CONNECT_SEL, 1},
+               {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+               {0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+               {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+               {}
+       };
+       static struct hda_verb dock_mic_present[] = {
+               {0x14, AC_VERB_SET_CONNECT_SEL, 0},
+               {0x17, AC_VERB_SET_CONNECT_SEL, 0},
+               {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+               {0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+               {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+               {}
+       };
+       static struct hda_verb ext_mic_absent[] = {
+               {0x14, AC_VERB_SET_CONNECT_SEL, 2},
+               {0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+               {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
                {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
                {}
        };
 
-       present = snd_hda_jack_detect(codec, 0x1a);
-       if (present) {
+       ext_present = snd_hda_jack_detect(codec, 0x1b);
+       dock_present = snd_hda_jack_detect(codec, 0x1a);
+       if (ext_present) {
                snd_printdd("CXT5066: external microphone detected\n");
                snd_hda_sequence_write(codec, ext_mic_present);
+       } else if (dock_present) {
+               snd_printdd("CXT5066: dock microphone detected\n");
+               snd_hda_sequence_write(codec, dock_mic_present);
        } else {
                snd_printdd("CXT5066: external microphone absent\n");
                snd_hda_sequence_write(codec, ext_mic_absent);
@@ -2053,22 +2275,26 @@ static void cxt5066_hp_automute(struct hda_codec *codec)
        /* Port D */
        portD = snd_hda_jack_detect(codec, 0x1c);
 
-       spec->hp_present = !!(portA | portD);
+       spec->hp_present = !!(portA);
+       spec->hp_present |= portD ? 2 : 0;
        snd_printdd("CXT5066: hp automute portA=%x portD=%x present=%d\n",
                portA, portD, spec->hp_present);
        cxt5066_update_speaker(codec);
 }
 
 /* unsolicited event for jack sensing */
-static void cxt5066_unsol_event(struct hda_codec *codec, unsigned int res)
+static void cxt5066_olpc_unsol_event(struct hda_codec *codec, unsigned int res)
 {
+       struct conexant_spec *spec = codec->spec;
        snd_printdd("CXT5066: unsol event %x (%x)\n", res, res >> 26);
        switch (res >> 26) {
        case CONEXANT_HP_EVENT:
                cxt5066_hp_automute(codec);
                break;
        case CONEXANT_MIC_EVENT:
-               cxt5066_automic(codec);
+               /* ignore mic events in DC mode; we're always using the jack */
+               if (!spec->dc_enable)
+                       cxt5066_olpc_automic(codec);
                break;
        }
 }
@@ -2087,6 +2313,34 @@ static void cxt5066_vostro_event(struct hda_codec *codec, unsigned int res)
        }
 }
 
+/* unsolicited event for jack sensing */
+static void cxt5066_ideapad_event(struct hda_codec *codec, unsigned int res)
+{
+       snd_printdd("CXT5066_ideapad: unsol event %x (%x)\n", res, res >> 26);
+       switch (res >> 26) {
+       case CONEXANT_HP_EVENT:
+               cxt5066_hp_automute(codec);
+               break;
+       case CONEXANT_MIC_EVENT:
+               cxt5066_ideapad_automic(codec);
+               break;
+       }
+}
+
+/* unsolicited event for jack sensing */
+static void cxt5066_thinkpad_event(struct hda_codec *codec, unsigned int res)
+{
+       snd_printdd("CXT5066_thinkpad: unsol event %x (%x)\n", res, res >> 26);
+       switch (res >> 26) {
+       case CONEXANT_HP_EVENT:
+               cxt5066_hp_automute(codec);
+               break;
+       case CONEXANT_MIC_EVENT:
+               cxt5066_thinkpad_automic(codec);
+               break;
+       }
+}
+
 static const struct hda_input_mux cxt5066_analog_mic_boost = {
        .num_items = 5,
        .items = {
@@ -2098,6 +2352,23 @@ static const struct hda_input_mux cxt5066_analog_mic_boost = {
        },
 };
 
+static void cxt5066_set_mic_boost(struct hda_codec *codec)
+{
+       struct conexant_spec *spec = codec->spec;
+       snd_hda_codec_write_cache(codec, 0x17, 0,
+               AC_VERB_SET_AMP_GAIN_MUTE,
+               AC_AMP_SET_RIGHT | AC_AMP_SET_LEFT | AC_AMP_SET_OUTPUT |
+                       cxt5066_analog_mic_boost.items[spec->mic_boost].index);
+       if (spec->ideapad || spec->thinkpad) {
+               /* adjust the internal mic as well...it is not through 0x17 */
+               snd_hda_codec_write_cache(codec, 0x23, 0,
+                       AC_VERB_SET_AMP_GAIN_MUTE,
+                       AC_AMP_SET_RIGHT | AC_AMP_SET_LEFT | AC_AMP_SET_INPUT |
+                               cxt5066_analog_mic_boost.
+                                       items[spec->mic_boost].index);
+       }
+}
+
 static int cxt5066_mic_boost_mux_enum_info(struct snd_kcontrol *kcontrol,
                                           struct snd_ctl_elem_info *uinfo)
 {
@@ -2108,12 +2379,8 @@ static int cxt5066_mic_boost_mux_enum_get(struct snd_kcontrol *kcontrol,
                                          struct snd_ctl_elem_value *ucontrol)
 {
        struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
-       int val;
-
-       val = snd_hda_codec_read(codec, 0x17, 0,
-               AC_VERB_GET_AMP_GAIN_MUTE, AC_AMP_GET_OUTPUT);
-
-       ucontrol->value.enumerated.item[0] = val & AC_AMP_GAIN;
+       struct conexant_spec *spec = codec->spec;
+       ucontrol->value.enumerated.item[0] = spec->mic_boost;
        return 0;
 }
 
@@ -2121,23 +2388,132 @@ static int cxt5066_mic_boost_mux_enum_put(struct snd_kcontrol *kcontrol,
                                          struct snd_ctl_elem_value *ucontrol)
 {
        struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct conexant_spec *spec = codec->spec;
        const struct hda_input_mux *imux = &cxt5066_analog_mic_boost;
        unsigned int idx;
+       idx = ucontrol->value.enumerated.item[0];
+       if (idx >= imux->num_items)
+               idx = imux->num_items - 1;
+
+       spec->mic_boost = idx;
+       if (!spec->dc_enable)
+               cxt5066_set_mic_boost(codec);
+       return 1;
+}
+
+static void cxt5066_enable_dc(struct hda_codec *codec)
+{
+       const struct hda_verb enable_dc_mode[] = {
+               /* disable gain */
+               {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+               /* switch to DC input */
+               {0x17, AC_VERB_SET_CONNECT_SEL, 3},
+               {}
+       };
+
+       /* configure as input source */
+       snd_hda_sequence_write(codec, enable_dc_mode);
+       cxt5066_olpc_select_mic(codec); /* also sets configured bias */
+}
+
+static void cxt5066_disable_dc(struct hda_codec *codec)
+{
+       /* reconfigure input source */
+       cxt5066_set_mic_boost(codec);
+       /* automic also selects the right mic if we're recording */
+       cxt5066_olpc_automic(codec);
+}
+
+static int cxt5066_olpc_dc_get(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_value *ucontrol)
+{
+       struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct conexant_spec *spec = codec->spec;
+       ucontrol->value.integer.value[0] = spec->dc_enable;
+       return 0;
+}
+
+static int cxt5066_olpc_dc_put(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_value *ucontrol)
+{
+       struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct conexant_spec *spec = codec->spec;
+       int dc_enable = !!ucontrol->value.integer.value[0];
 
-       if (!imux->num_items)
+       if (dc_enable == spec->dc_enable)
                return 0;
+
+       spec->dc_enable = dc_enable;
+       if (dc_enable)
+               cxt5066_enable_dc(codec);
+       else
+               cxt5066_disable_dc(codec);
+
+       return 1;
+}
+
+static int cxt5066_olpc_dc_bias_enum_info(struct snd_kcontrol *kcontrol,
+                                          struct snd_ctl_elem_info *uinfo)
+{
+       return snd_hda_input_mux_info(&cxt5066_olpc_dc_bias, uinfo);
+}
+
+static int cxt5066_olpc_dc_bias_enum_get(struct snd_kcontrol *kcontrol,
+                                         struct snd_ctl_elem_value *ucontrol)
+{
+       struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct conexant_spec *spec = codec->spec;
+       ucontrol->value.enumerated.item[0] = spec->dc_input_bias;
+       return 0;
+}
+
+static int cxt5066_olpc_dc_bias_enum_put(struct snd_kcontrol *kcontrol,
+                                         struct snd_ctl_elem_value *ucontrol)
+{
+       struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct conexant_spec *spec = codec->spec;
+       const struct hda_input_mux *imux = &cxt5066_analog_mic_boost;
+       unsigned int idx;
+
        idx = ucontrol->value.enumerated.item[0];
        if (idx >= imux->num_items)
                idx = imux->num_items - 1;
 
-       snd_hda_codec_write_cache(codec, 0x17, 0,
-               AC_VERB_SET_AMP_GAIN_MUTE,
-               AC_AMP_SET_RIGHT | AC_AMP_SET_LEFT | AC_AMP_SET_OUTPUT |
-                       imux->items[idx].index);
-
+       spec->dc_input_bias = idx;
+       if (spec->dc_enable)
+               cxt5066_set_olpc_dc_bias(codec);
        return 1;
 }
 
+static void cxt5066_olpc_capture_prepare(struct hda_codec *codec)
+{
+       struct conexant_spec *spec = codec->spec;
+       /* mark as recording and configure the microphone widget so that the
+        * recording LED comes on. */
+       spec->recording = 1;
+       cxt5066_olpc_select_mic(codec);
+}
+
+static void cxt5066_olpc_capture_cleanup(struct hda_codec *codec)
+{
+       struct conexant_spec *spec = codec->spec;
+       const struct hda_verb disable_mics[] = {
+               /* disable external mic, port B */
+               {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+               /* disble internal mic, port C */
+               {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+               /* disable DC capture, port F */
+               {0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+               {},
+       };
+
+       snd_hda_sequence_write(codec, disable_mics);
+       spec->recording = 0;
+}
+
 static struct hda_input_mux cxt5066_capture_source = {
        .num_items = 4,
        .items = {
@@ -2190,6 +2566,24 @@ static struct snd_kcontrol_new cxt5066_mixer_master_olpc[] = {
        {}
 };
 
+static struct snd_kcontrol_new cxt5066_mixer_olpc_dc[] = {
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name = "DC Mode Enable Switch",
+               .info = snd_ctl_boolean_mono_info,
+               .get = cxt5066_olpc_dc_get,
+               .put = cxt5066_olpc_dc_put,
+       },
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name = "DC Input Bias Enum",
+               .info = cxt5066_olpc_dc_bias_enum_info,
+               .get = cxt5066_olpc_dc_bias_enum_get,
+               .put = cxt5066_olpc_dc_bias_enum_put,
+       },
+       {}
+};
+
 static struct snd_kcontrol_new cxt5066_mixers[] = {
        {
                .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
@@ -2213,6 +2607,19 @@ static struct snd_kcontrol_new cxt5066_mixers[] = {
        {}
 };
 
+static struct snd_kcontrol_new cxt5066_vostro_mixers[] = {
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name = "Int Mic Boost Capture Enum",
+               .info = cxt5066_mic_boost_mux_enum_info,
+               .get = cxt5066_mic_boost_mux_enum_get,
+               .put = cxt5066_mic_boost_mux_enum_put,
+               .private_value = 0x23 | 0x100,
+       },
+       HDA_CODEC_VOLUME_MONO("Beep Playback Volume", 0x13, 1, 0x0, HDA_OUTPUT),
+       {}
+};
+
 static struct hda_verb cxt5066_init_verbs[] = {
        {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, /* Port B */
        {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, /* Port C */
@@ -2274,10 +2681,10 @@ static struct hda_verb cxt5066_init_verbs_olpc[] = {
        {0x19, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
 
        /* Port B: external microphone */
-       {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, CXT5066_OLPC_EXT_MIC_BIAS},
+       {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
 
        /* Port C: internal microphone */
-       {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+       {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
 
        /* Port D: unused */
        {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
@@ -2286,7 +2693,7 @@ static struct hda_verb cxt5066_init_verbs_olpc[] = {
        {0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
        {0x1d, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */
 
-       /* Port F: unused */
+       /* Port F: external DC input through microphone port */
        {0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
 
        /* Port G: internal speakers */
@@ -2390,6 +2797,114 @@ static struct hda_verb cxt5066_init_verbs_vostro[] = {
        { } /* end */
 };
 
+static struct hda_verb cxt5066_init_verbs_ideapad[] = {
+       {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, /* Port B */
+       {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, /* Port C */
+       {0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* Port F */
+       {0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* Port E */
+
+       /* Speakers  */
+       {0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+       {0x1f, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
+
+       /* HP, Amp  */
+       {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+       {0x19, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
+
+       {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+       {0x1c, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
+
+       /* DAC1 */
+       {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+       /* Node 14 connections: 0x17 0x18 0x23 0x24 0x27 */
+       {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x50},
+       {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+       {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2) | 0x50},
+       {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+       {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+       {0x14, AC_VERB_SET_CONNECT_SEL, 2},     /* default to internal mic */
+
+       /* Audio input selector */
+       {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x2},
+       {0x17, AC_VERB_SET_CONNECT_SEL, 1},     /* route ext mic */
+
+       /* SPDIF route: PCM */
+       {0x20, AC_VERB_SET_CONNECT_SEL, 0x0},
+       {0x22, AC_VERB_SET_CONNECT_SEL, 0x0},
+
+       {0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+       {0x22, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+
+       /* internal microphone */
+       {0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* enable int mic */
+
+       /* EAPD */
+       {0x1d, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */
+
+       {0x19, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT},
+       {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_MIC_EVENT},
+       { } /* end */
+};
+
+static struct hda_verb cxt5066_init_verbs_thinkpad[] = {
+       {0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* Port F */
+       {0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* Port E */
+
+       /* Port G: internal speakers  */
+       {0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+       {0x1f, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
+
+       /* Port A: HP, Amp  */
+       {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+       {0x19, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
+
+       /* Port B: Mic Dock */
+       {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+       /* Port C: Mic */
+       {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+       /* Port D: HP Dock, Amp */
+       {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+       {0x1c, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
+
+       /* DAC1 */
+       {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+       /* Node 14 connections: 0x17 0x18 0x23 0x24 0x27 */
+       {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x50},
+       {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+       {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2) | 0x50},
+       {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+       {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+       {0x14, AC_VERB_SET_CONNECT_SEL, 2},     /* default to internal mic */
+
+       /* Audio input selector */
+       {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x2},
+       {0x17, AC_VERB_SET_CONNECT_SEL, 1},     /* route ext mic */
+
+       /* SPDIF route: PCM */
+       {0x20, AC_VERB_SET_CONNECT_SEL, 0x0},
+       {0x22, AC_VERB_SET_CONNECT_SEL, 0x0},
+
+       {0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+       {0x22, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+
+       /* internal microphone */
+       {0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* enable int mic */
+
+       /* EAPD */
+       {0x1d, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */
+
+       /* enable unsolicited events for Port A, B, C and D */
+       {0x19, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT},
+       {0x1c, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT},
+       {0x1a, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_MIC_EVENT},
+       {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_MIC_EVENT},
+       { } /* end */
+};
+
 static struct hda_verb cxt5066_init_verbs_portd_lo[] = {
        {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
        { } /* end */
@@ -2398,28 +2913,55 @@ static struct hda_verb cxt5066_init_verbs_portd_lo[] = {
 /* initialize jack-sensing, too */
 static int cxt5066_init(struct hda_codec *codec)
 {
+       struct conexant_spec *spec = codec->spec;
+
        snd_printdd("CXT5066: init\n");
        conexant_init(codec);
        if (codec->patch_ops.unsol_event) {
                cxt5066_hp_automute(codec);
-               cxt5066_automic(codec);
+               if (spec->dell_vostro)
+                       cxt5066_vostro_automic(codec);
+               else if (spec->ideapad)
+                       cxt5066_ideapad_automic(codec);
+               else if (spec->thinkpad)
+                       cxt5066_thinkpad_automic(codec);
+       }
+       cxt5066_set_mic_boost(codec);
+       return 0;
+}
+
+static int cxt5066_olpc_init(struct hda_codec *codec)
+{
+       struct conexant_spec *spec = codec->spec;
+       snd_printdd("CXT5066: init\n");
+       conexant_init(codec);
+       cxt5066_hp_automute(codec);
+       if (!spec->dc_enable) {
+               cxt5066_set_mic_boost(codec);
+               cxt5066_olpc_automic(codec);
+       } else {
+               cxt5066_enable_dc(codec);
        }
        return 0;
 }
 
 enum {
-       CXT5066_LAPTOP,                 /* Laptops w/ EAPD support */
+       CXT5066_LAPTOP,         /* Laptops w/ EAPD support */
        CXT5066_DELL_LAPTOP,    /* Dell Laptop */
        CXT5066_OLPC_XO_1_5,    /* OLPC XO 1.5 */
        CXT5066_DELL_VOSTO,     /* Dell Vostro 1015i */
+       CXT5066_IDEAPAD,        /* Lenovo IdeaPad U150 */
+       CXT5066_THINKPAD,       /* Lenovo ThinkPad T410s, others? */
        CXT5066_MODELS
 };
 
 static const char *cxt5066_models[CXT5066_MODELS] = {
-       [CXT5066_LAPTOP]                = "laptop",
+       [CXT5066_LAPTOP]        = "laptop",
        [CXT5066_DELL_LAPTOP]   = "dell-laptop",
        [CXT5066_OLPC_XO_1_5]   = "olpc-xo-1_5",
-       [CXT5066_DELL_VOSTO]    = "dell-vostro"
+       [CXT5066_DELL_VOSTO]    = "dell-vostro",
+       [CXT5066_IDEAPAD]       = "ideapad",
+       [CXT5066_THINKPAD]      = "thinkpad",
 };
 
 static struct snd_pci_quirk cxt5066_cfg_tbl[] = {
@@ -2429,6 +2971,14 @@ static struct snd_pci_quirk cxt5066_cfg_tbl[] = {
                      CXT5066_DELL_LAPTOP),
        SND_PCI_QUIRK(0x152d, 0x0833, "OLPC XO-1.5", CXT5066_OLPC_XO_1_5),
        SND_PCI_QUIRK(0x1028, 0x0402, "Dell Vostro", CXT5066_DELL_VOSTO),
+       SND_PCI_QUIRK(0x1028, 0x0408, "Dell Inspiron One 19T", CXT5066_IDEAPAD),
+       SND_PCI_QUIRK(0x1179, 0xff50, "Toshiba Satellite P500-PSPGSC-01800T", CXT5066_OLPC_XO_1_5),
+       SND_PCI_QUIRK(0x1179, 0xffe0, "Toshiba Satellite Pro T130-15F", CXT5066_OLPC_XO_1_5),
+       SND_PCI_QUIRK(0x17aa, 0x21b2, "Thinkpad X100e", CXT5066_IDEAPAD),
+       SND_PCI_QUIRK(0x17aa, 0x21b3, "Thinkpad Edge 13 (197)", CXT5066_IDEAPAD),
+       SND_PCI_QUIRK(0x17aa, 0x21b4, "Thinkpad Edge", CXT5066_IDEAPAD),
+       SND_PCI_QUIRK(0x17aa, 0x3a0d, "ideapad", CXT5066_IDEAPAD),
+       SND_PCI_QUIRK(0x17aa, 0x215e, "Lenovo Thinkpad", CXT5066_THINKPAD),
        {}
 };
 
@@ -2443,7 +2993,7 @@ static int patch_cxt5066(struct hda_codec *codec)
        codec->spec = spec;
 
        codec->patch_ops = conexant_patch_ops;
-       codec->patch_ops.init = cxt5066_init;
+       codec->patch_ops.init = conexant_init;
 
        spec->dell_automute = 0;
        spec->multiout.max_channels = 2;
@@ -2456,7 +3006,6 @@ static int patch_cxt5066(struct hda_codec *codec)
        spec->input_mux = &cxt5066_capture_source;
 
        spec->port_d_mode = PIN_HP;
-       spec->ext_mic_bias = PIN_VREF80;
 
        spec->num_init_verbs = 1;
        spec->init_verbs[0] = cxt5066_init_verbs;
@@ -2483,25 +3032,69 @@ static int patch_cxt5066(struct hda_codec *codec)
                spec->dell_automute = 1;
                break;
        case CXT5066_OLPC_XO_1_5:
-               codec->patch_ops.unsol_event = cxt5066_unsol_event;
+               codec->patch_ops.init = cxt5066_olpc_init;
+               codec->patch_ops.unsol_event = cxt5066_olpc_unsol_event;
                spec->init_verbs[0] = cxt5066_init_verbs_olpc;
                spec->mixers[spec->num_mixers++] = cxt5066_mixer_master_olpc;
+               spec->mixers[spec->num_mixers++] = cxt5066_mixer_olpc_dc;
                spec->mixers[spec->num_mixers++] = cxt5066_mixers;
                spec->port_d_mode = 0;
-               spec->ext_mic_bias = CXT5066_OLPC_EXT_MIC_BIAS;
+               spec->mic_boost = 3; /* default 30dB gain */
 
                /* no S/PDIF out */
                spec->multiout.dig_out_nid = 0;
 
                /* input source automatically selected */
                spec->input_mux = NULL;
+
+               /* our capture hooks which allow us to turn on the microphone LED
+                * at the right time */
+               spec->capture_prepare = cxt5066_olpc_capture_prepare;
+               spec->capture_cleanup = cxt5066_olpc_capture_cleanup;
                break;
        case CXT5066_DELL_VOSTO:
+               codec->patch_ops.init = cxt5066_init;
                codec->patch_ops.unsol_event = cxt5066_vostro_event;
                spec->init_verbs[0] = cxt5066_init_verbs_vostro;
                spec->mixers[spec->num_mixers++] = cxt5066_mixer_master_olpc;
                spec->mixers[spec->num_mixers++] = cxt5066_mixers;
+               spec->mixers[spec->num_mixers++] = cxt5066_vostro_mixers;
+               spec->port_d_mode = 0;
+               spec->dell_vostro = 1;
+               spec->mic_boost = 3; /* default 30dB gain */
+               snd_hda_attach_beep_device(codec, 0x13);
+
+               /* no S/PDIF out */
+               spec->multiout.dig_out_nid = 0;
+
+               /* input source automatically selected */
+               spec->input_mux = NULL;
+               break;
+       case CXT5066_IDEAPAD:
+               codec->patch_ops.init = cxt5066_init;
+               codec->patch_ops.unsol_event = cxt5066_ideapad_event;
+               spec->mixers[spec->num_mixers++] = cxt5066_mixer_master;
+               spec->mixers[spec->num_mixers++] = cxt5066_mixers;
+               spec->init_verbs[0] = cxt5066_init_verbs_ideapad;
                spec->port_d_mode = 0;
+               spec->ideapad = 1;
+               spec->mic_boost = 2;    /* default 20dB gain */
+
+               /* no S/PDIF out */
+               spec->multiout.dig_out_nid = 0;
+
+               /* input source automatically selected */
+               spec->input_mux = NULL;
+               break;
+       case CXT5066_THINKPAD:
+               codec->patch_ops.init = cxt5066_init;
+               codec->patch_ops.unsol_event = cxt5066_thinkpad_event;
+               spec->mixers[spec->num_mixers++] = cxt5066_mixer_master;
+               spec->mixers[spec->num_mixers++] = cxt5066_mixers;
+               spec->init_verbs[0] = cxt5066_init_verbs_thinkpad;
+               spec->thinkpad = 1;
+               spec->port_d_mode = PIN_OUT;
+               spec->mic_boost = 2;    /* default 20dB gain */
 
                /* no S/PDIF out */
                spec->multiout.dig_out_nid = 0;
@@ -2528,6 +3121,8 @@ static struct hda_codec_preset snd_hda_preset_conexant[] = {
          .patch = patch_cxt5066 },
        { .id = 0x14f15067, .name = "CX20583 (Pebble HSF)",
          .patch = patch_cxt5066 },
+       { .id = 0x14f15069, .name = "CX20585",
+         .patch = patch_cxt5066 },
        {} /* terminator */
 };
 
@@ -2536,6 +3131,7 @@ MODULE_ALIAS("snd-hda-codec-id:14f15047");
 MODULE_ALIAS("snd-hda-codec-id:14f15051");
 MODULE_ALIAS("snd-hda-codec-id:14f15066");
 MODULE_ALIAS("snd-hda-codec-id:14f15067");
+MODULE_ALIAS("snd-hda-codec-id:14f15069");
 
 MODULE_LICENSE("GPL");
 MODULE_DESCRIPTION("Conexant HD-audio codec");