From 3d21d3f7e7032619f5c5b47d3ee23bbe45de5993 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 29 Jul 2009 14:32:56 +0200 Subject: [PATCH] ALSA: hda - Support auto-mic switching with IDT/STAC codec Support the automatic mic-switching with some devices with IDT/STAC codecs. The condition is that the device has only two inputs, one for an external mic and one for an internal mic. Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_sigmatel.c | 170 +++++++++++++++++++++++++++++---- 1 file changed, 154 insertions(+), 16 deletions(-) diff --git a/sound/pci/hda/patch_sigmatel.c b/sound/pci/hda/patch_sigmatel.c index 307e86ceede1..f39dc98519a1 100644 --- a/sound/pci/hda/patch_sigmatel.c +++ b/sound/pci/hda/patch_sigmatel.c @@ -40,6 +40,7 @@ enum { STAC_INSERT_EVENT, STAC_PWR_EVENT, STAC_HP_EVENT, + STAC_MIC_EVENT, }; enum { @@ -176,6 +177,12 @@ struct sigmatel_jack { struct snd_jack *jack; }; +struct sigmatel_mic_route { + hda_nid_t pin; + unsigned char mux_idx; + unsigned char dmux_idx; +}; + struct sigmatel_spec { struct snd_kcontrol_new *mixers[4]; unsigned int num_mixers; @@ -187,6 +194,7 @@ struct sigmatel_spec { unsigned int hp_detect: 1; unsigned int spdif_mute: 1; unsigned int check_volume_offset:1; + unsigned int auto_mic:1; /* gpio lines */ unsigned int eapd_mask; @@ -243,6 +251,9 @@ struct sigmatel_spec { unsigned long *capsws; /* amp-mute attr: HDA_COMPOSE_AMP_VAL() */ unsigned int num_caps; /* number of capture volume/switch elements */ + struct sigmatel_mic_route ext_mic; + struct sigmatel_mic_route int_mic; + const char **spdif_labels; hda_nid_t dig_in_nid; @@ -1304,7 +1315,7 @@ static int stac92xx_build_controls(struct hda_codec *codec) if (err < 0) return err; } - if (spec->num_dmuxes > 0) { + if (!spec->auto_mic && spec->num_dmuxes > 0) { stac_dmux_mixer.count = spec->num_dmuxes; err = snd_hda_ctl_add(codec, snd_ctl_new1(&stac_dmux_mixer, codec)); @@ -2950,6 +2961,8 @@ static int stac92xx_add_input_source(struct sigmatel_spec *spec) struct snd_kcontrol_new *knew; struct hda_input_mux *imux = &spec->private_imux; + if (spec->auto_mic) + return 0; /* no need for input source */ if (!spec->num_adcs || imux->num_items <= 1) return 0; /* no need for input source control */ knew = stac_control_new(spec, &stac_input_src_temp, @@ -3557,14 +3570,26 @@ static const char *stac92xx_dmic_labels[5] = { "Digital Mic 3", "Digital Mic 4" }; +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; +} + /* create playback/capture controls for input pins on dmic capable codecs */ static int stac92xx_auto_create_dmic_input_ctls(struct hda_codec *codec, const struct auto_pin_cfg *cfg) { struct sigmatel_spec *spec = codec->spec; struct hda_input_mux *dimux = &spec->private_dimux; - hda_nid_t con_lst[HDA_MAX_NUM_INPUTS]; - int err, i, j; + int err, i; char name[32]; dimux->items[dimux->num_items].label = stac92xx_dmic_labels[0]; @@ -3574,7 +3599,6 @@ static int stac92xx_auto_create_dmic_input_ctls(struct hda_codec *codec, for (i = 0; i < spec->num_dmics; i++) { hda_nid_t nid; int index; - int num_cons; unsigned int wcaps; unsigned int def_conf; @@ -3583,17 +3607,10 @@ static int stac92xx_auto_create_dmic_input_ctls(struct hda_codec *codec, continue; nid = spec->dmic_nids[i]; - num_cons = snd_hda_get_connections(codec, - spec->dmux_nids[0], - con_lst, - HDA_MAX_NUM_INPUTS); - for (j = 0; j < num_cons; j++) - if (con_lst[j] == nid) { - index = j; - goto found; - } - continue; -found: + index = get_connection_index(codec, spec->dmux_nids[0], nid); + if (index < 0) + continue; + wcaps = get_wcaps(codec, nid) & (AC_WCAP_OUT_AMP | AC_WCAP_IN_AMP); @@ -3620,6 +3637,88 @@ found: return 0; } +static int check_mic_pin(struct hda_codec *codec, hda_nid_t nid, + hda_nid_t *fixed, hda_nid_t *ext) +{ + unsigned int cfg; + + if (!nid) + return 0; + cfg = snd_hda_codec_get_pincfg(codec, nid); + switch (get_defcfg_connect(cfg)) { + case AC_JACK_PORT_FIXED: + if (*fixed) + return 1; /* already occupied */ + *fixed = nid; + break; + case AC_JACK_PORT_COMPLEX: + if (*ext) + return 1; /* already occupied */ + *ext = nid; + break; + } + return 0; +} + +static int set_mic_route(struct hda_codec *codec, + struct sigmatel_mic_route *mic, + hda_nid_t pin) +{ + struct sigmatel_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + int i; + + mic->pin = pin; + for (i = AUTO_PIN_MIC; i <= AUTO_PIN_FRONT_MIC; i++) + if (pin == cfg->input_pins[i]) + break; + if (i <= AUTO_PIN_FRONT_MIC) { + /* analog pin */ + mic->dmux_idx = 0; + i = get_connection_index(codec, spec->mux_nids[0], pin); + if (i < 0) + return -1; + mic->mux_idx = i; + } else { + /* digital pin */ + mic->mux_idx = 0; + i = get_connection_index(codec, spec->dmux_nids[0], pin); + if (i < 0) + return -1; + mic->dmux_idx = i; + } + return 0; +} + +/* return non-zero if the device is for automatic mic switch */ +static int stac_check_auto_mic(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + hda_nid_t fixed, ext; + int i; + + for (i = AUTO_PIN_LINE; i < AUTO_PIN_LAST; i++) { + if (cfg->input_pins[i]) + return 0; /* must be exclusively mics */ + } + fixed = ext = 0; + for (i = AUTO_PIN_MIC; i <= AUTO_PIN_FRONT_MIC; i++) + if (check_mic_pin(codec, cfg->input_pins[i], &fixed, &ext)) + return 0; + for (i = 0; i < spec->num_dmics; i++) + if (check_mic_pin(codec, spec->dmic_nids[i], &fixed, &ext)) + return 0; + if (!fixed || !ext) + return 0; + if (!(get_wcaps(codec, ext) & AC_WCAP_UNSOL_CAP)) + return 0; /* no unsol support */ + if (set_mic_route(codec, &spec->ext_mic, ext) || + set_mic_route(codec, &spec->int_mic, fixed)) + return 0; /* something is wrong */ + return 1; +} + /* create playback/capture controls for input pins */ static int stac92xx_auto_create_analog_input_ctls(struct hda_codec *codec, const struct auto_pin_cfg *cfg) { @@ -3837,6 +3936,14 @@ static int stac92xx_parse_auto_config(struct hda_codec *codec, hda_nid_t dig_out spec->autocfg.line_outs = 0; } + if (stac_check_auto_mic(codec)) { + spec->auto_mic = 1; + /* only one capture for auto-mic */ + spec->num_adcs = 1; + spec->num_caps = 1; + spec->num_muxes = 1; + } + for (i = 0; i < spec->num_caps; i++) { err = stac92xx_add_capvol_ctls(codec, spec->capvols[i], spec->capsws[i], i); @@ -4264,6 +4371,10 @@ static int stac92xx_init(struct hda_codec *codec) for (i = 0; i < cfg->hp_outs; i++) stac_toggle_power_map(codec, cfg->hp_pins[i], 1); } + if (spec->auto_mic) { + if (enable_pin_detect(codec, spec->ext_mic.pin, STAC_MIC_EVENT)) + stac_issue_unsol_event(codec, spec->ext_mic.pin); + } for (i = 0; i < AUTO_PIN_LAST; i++) { hda_nid_t nid = cfg->input_pins[i]; if (nid) { @@ -4601,6 +4712,25 @@ static void stac92xx_report_jack(struct hda_codec *codec, hda_nid_t nid) } } +static void stac92xx_mic_detect(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + struct sigmatel_mic_route *mic; + + if (get_pin_presence(codec, spec->ext_mic.pin)) + mic = &spec->ext_mic; + else + mic = &spec->int_mic; + if (mic->dmux_idx) + snd_hda_codec_write_cache(codec, spec->dmux_nids[0], 0, + AC_VERB_SET_CONNECT_SEL, + mic->dmux_idx); + else + snd_hda_codec_write_cache(codec, spec->mux_nids[0], 0, + AC_VERB_SET_CONNECT_SEL, + mic->mux_idx); +} + static void stac_issue_unsol_event(struct hda_codec *codec, hda_nid_t nid) { struct sigmatel_event *event = stac_get_event(codec, nid); @@ -4623,7 +4753,15 @@ static void stac92xx_unsol_event(struct hda_codec *codec, unsigned int res) switch (event->type) { case STAC_HP_EVENT: stac92xx_hp_detect(codec); - /* fallthru */ + break; + case STAC_MIC_EVENT: + stac92xx_mic_detect(codec); + break; + } + + switch (event->type) { + case STAC_HP_EVENT: + case STAC_MIC_EVENT: case STAC_INSERT_EVENT: case STAC_PWR_EVENT: if (spec->num_pwrs > 0) -- 2.39.2