ALSA: hda - Add the generic Headphone Mic feature
authorTakashi Iwai <tiwai@suse.de>
Tue, 19 Feb 2013 16:12:42 +0000 (17:12 +0100)
committerTakashi Iwai <tiwai@suse.de>
Thu, 7 Mar 2013 17:29:52 +0000 (18:29 +0100)
This patch improves the generic parser code to allow to set up the
headphone jack as a mic input.  User can enable this feature by giving
hp_mic hint string.

The former shared hp/mic feature for the single built-in mic is still
retained.  This detection can be disabled now via hp_mic_detect hint
string, too.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
Documentation/sound/alsa/HD-Audio.txt
sound/pci/hda/hda_generic.c
sound/pci/hda/hda_generic.h

index d4faa63ff35212dac986af857afd5666a2b4d93b..77e176a35ce1c9a4f505e1207c9d2feaeb3b289c 100644 (file)
@@ -466,6 +466,9 @@ The generic parser supports the following hints:
 - add_in_jack_modes (bool): add "xxx Jack Mode" enum controls to each
   input jack for allowing to change the mic bias vref
 - power_down_unused (bool): power down the unused widgets
+- add_hp_mic (bool): add the headphone to capture source if possible
+- hp_mic_detect (bool): enable/disable the hp/mic shared input for a
+  single built-in mic case; default true
 - mixer_nid (int): specifies the widget NID of the analog-loopback
   mixer
 
index 78897d05d80f281d9bc8427a09a4da426f90dbe4..73de215da03f272b9cd734c2a3c5555bb0898908 100644 (file)
@@ -159,6 +159,12 @@ static void parse_user_hints(struct hda_codec *codec)
        val = snd_hda_get_bool_hint(codec, "power_down_unused");
        if (val >= 0)
                spec->power_down_unused = !!val;
+       val = snd_hda_get_bool_hint(codec, "add_hp_mic");
+       if (val >= 0)
+               spec->hp_mic = !!val;
+       val = snd_hda_get_bool_hint(codec, "hp_mic_detect");
+       if (val >= 0)
+               spec->suppress_hp_mic_detect = !val;
 
        if (!snd_hda_get_int_hint(codec, "mixer_nid", &val))
                spec->mixer_nid = val;
@@ -2194,63 +2200,97 @@ static int create_loopback_mixing_ctl(struct hda_codec *codec)
 static void call_update_outputs(struct hda_codec *codec);
 
 /* for shared I/O, change the pin-control accordingly */
-static void update_shared_mic_hp(struct hda_codec *codec, bool set_as_mic)
+static void update_hp_mic(struct hda_codec *codec, int adc_mux, bool force)
 {
        struct hda_gen_spec *spec = codec->spec;
+       bool as_mic;
        unsigned int val;
-       hda_nid_t pin = spec->autocfg.inputs[1].pin;
-       /* NOTE: this assumes that there are only two inputs, the
-        * first is the real internal mic and the second is HP/mic jack.
-        */
+       hda_nid_t pin;
 
-       val = snd_hda_get_default_vref(codec, pin);
+       pin = spec->hp_mic_pin;
+       as_mic = spec->cur_mux[adc_mux] == spec->hp_mic_mux_idx;
 
-       /* This pin does not have vref caps - let's enable vref on pin 0x18
-          instead, as suggested by Realtek */
+       if (!force) {
+               val = snd_hda_codec_get_pin_target(codec, pin);
+               if (as_mic) {
+                       if (val & PIN_IN)
+                               return;
+               } else {
+                       if (val & PIN_OUT)
+                               return;
+               }
+       }
+
+       val = snd_hda_get_default_vref(codec, pin);
+       /* if the HP pin doesn't support VREF and the codec driver gives an
+        * alternative pin, set up the VREF on that pin instead
+        */
        if (val == AC_PINCTL_VREF_HIZ && spec->shared_mic_vref_pin) {
                const hda_nid_t vref_pin = spec->shared_mic_vref_pin;
                unsigned int vref_val = snd_hda_get_default_vref(codec, vref_pin);
                if (vref_val != AC_PINCTL_VREF_HIZ)
                        snd_hda_set_pin_ctl_cache(codec, vref_pin,
-                                       PIN_IN | (set_as_mic ? vref_val : 0));
+                                                 PIN_IN | (as_mic ? vref_val : 0));
        }
 
-       val = set_as_mic ? val | PIN_IN : PIN_HP;
+       if (as_mic)
+               val |= PIN_IN;
+       else
+               val = PIN_HP;
        set_pin_target(codec, pin, val, true);
 
-       spec->automute_speaker = !set_as_mic;
-       call_update_outputs(codec);
+       /* update HP auto-mute state too */
+       if (spec->hp_automute_hook)
+               spec->hp_automute_hook(codec, NULL);
+       else
+               snd_hda_gen_hp_automute(codec, NULL);
 }
 
 /* create a shared input with the headphone out */
-static int create_shared_input(struct hda_codec *codec)
+static int create_hp_mic(struct hda_codec *codec)
 {
        struct hda_gen_spec *spec = codec->spec;
        struct auto_pin_cfg *cfg = &spec->autocfg;
        unsigned int defcfg;
        hda_nid_t nid;
 
-       /* only one internal input pin? */
-       if (cfg->num_inputs != 1)
-               return 0;
-       defcfg = snd_hda_codec_get_pincfg(codec, cfg->inputs[0].pin);
-       if (snd_hda_get_input_pin_attr(defcfg) != INPUT_PIN_ATTR_INT)
+       if (!spec->hp_mic) {
+               if (spec->suppress_hp_mic_detect)
+                       return 0;
+               /* automatic detection: only if no input or a single internal
+                * input pin is found, try to detect the shared hp/mic
+                */
+               if (cfg->num_inputs > 1)
+                       return 0;
+               else if (cfg->num_inputs == 1) {
+                       defcfg = snd_hda_codec_get_pincfg(codec, cfg->inputs[0].pin);
+                       if (snd_hda_get_input_pin_attr(defcfg) != INPUT_PIN_ATTR_INT)
+                               return 0;
+               }
+       }
+
+       spec->hp_mic = 0; /* clear once */
+       if (cfg->num_inputs >= AUTO_CFG_MAX_INS)
                return 0;
 
-       if (cfg->hp_outs == 1 && cfg->line_out_type == AUTO_PIN_SPEAKER_OUT)
-               nid = cfg->hp_pins[0]; /* OK, we have a single HP-out */
-       else if (cfg->line_outs == 1 && cfg->line_out_type == AUTO_PIN_HP_OUT)
-               nid = cfg->line_out_pins[0]; /* OK, we have a single line-out */
-       else
-               return 0; /* both not available */
+       nid = 0;
+       if (cfg->line_out_type == AUTO_PIN_HP_OUT && cfg->line_outs > 0)
+               nid = cfg->line_out_pins[0];
+       else if (cfg->hp_outs > 0)
+               nid = cfg->hp_pins[0];
+       if (!nid)
+               return 0;
 
        if (!(snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_IN))
                return 0; /* no input */
 
-       cfg->inputs[1].pin = nid;
-       cfg->inputs[1].type = AUTO_PIN_MIC;
-       cfg->num_inputs = 2;
-       spec->shared_mic_hp = 1;
+       cfg->inputs[cfg->num_inputs].pin = nid;
+       cfg->inputs[cfg->num_inputs].type = AUTO_PIN_MIC;
+       cfg->num_inputs++;
+       spec->hp_mic = 1;
+       spec->hp_mic_pin = nid;
+       /* we can't handle auto-mic together with HP-mic */
+       spec->suppress_auto_mic = 1;
        snd_printdd("hda-codec: Enable shared I/O jack on NID 0x%x\n", nid);
        return 0;
 }
@@ -2602,7 +2642,6 @@ static int check_dyn_adc_switch(struct hda_codec *codec)
        unsigned int ok_bits;
        int i, n, nums;
 
- again:
        nums = 0;
        ok_bits = 0;
        for (n = 0; n < spec->num_adc_nids; n++) {
@@ -2617,12 +2656,6 @@ static int check_dyn_adc_switch(struct hda_codec *codec)
        }
 
        if (!ok_bits) {
-               if (spec->shared_mic_hp) {
-                       spec->shared_mic_hp = 0;
-                       imux->num_items = 1;
-                       goto again;
-               }
-
                /* check whether ADC-switch is possible */
                for (i = 0; i < imux->num_items; i++) {
                        for (n = 0; n < spec->num_adc_nids; n++) {
@@ -2655,7 +2688,8 @@ static int check_dyn_adc_switch(struct hda_codec *codec)
                spec->num_adc_nids = nums;
        }
 
-       if (imux->num_items == 1 || spec->shared_mic_hp) {
+       if (imux->num_items == 1 ||
+           (imux->num_items == 2 && spec->hp_mic)) {
                snd_printdd("hda-codec: reducing to a single ADC\n");
                spec->num_adc_nids = 1; /* reduce to a single ADC */
        }
@@ -2692,6 +2726,8 @@ static int parse_capture_source(struct hda_codec *codec, hda_nid_t pin,
                        snd_hda_get_path_idx(codec, path);
 
                if (!imux_added) {
+                       if (spec->hp_mic_pin == pin)
+                               spec->hp_mic_mux_idx = imux->num_items;
                        spec->imux_pins[imux->num_items] = pin;
                        snd_hda_add_imux_item(imux, label, cfg_idx, NULL);
                        imux_added = true;
@@ -3416,8 +3452,8 @@ static int mux_select(struct hda_codec *codec, unsigned int adc_idx,
 
        spec->cur_mux[adc_idx] = idx;
 
-       if (spec->shared_mic_hp)
-               update_shared_mic_hp(codec, spec->cur_mux[adc_idx]);
+       if (spec->hp_mic)
+               update_hp_mic(codec, adc_idx, false);
 
        if (spec->dyn_adc_switch)
                dyn_adc_pcm_resetup(codec, idx);
@@ -3465,18 +3501,21 @@ static void do_automute(struct hda_codec *codec, int num_pins, hda_nid_t *pins,
 
        for (i = 0; i < num_pins; i++) {
                hda_nid_t nid = pins[i];
-               unsigned int val;
+               unsigned int val, oldval;
                if (!nid)
                        break;
+               oldval = snd_hda_codec_get_pin_target(codec, nid);
+               if (oldval & PIN_IN)
+                       continue; /* no mute for inputs */
                /* don't reset VREF value in case it's controlling
                 * the amp (see alc861_fixup_asus_amp_vref_0f())
                 */
                if (spec->keep_vref_in_automute)
-                       val = snd_hda_codec_get_pin_target(codec, nid) & ~PIN_HP;
+                       val = oldval & ~PIN_HP;
                else
                        val = 0;
                if (!mute)
-                       val |= snd_hda_codec_get_pin_target(codec, nid);
+                       val |= oldval;
                /* here we call update_pin_ctl() so that the pinctl is changed
                 * without changing the pinctl target value;
                 * the original target value will be still referred at the
@@ -3497,8 +3536,7 @@ void snd_hda_gen_update_outputs(struct hda_codec *codec)
         * in general, HP pins/amps control should be enabled in all cases,
         * but currently set only for master_mute, just to be safe
         */
-       if (!spec->shared_mic_hp) /* don't change HP-pin when shared with mic */
-               do_automute(codec, ARRAY_SIZE(spec->autocfg.hp_pins),
+       do_automute(codec, ARRAY_SIZE(spec->autocfg.hp_pins),
                    spec->autocfg.hp_pins, spec->master_mute);
 
        if (!spec->automute_speaker)
@@ -3978,7 +4016,7 @@ int snd_hda_gen_parse_auto_config(struct hda_codec *codec,
        err = create_loopback_mixing_ctl(codec);
        if (err < 0)
                return err;
-       err = create_shared_input(codec);
+       err = create_hp_mic(codec);
        if (err < 0)
                return err;
        err = create_input_ctls(codec);
@@ -4004,11 +4042,9 @@ int snd_hda_gen_parse_auto_config(struct hda_codec *codec,
        if (err < 0)
                return err;
 
-       if (!spec->shared_mic_hp) {
-               err = check_auto_mic_availability(codec);
-               if (err < 0)
-                       return err;
-       }
+       err = check_auto_mic_availability(codec);
+       if (err < 0)
+               return err;
 
        err = create_capture_mixers(codec);
        if (err < 0)
@@ -4115,9 +4151,9 @@ int snd_hda_gen_build_controls(struct hda_codec *codec)
 
        free_kctls(spec); /* no longer needed */
 
-       if (spec->shared_mic_hp) {
+       if (spec->hp_mic_pin) {
                int err;
-               int nid = spec->autocfg.inputs[1].pin;
+               int nid = spec->hp_mic_pin;
                err = snd_hda_jack_add_kctl(codec, nid, "Headphone Mic", 0);
                if (err < 0)
                        return err;
@@ -4780,11 +4816,10 @@ static void init_input_src(struct hda_codec *codec)
                                snd_hda_activate_path(codec, path, active, false);
                        }
                }
+               if (spec->hp_mic)
+                       update_hp_mic(codec, c, true);
        }
 
-       if (spec->shared_mic_hp)
-               update_shared_mic_hp(codec, spec->cur_mux[0]);
-
        if (spec->cap_sync_hook)
                spec->cap_sync_hook(codec, NULL);
 }
index 009b57be96d36e4ee9103794404edf968d009748..7ee5b57946c9734e2d92ef97a9ddbf7084837e97 100644 (file)
@@ -145,7 +145,10 @@ struct hda_gen_spec {
        hda_nid_t private_dac_nids[AUTO_CFG_MAX_OUTS];
        hda_nid_t imux_pins[HDA_MAX_NUM_INPUTS];
        unsigned int dyn_adc_idx[HDA_MAX_NUM_INPUTS];
+       /* shared hp/mic */
        hda_nid_t shared_mic_vref_pin;
+       hda_nid_t hp_mic_pin;
+       int hp_mic_mux_idx;
 
        /* DAC/ADC lists */
        int num_all_dacs;
@@ -200,7 +203,8 @@ struct hda_gen_spec {
 
        /* other parse behavior flags */
        unsigned int need_dac_fix:1; /* need to limit DACs for multi channels */
-       unsigned int shared_mic_hp:1; /* HP/Mic-in sharing */
+       unsigned int hp_mic:1; /* Allow HP as a mic-in */
+       unsigned int suppress_hp_mic_detect:1; /* Don't detect HP/mic */
        unsigned int no_primary_hp:1; /* Don't prefer HP pins to speaker pins */
        unsigned int multi_cap_vol:1; /* allow multiple capture xxx volumes */
        unsigned int inv_dmic_split:1; /* inverted dmic w/a for conexant */