ALSA: hda - Use only one capture stream for auto-mic
[pandora-kernel.git] / sound / pci / hda / patch_realtek.c
index 587d94f..7ecf929 100644 (file)
@@ -258,6 +258,14 @@ enum {
        ALC_INIT_GPIO3,
 };
 
+struct alc_mic_route {
+       hda_nid_t pin;
+       unsigned char mux_idx;
+       unsigned char amix_idx;
+};
+
+#define MUX_IDX_UNDEF  ((unsigned char)-1)
+
 struct alc_spec {
        /* codec parameterization */
        struct snd_kcontrol_new *mixers[5];     /* mixer arrays */
@@ -271,13 +279,13 @@ struct alc_spec {
                                                 */
        unsigned int num_init_verbs;
 
-       char stream_name_analog[16];    /* analog PCM stream */
+       char stream_name_analog[32];    /* analog PCM stream */
        struct hda_pcm_stream *stream_analog_playback;
        struct hda_pcm_stream *stream_analog_capture;
        struct hda_pcm_stream *stream_analog_alt_playback;
        struct hda_pcm_stream *stream_analog_alt_capture;
 
-       char stream_name_digital[16];   /* digital PCM stream */
+       char stream_name_digital[32];   /* digital PCM stream */
        struct hda_pcm_stream *stream_digital_playback;
        struct hda_pcm_stream *stream_digital_capture;
 
@@ -300,6 +308,8 @@ struct alc_spec {
        unsigned int num_mux_defs;
        const struct hda_input_mux *input_mux;
        unsigned int cur_mux[3];
+       struct alc_mic_route ext_mic;
+       struct alc_mic_route int_mic;
 
        /* channel model */
        const struct hda_channel_mode *channel_mode;
@@ -327,6 +337,7 @@ struct alc_spec {
        unsigned int sense_updated: 1;
        unsigned int jack_present: 1;
        unsigned int master_sw: 1;
+       unsigned int auto_mic:1;
 
        /* other flags */
        unsigned int no_analog :1; /* digital I/O only */
@@ -557,7 +568,7 @@ static int alc_pin_mode_get(struct snd_kcontrol *kcontrol,
 
        /* Find enumerated value for current pinctl setting */
        i = alc_pin_mode_min(dir);
-       while (alc_pin_mode_values[i] != pinctl && i <= alc_pin_mode_max(dir))
+       while (i <= alc_pin_mode_max(dir) && alc_pin_mode_values[i] != pinctl)
                i++;
        *valp = i <= alc_pin_mode_max(dir) ? i: alc_pin_mode_min(dir);
        return 0;
@@ -963,30 +974,79 @@ static void alc_automute_pin(struct hda_codec *codec)
        }
 }
 
-#if 0 /* it's broken in some cases -- temporarily disabled */
+static int get_connection_index(struct hda_codec *codec, hda_nid_t mux,
+                               hda_nid_t nid)
+{
+       hda_nid_t conn[HDA_MAX_NUM_INPUTS];
+       int i, nums;
+
+       nums = snd_hda_get_connections(codec, mux, conn, ARRAY_SIZE(conn));
+       for (i = 0; i < nums; i++)
+               if (conn[i] == nid)
+                       return i;
+       return -1;
+}
+
+static int set_mic_mux_idx(struct hda_codec *codec, hda_nid_t cap,
+                          struct alc_mic_route *mic)
+{
+       int idx = get_connection_index(codec, cap, mic->pin);
+       if (idx < 0)
+               return 1; /* invalid */
+       mic->mux_idx = idx;
+       return 0;
+}
+
 static void alc_mic_automute(struct hda_codec *codec)
 {
        struct alc_spec *spec = codec->spec;
-       unsigned int present;
-       unsigned int mic_nid = spec->autocfg.input_pins[AUTO_PIN_MIC];
-       unsigned int fmic_nid = spec->autocfg.input_pins[AUTO_PIN_FRONT_MIC];
-       unsigned int mix_nid = spec->capsrc_nids[0];
-       unsigned int capsrc_idx_mic, capsrc_idx_fmic;
-
-       capsrc_idx_mic = mic_nid - 0x18;
-       capsrc_idx_fmic = fmic_nid - 0x18;
-       present = snd_hda_codec_read(codec, mic_nid, 0,
-                                    AC_VERB_GET_PIN_SENSE, 0) & 0x80000000;
-       snd_hda_codec_write(codec, mix_nid, 0, AC_VERB_SET_AMP_GAIN_MUTE,
-                   0x7000 | (capsrc_idx_mic << 8) | (present ? 0 : 0x80));
-       snd_hda_codec_write(codec, mix_nid, 0, AC_VERB_SET_AMP_GAIN_MUTE,
-                   0x7000 | (capsrc_idx_fmic << 8) | (present ? 0x80 : 0));
-       snd_hda_codec_amp_stereo(codec, 0x0b, HDA_INPUT, capsrc_idx_fmic,
-                        HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0);
+       struct alc_mic_route *dead, *alive;
+       unsigned int present, type;
+       hda_nid_t cap_nid;
+
+       if (!spec->int_mic.pin || !spec->ext_mic.pin)
+               return;
+       if (snd_BUG_ON(!spec->adc_nids))
+               return;
+
+       cap_nid = spec->capsrc_nids ? spec->capsrc_nids[0] : spec->adc_nids[0];
+
+       present = snd_hda_codec_read(codec, spec->ext_mic.pin, 0,
+                                    AC_VERB_GET_PIN_SENSE, 0);
+       present &= AC_PINSENSE_PRESENCE;
+       if (present) {
+               alive = &spec->ext_mic;
+               dead = &spec->int_mic;
+       } else {
+               alive = &spec->int_mic;
+               dead = &spec->ext_mic;
+       }
+
+       if (alive->mux_idx == MUX_IDX_UNDEF &&
+           set_mic_mux_idx(codec, cap_nid, alive))
+               return;
+       if (dead->mux_idx == MUX_IDX_UNDEF &&
+           set_mic_mux_idx(codec, cap_nid, dead))
+               return;
+
+       type = get_wcaps_type(get_wcaps(codec, cap_nid));
+       if (type == AC_WID_AUD_MIX) {
+               /* Matrix-mixer style (e.g. ALC882) */
+               snd_hda_codec_amp_stereo(codec, cap_nid, HDA_INPUT,
+                                        alive->mux_idx,
+                                        HDA_AMP_MUTE, 0);
+               snd_hda_codec_amp_stereo(codec, cap_nid, HDA_INPUT,
+                                        dead->mux_idx,
+                                        HDA_AMP_MUTE, HDA_AMP_MUTE);
+       } else {
+               /* MUX style (e.g. ALC880) */
+               snd_hda_codec_write_cache(codec, cap_nid, 0,
+                                         AC_VERB_SET_CONNECT_SEL,
+                                         alive->mux_idx);
+       }
+
+       /* FIXME: analog mixer */
 }
-#else
-#define alc_mic_automute(codec) do {} while(0) /* NOP */
-#endif /* disabled */
 
 /* unsolicited event for HP jack sensing */
 static void alc_sku_unsol_event(struct hda_codec *codec, unsigned int res)
@@ -1142,6 +1202,55 @@ static void alc_init_auto_hp(struct hda_codec *codec)
        spec->unsol_event = alc_sku_unsol_event;
 }
 
+static void alc_init_auto_mic(struct hda_codec *codec)
+{
+       struct alc_spec *spec = codec->spec;
+       struct auto_pin_cfg *cfg = &spec->autocfg;
+       hda_nid_t fixed, ext;
+       int i;
+
+       /* there must be only two mic inputs exclusively */
+       for (i = AUTO_PIN_LINE; i < AUTO_PIN_LAST; i++)
+               if (cfg->input_pins[i])
+                       return;
+
+       fixed = ext = 0;
+       for (i = AUTO_PIN_MIC; i <= AUTO_PIN_FRONT_MIC; i++) {
+               hda_nid_t nid = cfg->input_pins[i];
+               unsigned int defcfg;
+               if (!nid)
+                       return;
+               defcfg = snd_hda_codec_get_pincfg(codec, nid);
+               switch (get_defcfg_connect(defcfg)) {
+               case AC_JACK_PORT_FIXED:
+                       if (fixed)
+                               return; /* already occupied */
+                       fixed = nid;
+                       break;
+               case AC_JACK_PORT_COMPLEX:
+                       if (ext)
+                               return; /* already occupied */
+                       ext = nid;
+                       break;
+               default:
+                       return; /* invalid entry */
+               }
+       }
+       if (!(get_wcaps(codec, ext) & AC_WCAP_UNSOL_CAP))
+               return; /* no unsol support */
+       snd_printdd("realtek: Enable auto-mic switch on NID 0x%x/0x%x\n",
+                   ext, fixed);
+       spec->ext_mic.pin = ext;
+       spec->int_mic.pin = fixed;
+       spec->ext_mic.mux_idx = MUX_IDX_UNDEF; /* set later */
+       spec->int_mic.mux_idx = MUX_IDX_UNDEF; /* set later */
+       spec->auto_mic = 1;
+       snd_hda_codec_write_cache(codec, spec->ext_mic.pin, 0,
+                                 AC_VERB_SET_UNSOLICITED_ENABLE,
+                                 AC_USRSP_EN | ALC880_MIC_EVENT);
+       spec->unsol_event = alc_sku_unsol_event;
+}
+
 /* check subsystem ID and set up device-specific initialization;
  * return 1 if initialized, 0 if invalid SSID
  */
@@ -1243,6 +1352,7 @@ do_sku:
        }
 
        alc_init_auto_hp(codec);
+       alc_init_auto_mic(codec);
        return 1;
 }
 
