Skip to content

Commit

Permalink
ALSA: hda - Add the generic Headphone Mic feature
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Takashi Iwai committed Mar 7, 2013
1 parent 2dad940 commit 967303d
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 55 deletions.
3 changes: 3 additions & 0 deletions Documentation/sound/alsa/HD-Audio.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
143 changes: 89 additions & 54 deletions sound/pci/hda/hda_generic.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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++) {
Expand All @@ -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++) {
Expand Down Expand Up @@ -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 */
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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);
Expand All @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down
6 changes: 5 additions & 1 deletion sound/pci/hda/hda_generic.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 */
Expand Down

0 comments on commit 967303d

Please sign in to comment.