* Re: [PATCH] ALSA: hda/generic: Add mic autoswitch support for dyn_adc_switch mode
2026-05-03 11:45 [PATCH] ALSA: hda/generic: Add mic autoswitch support for dyn_adc_switch mode Zhang Heng
@ 2026-05-04 16:25 ` Takashi Iwai
0 siblings, 0 replies; 2+ messages in thread
From: Takashi Iwai @ 2026-05-04 16:25 UTC (permalink / raw)
To: Zhang Heng; +Cc: perex, tiwai, linux-sound, linux-kernel
On Sun, 03 May 2026 13:45:12 +0200,
Zhang Heng wrote:
>
> When auto_mic is not available but dyn_adc_switch mode is enabled
> (e.g., on laptops with both front and rear mic jacks), this patch
> enables automatic microphone switching based on jack detection.
>
> The patch includes three changes:
>
> 1. In check_dyn_adc_switch(): Register jack detect callbacks for
> all input pins when dyn_adc_switch is enabled and auto_mic is
> not available.
>
> 2. In call_mic_autoswitch(): Add handling for dyn_adc_switch mode
> to check imux_pins[] for jack presence, searching from back to
> front (last inserted wins).
>
> 3. In mux_select(): Notify Capture Source controls after switching
> to sync with user-space (PulseAudio/PipeWire).
>
> Problem description:
> On Ubuntu 20.04 (with older PulseAudio/PipeWire):
> - Front mic is unplugged
> - Plug in rear mic
> - Jack event is reported correctly
> - Volume control (PulseAudio/PipeWire) recognizes the rear mic
> - But alsamixer does NOT switch to rear mic
> - Codec also does NOT perform the switch
>
> The root cause is that in dyn_adc_switch mode without auto_mic,
> the jack detect callback was not properly set up to trigger the
> mic autoswitch. The call_mic_autoswitch() function only calls
> mic_autoswitch_hook or snd_hda_gen_mic_autoswitch(), which rely
> on auto_mic being enabled.
>
> Additionally, after mux_select() performs the switch, user-space
> (PulseAudio/PipeWire) may not be aware of the path change,
> causing Capture Switch to show 'off'.
>
> This patch fixes both issues by:
> 1. Registering jack detect callbacks for all input pins in
> dyn_adc_switch mode
> 2. Notifying Capture Source controls after switching
>
> Tested on SN6186 codec with Ubuntu 20.04 and 25.10.
>
> Question to the community: Is this approach correct? Should additional
> changes be made to handle mute state preservation, or is this purely
> a user-space issue that requires updating PulseAudio/PipeWire?
>
> Testing and feedback are welcome.
I think the basic idea is OK. We can treat the auto-mic with dynamic
ADC switches, too.
But it's not clear what's the actual intent in your code changes:
> Signed-off-by: Zhang Heng <zhangheng@kylinos.cn>
> ---
> sound/hda/codecs/generic.c | 82 ++++++++++++++++++++++++++++++++++++--
> 1 file changed, 79 insertions(+), 3 deletions(-)
>
> diff --git a/sound/hda/codecs/generic.c b/sound/hda/codecs/generic.c
> index 660a9f2c0ded..c536de10d8b8 100644
> --- a/sound/hda/codecs/generic.c
> +++ b/sound/hda/codecs/generic.c
> @@ -27,6 +27,10 @@
> #include "hda_beep.h"
> #include "generic.h"
>
> +/* Forward declaration */
> +static void call_mic_autoswitch(struct hda_codec *codec,
> + struct hda_jack_callback *jack);
> +
>
> /**
> * snd_hda_gen_spec_init - initialize hda_gen_spec struct
> @@ -3238,6 +3242,19 @@ static int check_dyn_adc_switch(struct hda_codec *codec)
> if (!spec->dyn_adc_switch && spec->multi_cap_vol)
> spec->num_adc_nids = 1;
>
> + /* Enable mic autoswitch for dyn_adc_switch mode when auto_mic is not available */
> + if (!spec->auto_mic && imux->num_items > 1) {
> + int i;
> + for (i = 0; i < imux->num_items; i++) {
> + hda_nid_t pin = spec->imux_pins[i];
> + if (!is_jack_detectable(codec, pin))
> + continue;
> + snd_hda_jack_detect_enable_callback(codec, pin,
> + call_mic_autoswitch);
> + }
> + codec_dbg(codec, "Enable mic autoswitch for input sources\n");
> + }
IMO, this should be rather put at auto_mic_check_imux() instead,
something like:
@@ -4789,13 +4789,15 @@ static bool auto_mic_check_imux(struct hda_codec *codec)
const struct hda_input_mux *imux;
int i;
- imux = &spec->input_mux;
- for (i = 0; i < spec->am_num_entries; i++) {
- spec->am_entry[i].idx =
- find_idx_in_nid_list(spec->am_entry[i].pin,
- spec->imux_pins, imux->num_items);
- if (spec->am_entry[i].idx < 0)
- return false; /* no corresponding imux */
+ if (!spec->dyn_adc_switch) {
+ imux = &spec->input_mux;
+ for (i = 0; i < spec->am_num_entries; i++) {
+ spec->am_entry[i].idx =
+ find_idx_in_nid_list(spec->am_entry[i].pin,
+ spec->imux_pins, imux->num_items);
+ if (spec->am_entry[i].idx < 0)
+ return false; /* no corresponding imux */
+ }
}
/* we don't need the jack detection for the first pin */
> return 0;
> }
>
> @@ -4107,9 +4124,38 @@ static int mux_select(struct hda_codec *codec, unsigned int adc_idx,
> path = get_input_path(codec, adc_idx, idx);
> if (!path)
> return 0;
> - if (path->active)
> - return 0;
> - snd_hda_activate_path(codec, path, true, false);
> + if (path->active) {
> + codec_err(codec, "[MUX] mux_select: path already active, skip activate but still notify\n");
> + } else {
> + snd_hda_activate_path(codec, path, true, false);
> + }
> +
> + /* Notify Input Source / Capture Source controls that the path has changed */
> + if (!spec->auto_mic && spec->input_mux.num_items > 1) {
> + struct snd_card *card = codec->card;
> + struct snd_kcontrol *kctl;
> + struct snd_ctl_elem_id id;
> + int notified = 0;
> +
> + codec_err(codec, "[MUX] mux_select: notifying controls, num_items=%d\n", spec->input_mux.num_items);
> +
> + if (card) {
> + list_for_each_entry(kctl, &card->controls, list) {
> + if (kctl->id.iface != SNDRV_CTL_ELEM_IFACE_MIXER)
> + continue;
> + codec_err(codec, "[MUX] mux_select: found control '%s'\n", kctl->id.name);
> + if (strncmp(kctl->id.name, "Capture Source", 14) == 0) {
> + id = kctl->id;
> + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &id);
> + codec_err(codec, "[MUX] mux_select: notified '%s'\n", kctl->id.name);
> + notified = 1;
> + }
> + }
> + }
> + if (!notified)
> + codec_err(codec, "[MUX] mux_select: no Input/Capture Source control found!\n");
> + }
> +
Hmm, I can't follow the logic here. What does this code change try to
achieve...?
> if (spec->cap_sync_hook)
> spec->cap_sync_hook(codec, NULL, NULL);
> path_power_down_sync(codec, old_path);
> @@ -4602,6 +4648,36 @@ static void call_mic_autoswitch(struct hda_codec *codec,
> struct hda_jack_callback *jack)
> {
> struct hda_gen_spec *spec = codec->spec;
> + const struct hda_input_mux *imux;
> + int i;
> +
> + /* Handle dyn_adc_switch mode - use imux_pins[] */
> + if (!spec->auto_mic && spec->dyn_adc_switch) {
> + imux = &spec->input_mux;
> + if (imux->num_items <= 1)
> + goto fallback;
> +
> + codec_err(codec, "[AUTO_MIC] call_mic_autoswitch: dyn_adc_switch mode, checking %d inputs\n", imux->num_items);
> +
> + /* Search from back to front (last inserted wins) */
> + for (i = imux->num_items - 1; i >= 0; i--) {
> + hda_nid_t pin = spec->imux_pins[i];
> + int det = is_jack_detectable(codec, pin);
> + int state = snd_hda_jack_detect_state(codec, pin);
> + codec_err(codec, "[AUTO_MIC] call_mic_autoswitch: imux[%d] pin=0x%02x, det=%d, state=%s\n",
> + i, pin, det, state == HDA_JACK_PRESENT ? "PRESENT" : "ABSENT");
> + if (det && state == HDA_JACK_PRESENT) {
> + codec_err(codec, "[AUTO_MIC] call_mic_autoswitch: switching to imux %d\n", i);
> + mux_select(codec, 0, i);
> + return;
> + }
> + }
> + /* No jack present, keep current selection (don't switch to imux 0) */
> + codec_err(codec, "[AUTO_MIC] call_mic_autoswitch: no jack present, keeping current\n");
> + return;
> + }
> +
> +fallback:
> if (spec->mic_autoswitch_hook)
> spec->mic_autoswitch_hook(codec, jack);
> else
Ditto. Something too complex here.
mux_select() should handle the dynamic ADC switching, per se.
And, with the auto-mic mode, the driver shouldn't expose the Capture
Source mixer control. That said, enabling the auto-mic would have a
clear downside for the user of PA/PW, too.
thanks,
Takashi
^ permalink raw reply [flat|nested] 2+ messages in thread