@@ -1255,6 +1365,7 @@ static void alc_ssid_check(struct hda_codec *codec,
                           "Enable default setup for auto mode as fallback\n");
                spec->init_amp = ALC_INIT_DEFAULT;
                alc_init_auto_hp(codec);
+               alc_init_auto_mic(codec);
        }
 }
 
@@ -4572,7 +4683,10 @@ static void set_capture_mixer(struct alc_spec *spec)
        };
        if (spec->num_adc_nids > 0 && spec->num_adc_nids <= 3) {
                int mux;
-               if (spec->input_mux && spec->input_mux->num_items > 1)
+               if (spec->auto_mic) {
+                       mux = 0;
+                       spec->num_adc_nids = 1; /* support only one ADC */
+               } else if (spec->input_mux && spec->input_mux->num_items > 1)
                        mux = 1;
                else
                        mux = 0;
@@ -13372,6 +13486,8 @@ static int patch_alc269(struct hda_codec *codec)
                set_capture_mixer(spec);
        set_beep_amp(spec, 0x0b, 0x04, HDA_INPUT);
 
+       spec->vmaster_nid = 0x02;
+
        codec->patch_ops = alc_patch_ops;
        if (board_config == ALC269_AUTO)
                spec->init_hook = alc269_auto_init;
@@ -15018,7 +15134,7 @@ static struct snd_pci_quirk alc861vd_cfg_tbl[] = {
        SND_PCI_QUIRK(0x10de, 0x03f0, "Realtek ALC660 demo", ALC660VD_3ST),
        SND_PCI_QUIRK(0x1179, 0xff00, "Toshiba A135", ALC861VD_LENOVO),
        /*SND_PCI_QUIRK(0x1179, 0xff00, "DALLAS", ALC861VD_DALLAS),*/ /*lenovo*/
-       SND_PCI_QUIRK(0x1179, 0xff01, "DALLAS", ALC861VD_DALLAS),
+       SND_PCI_QUIRK(0x1179, 0xff01, "Toshiba A135", ALC861VD_LENOVO),
        SND_PCI_QUIRK(0x1179, 0xff03, "Toshiba P205", ALC861VD_LENOVO),
        SND_PCI_QUIRK(0x1179, 0xff31, "Toshiba L30-149", ALC861VD_DALLAS),
        SND_PCI_QUIRK(0x1565, 0x820d, "Biostar NF61S SE", ALC861VD_6ST_DIG),
@@ -17116,25 +17232,6 @@ static int alc662_auto_create_extra_out(struct alc_spec *spec, hda_nid_t pin,
        return 0;
 }
 
-/* return the index of the src widget from the connection list of the nid.
- * return -1 if not found
- */
-static int alc662_input_pin_idx(struct hda_codec *codec, hda_nid_t nid,
-                               hda_nid_t src)
-{
-       hda_nid_t conn_list[HDA_MAX_CONNECTIONS];
-       int i, conns;
-
-       conns = snd_hda_get_connections(codec, nid, conn_list,
-                                       ARRAY_SIZE(conn_list));
-       if (conns < 0)
-               return -1;
-       for (i = 0; i < conns; i++)
-               if (conn_list[i] == src)
-                       return i;
-       return -1;
-}
-
 static int alc662_is_input_pin(struct hda_codec *codec, hda_nid_t nid)
 {
        unsigned int pincap = snd_hda_query_pin_caps(codec, nid);
@@ -17151,7 +17248,7 @@ static int alc662_auto_create_analog_input_ctls(struct hda_codec *codec,
 
        for (i = 0; i < AUTO_PIN_LAST; i++) {
                if (alc662_is_input_pin(codec, cfg->input_pins[i])) {
-                       idx = alc662_input_pin_idx(codec, 0x0b,
+                       idx = get_connection_index(codec, 0x0b,
                                                   cfg->input_pins[i]);
                        if (idx >= 0) {
                                err = new_analog_input(spec, cfg->input_pins[i],
@@ -17160,7 +17257,7 @@ static int alc662_auto_create_analog_input_ctls(struct hda_codec *codec,
                                if (err < 0)
                                        return err;
                        }
-                       idx = alc662_input_pin_idx(codec, 0x22,
+                       idx = get_connection_index(codec, 0x22,
                                                   cfg->input_pins[i]);
                        if (idx >= 0) {
                                imux->items[imux->num_items].label =