From 967303dabc22335e83c6ee4a9e0684a7c05da976 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 19 Feb 2013 17:12:42 +0100 Subject: ALSA: hda - Add the generic Headphone Mic feature 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 diff --git a/Documentation/sound/alsa/HD-Audio.txt b/Documentation/sound/alsa/HD-Audio.txt index d4faa63..77e176a 100644 --- a/Documentation/sound/alsa/HD-Audio.txt +++ b/Documentation/sound/alsa/HD-Audio.txt @@ -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 diff --git a/sound/pci/hda/hda_generic.c b/sound/pci/hda/hda_generic.c index 78897d0..73de215 100644 --- a/sound/pci/hda/hda_generic.c +++ b/sound/pci/hda/hda_generic.c @@ -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); } diff --git a/sound/pci/hda/hda_generic.h b/sound/pci/hda/hda_generic.h index 009b57b..7ee5b57 100644 --- a/sound/pci/hda/hda_generic.h +++ b/sound/pci/hda/hda_generic.h @@ -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 */ -- cgit v0.10.2