Commit 967303da authored by Takashi Iwai's avatar Takashi Iwai

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: default avatarTakashi Iwai <tiwai@suse.de>
parent 2dad9402
...@@ -466,6 +466,9 @@ The generic parser supports the following hints: ...@@ -466,6 +466,9 @@ The generic parser supports the following hints:
- add_in_jack_modes (bool): add "xxx Jack Mode" enum controls to each - add_in_jack_modes (bool): add "xxx Jack Mode" enum controls to each
input jack for allowing to change the mic bias vref input jack for allowing to change the mic bias vref
- power_down_unused (bool): power down the unused widgets - 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_nid (int): specifies the widget NID of the analog-loopback
mixer mixer
......
...@@ -159,6 +159,12 @@ static void parse_user_hints(struct hda_codec *codec) ...@@ -159,6 +159,12 @@ static void parse_user_hints(struct hda_codec *codec)
val = snd_hda_get_bool_hint(codec, "power_down_unused"); val = snd_hda_get_bool_hint(codec, "power_down_unused");
if (val >= 0) if (val >= 0)
spec->power_down_unused = !!val; 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)) if (!snd_hda_get_int_hint(codec, "mixer_nid", &val))
spec->mixer_nid = val; spec->mixer_nid = val;
...@@ -2194,63 +2200,97 @@ static int create_loopback_mixing_ctl(struct hda_codec *codec) ...@@ -2194,63 +2200,97 @@ static int create_loopback_mixing_ctl(struct hda_codec *codec)
static void call_update_outputs(struct hda_codec *codec); static void call_update_outputs(struct hda_codec *codec);
/* for shared I/O, change the pin-control accordingly */ /* 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; struct hda_gen_spec *spec = codec->spec;
bool as_mic;
unsigned int val; unsigned int val;
hda_nid_t pin = spec->autocfg.inputs[1].pin; hda_nid_t pin;
/* NOTE: this assumes that there are only two inputs, the
* first is the real internal mic and the second is HP/mic jack.
*/
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 if (!force) {
instead, as suggested by Realtek */ 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) { if (val == AC_PINCTL_VREF_HIZ && spec->shared_mic_vref_pin) {
const hda_nid_t vref_pin = 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); unsigned int vref_val = snd_hda_get_default_vref(codec, vref_pin);
if (vref_val != AC_PINCTL_VREF_HIZ) if (vref_val != AC_PINCTL_VREF_HIZ)
snd_hda_set_pin_ctl_cache(codec, vref_pin, 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); set_pin_target(codec, pin, val, true);
spec->automute_speaker = !set_as_mic; /* update HP auto-mute state too */
call_update_outputs(codec); 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 */ /* 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 hda_gen_spec *spec = codec->spec;
struct auto_pin_cfg *cfg = &spec->autocfg; struct auto_pin_cfg *cfg = &spec->autocfg;
unsigned int defcfg; unsigned int defcfg;
hda_nid_t nid; hda_nid_t nid;
/* only one internal input pin? */ if (!spec->hp_mic) {
if (cfg->num_inputs != 1) if (spec->suppress_hp_mic_detect)
return 0; return 0;
defcfg = snd_hda_codec_get_pincfg(codec, cfg->inputs[0].pin); /* automatic detection: only if no input or a single internal
if (snd_hda_get_input_pin_attr(defcfg) != INPUT_PIN_ATTR_INT) * 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; return 0;
if (cfg->hp_outs == 1 && cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) nid = 0;
nid = cfg->hp_pins[0]; /* OK, we have a single HP-out */ if (cfg->line_out_type == AUTO_PIN_HP_OUT && cfg->line_outs > 0)
else if (cfg->line_outs == 1 && cfg->line_out_type == AUTO_PIN_HP_OUT) nid = cfg->line_out_pins[0];
nid = cfg->line_out_pins[0]; /* OK, we have a single line-out */ else if (cfg->hp_outs > 0)
else nid = cfg->hp_pins[0];
return 0; /* both not available */ if (!nid)
return 0;
if (!(snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_IN)) if (!(snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_IN))
return 0; /* no input */ return 0; /* no input */
cfg->inputs[1].pin = nid; cfg->inputs[cfg->num_inputs].pin = nid;
cfg->inputs[1].type = AUTO_PIN_MIC; cfg->inputs[cfg->num_inputs].type = AUTO_PIN_MIC;
cfg->num_inputs = 2; cfg->num_inputs++;
spec->shared_mic_hp = 1; 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); snd_printdd("hda-codec: Enable shared I/O jack on NID 0x%x\n", nid);
return 0; return 0;
} }
...@@ -2602,7 +2642,6 @@ static int check_dyn_adc_switch(struct hda_codec *codec) ...@@ -2602,7 +2642,6 @@ static int check_dyn_adc_switch(struct hda_codec *codec)
unsigned int ok_bits; unsigned int ok_bits;
int i, n, nums; int i, n, nums;
again:
nums = 0; nums = 0;
ok_bits = 0; ok_bits = 0;
for (n = 0; n < spec->num_adc_nids; n++) { for (n = 0; n < spec->num_adc_nids; n++) {
...@@ -2617,12 +2656,6 @@ static int check_dyn_adc_switch(struct hda_codec *codec) ...@@ -2617,12 +2656,6 @@ static int check_dyn_adc_switch(struct hda_codec *codec)
} }
if (!ok_bits) { 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 */ /* check whether ADC-switch is possible */
for (i = 0; i < imux->num_items; i++) { for (i = 0; i < imux->num_items; i++) {
for (n = 0; n < spec->num_adc_nids; n++) { for (n = 0; n < spec->num_adc_nids; n++) {
...@@ -2655,7 +2688,8 @@ static int check_dyn_adc_switch(struct hda_codec *codec) ...@@ -2655,7 +2688,8 @@ static int check_dyn_adc_switch(struct hda_codec *codec)
spec->num_adc_nids = nums; 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"); snd_printdd("hda-codec: reducing to a single ADC\n");
spec->num_adc_nids = 1; /* reduce to a single ADC */ 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, ...@@ -2692,6 +2726,8 @@ static int parse_capture_source(struct hda_codec *codec, hda_nid_t pin,
snd_hda_get_path_idx(codec, path); snd_hda_get_path_idx(codec, path);
if (!imux_added) { if (!imux_added) {
if (spec->hp_mic_pin == pin)
spec->hp_mic_mux_idx = imux->num_items;
spec->imux_pins[imux->num_items] = pin; spec->imux_pins[imux->num_items] = pin;
snd_hda_add_imux_item(imux, label, cfg_idx, NULL); snd_hda_add_imux_item(imux, label, cfg_idx, NULL);
imux_added = true; imux_added = true;
...@@ -3416,8 +3452,8 @@ static int mux_select(struct hda_codec *codec, unsigned int adc_idx, ...@@ -3416,8 +3452,8 @@ static int mux_select(struct hda_codec *codec, unsigned int adc_idx,
spec->cur_mux[adc_idx] = idx; spec->cur_mux[adc_idx] = idx;
if (spec->shared_mic_hp) if (spec->hp_mic)
update_shared_mic_hp(codec, spec->cur_mux[adc_idx]); update_hp_mic(codec, adc_idx, false);
if (spec->dyn_adc_switch) if (spec->dyn_adc_switch)
dyn_adc_pcm_resetup(codec, idx); 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, ...@@ -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++) { for (i = 0; i < num_pins; i++) {
hda_nid_t nid = pins[i]; hda_nid_t nid = pins[i];
unsigned int val; unsigned int val, oldval;
if (!nid) if (!nid)
break; 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 /* don't reset VREF value in case it's controlling
* the amp (see alc861_fixup_asus_amp_vref_0f()) * the amp (see alc861_fixup_asus_amp_vref_0f())
*/ */
if (spec->keep_vref_in_automute) if (spec->keep_vref_in_automute)
val = snd_hda_codec_get_pin_target(codec, nid) & ~PIN_HP; val = oldval & ~PIN_HP;
else else
val = 0; val = 0;
if (!mute) 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 /* here we call update_pin_ctl() so that the pinctl is changed
* without changing the pinctl target value; * without changing the pinctl target value;
* the original target value will be still referred at the * the original target value will be still referred at the
...@@ -3497,8 +3536,7 @@ void snd_hda_gen_update_outputs(struct hda_codec *codec) ...@@ -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, * in general, HP pins/amps control should be enabled in all cases,
* but currently set only for master_mute, just to be safe * 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); spec->autocfg.hp_pins, spec->master_mute);
if (!spec->automute_speaker) if (!spec->automute_speaker)
...@@ -3978,7 +4016,7 @@ int snd_hda_gen_parse_auto_config(struct hda_codec *codec, ...@@ -3978,7 +4016,7 @@ int snd_hda_gen_parse_auto_config(struct hda_codec *codec,
err = create_loopback_mixing_ctl(codec); err = create_loopback_mixing_ctl(codec);
if (err < 0) if (err < 0)
return err; return err;
err = create_shared_input(codec); err = create_hp_mic(codec);
if (err < 0) if (err < 0)
return err; return err;
err = create_input_ctls(codec); err = create_input_ctls(codec);
...@@ -4004,11 +4042,9 @@ int snd_hda_gen_parse_auto_config(struct hda_codec *codec, ...@@ -4004,11 +4042,9 @@ int snd_hda_gen_parse_auto_config(struct hda_codec *codec,
if (err < 0) if (err < 0)
return err; return err;
if (!spec->shared_mic_hp) { err = check_auto_mic_availability(codec);
err = check_auto_mic_availability(codec); if (err < 0)
if (err < 0) return err;
return err;
}
err = create_capture_mixers(codec); err = create_capture_mixers(codec);
if (err < 0) if (err < 0)
...@@ -4115,9 +4151,9 @@ int snd_hda_gen_build_controls(struct hda_codec *codec) ...@@ -4115,9 +4151,9 @@ int snd_hda_gen_build_controls(struct hda_codec *codec)
free_kctls(spec); /* no longer needed */ free_kctls(spec); /* no longer needed */
if (spec->shared_mic_hp) { if (spec->hp_mic_pin) {
int err; 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); err = snd_hda_jack_add_kctl(codec, nid, "Headphone Mic", 0);
if (err < 0) if (err < 0)
return err; return err;
...@@ -4780,11 +4816,10 @@ static void init_input_src(struct hda_codec *codec) ...@@ -4780,11 +4816,10 @@ static void init_input_src(struct hda_codec *codec)
snd_hda_activate_path(codec, path, active, false); 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) if (spec->cap_sync_hook)
spec->cap_sync_hook(codec, NULL); spec->cap_sync_hook(codec, NULL);
} }
......
...@@ -145,7 +145,10 @@ struct hda_gen_spec { ...@@ -145,7 +145,10 @@ struct hda_gen_spec {
hda_nid_t private_dac_nids[AUTO_CFG_MAX_OUTS]; hda_nid_t private_dac_nids[AUTO_CFG_MAX_OUTS];
hda_nid_t imux_pins[HDA_MAX_NUM_INPUTS]; hda_nid_t imux_pins[HDA_MAX_NUM_INPUTS];
unsigned int dyn_adc_idx[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 shared_mic_vref_pin;
hda_nid_t hp_mic_pin;
int hp_mic_mux_idx;
/* DAC/ADC lists */ /* DAC/ADC lists */
int num_all_dacs; int num_all_dacs;
...@@ -200,7 +203,8 @@ struct hda_gen_spec { ...@@ -200,7 +203,8 @@ struct hda_gen_spec {
/* other parse behavior flags */ /* other parse behavior flags */
unsigned int need_dac_fix:1; /* need to limit DACs for multi channels */ 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 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 multi_cap_vol:1; /* allow multiple capture xxx volumes */
unsigned int inv_dmic_split:1; /* inverted dmic w/a for conexant */ unsigned int inv_dmic_split:1; /* inverted dmic w/a for conexant */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment