[alsa-devel] User-friendly dynamic HDA pin reconfiguration
Hi,
We are working with a new consumer All-in-One platform with HDA audio (Realtek codec).
Upon plugging in an audio device into one of the audio jacks, under Windows, a prompt appears which says: Which device did you plug in? And you can select from several options: Mic In / Headphone / Speaker Out Screenshot: https://imgur.com/a/mrUolHN
The system vendor is asking if we can implement the same under Linux.
At first glance, this sounds quite similar to what happens on other models when you plug in a headset under GNOME: a dialog pops up asking if you connected a mic, headphones, or a headset. Under the surface, such headset jacks seems to correspond to 2 separate HDA pins (one input and one output) and this dialog seems to manage which streams are enabled or muted.
However, this case is different. We have experimented, and the jack corresponds to just a single HDA pin. We can manually quirk or retask this pin to be an input or an output and it works in the direction that we choose. But that's a far cry from having a nice GUI that automatically comes up asking the user how the pin should be configured.
Is there any current solution or work in progress with the goal of offering this functionality?
If not, brainstorming, possible solutions could include:
1. Taking the hdajackretask idea but making it user-accessible and user-friendly (currently it goes through debugfs and must be done by root). This would require the creation of a new kernel interface to expose pin reconfiguration in a nicer way, which would have to be plumbed through pulseaudio and GNOME.
2. Could ALSA ignore the pin config here and provide both input and output streams for this pin? Then pulseaudio + GUI could pick which one to use - similar to the existing Headset dialog. This would still require some work on pulseaudio and the corresponding UI, but it might be a less invasive project.
Thanks, Daniel
On Wed, 31 Oct 2018 09:47:27 +0100, Daniel Drake wrote:
Hi,
We are working with a new consumer All-in-One platform with HDA audio (Realtek codec).
Upon plugging in an audio device into one of the audio jacks, under Windows, a prompt appears which says: Which device did you plug in? And you can select from several options: Mic In / Headphone / Speaker Out Screenshot: https://imgur.com/a/mrUolHN
The system vendor is asking if we can implement the same under Linux.
At first glance, this sounds quite similar to what happens on other models when you plug in a headset under GNOME: a dialog pops up asking if you connected a mic, headphones, or a headset. Under the surface, such headset jacks seems to correspond to 2 separate HDA pins (one input and one output) and this dialog seems to manage which streams are enabled or muted.
However, this case is different. We have experimented, and the jack corresponds to just a single HDA pin. We can manually quirk or retask this pin to be an input or an output and it works in the direction that we choose. But that's a far cry from having a nice GUI that automatically comes up asking the user how the pin should be configured.
Is there any current solution or work in progress with the goal of offering this functionality?
If not, brainstorming, possible solutions could include:
- Taking the hdajackretask idea but making it user-accessible and
user-friendly (currently it goes through debugfs and must be done by root). This would require the creation of a new kernel interface to expose pin reconfiguration in a nicer way, which would have to be plumbed through pulseaudio and GNOME.
- Could ALSA ignore the pin config here and provide both input and
output streams for this pin? Then pulseaudio + GUI could pick which one to use - similar to the existing Headset dialog. This would still require some work on pulseaudio and the corresponding UI, but it might be a less invasive project.
Actually the generic parser allows to create a "Headphone Mic Jack Mode" enum control in a certain condition. This allows user to switch the headphone jack role via the enum. Isn't it the case?
Takashi
Takashi Iwai wrote:
Actually the generic parser allows to create a "Headphone Mic Jack Mode" enum control in a certain condition. This allows user to switch the headphone jack role via the enum. Isn't it the case?
Thanks for the pointer. In this case the hardware actually has two jacks: a combined headphones/mic jack (i.e. headset), plus a Line In/Out jack.
The headset jack is already working fine. I traced the code you mentioned, it has this condition: if (spec->hp_mic_pin && (spec->auto_mic || spec->input_mux.num_items == 1 || spec->add_jack_modes)) { err = create_hp_mic_jack_mode(codec, spec->hp_mic_pin);
hp_mic_pin is not set because we hit this codepath in create_hp_mic():
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; }
and given that the headset jack is already working OK, I don't think this code is relevant in this case.
However I did take it as a suggestion that I could implement a custom mixer control to change the pin settings. I tried setting both pincfg and pinctl, however after changing the value of my new control, the kernel still describes the pin with the same name in the exposed mixer control, "Line Out Playback Switch", so I'm not sure how a userspace agent such as pulseaudio would be able to know that it should now offer a Line In input option.
My scratch code is below. Further input appreciated!
--- sound/pci/hda/patch_realtek.c | 74 ++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-)
diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 46d41d8c92eb..d6ff2f96a9db 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -105,6 +105,9 @@ struct alc_spec { int current_headset_mode; int current_headset_type;
+ /* Line jack mode selector */ + unsigned int line_jack_mode; + /* hooks */ void (*init_hook)(struct hda_codec *codec); #ifdef CONFIG_PM @@ -5168,6 +5171,68 @@ static void alc269_fixup_limit_int_mic_boost(struct hda_codec *codec, } }
+static int vcopperbox_line_mode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { "Line In", "Line Out" }; + return snd_hda_enum_helper_info(kcontrol, uinfo, 2, texts); +} + +static int vcopperbox_line_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct alc_spec *spec = codec->spec; + // FIXME autodetect based on pin cfg val? + ucontrol->value.enumerated.item[0] = spec->line_jack_mode; + return 0; +} + +static int vcopperbox_line_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct alc_spec *spec = codec->spec; + unsigned int val = ucontrol->value.enumerated.item[0]; + unsigned int pincfg, pinctl; + + // FIXME just tweak the bits of interest + // page 182 + // bits 20 to 23 is the type selector, 8=IN 0=OUT 2=HP OUT + if (val == 0) { + /* Line In */ + pincfg = 0x018110d1; + pinctl = PIN_IN; + } else { + /* Line Out */ + pincfg = 0x01211030; + pinctl = PIN_OUT; + } + + snd_hda_codec_set_pincfg(codec, 0x1a, pincfg); + snd_hda_set_pin_ctl_cache(codec, 0x1a, pinctl); + spec->line_jack_mode = val; + return 1; +} + +static const struct snd_kcontrol_new vcopperbox_line_select_enum = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = vcopperbox_line_mode_info, + .get = vcopperbox_line_mode_get, + .put = vcopperbox_line_mode_put, +}; + +static void alc269_acer_vcopperbox_line_select(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PROBE) + snd_hda_gen_add_kctl(&spec->gen, "Line Jack Mode", + &vcopperbox_line_select_enum); +} + static void alc283_hp_automute_hook(struct hda_codec *codec, struct hda_jack_callback *jack) { @@ -5560,6 +5625,7 @@ enum { ALC295_FIXUP_ASUS_MIC_NO_PRESENCE, ALC282_FIXUP_ACER_TRAVELMATE_PINS, ALC255_FIXUP_ACER_LIMIT_INT_MIC_BOOST, + ALC269VC_FIXUP_ACER_VCOPPERBOX_LINE_SELECT, ALC269VC_FIXUP_ACER_VCOPPERBOX_PINS, ALC294_FIXUP_ASUS_SPK_NOISE, ALC256_FIXUP_ACER_SWIFT_NO_MIC_PRESENCE, @@ -6487,6 +6553,12 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC255_FIXUP_ACER_MIC_NO_PRESENCE, }, + [ALC269VC_FIXUP_ACER_VCOPPERBOX_LINE_SELECT] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_acer_vcopperbox_line_select, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE + }, [ALC269VC_FIXUP_ACER_VCOPPERBOX_PINS] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { @@ -6496,7 +6568,7 @@ static const struct hda_fixup alc269_fixups[] = { { }, }, .chained = true, - .chain_id = ALC269_FIXUP_HEADSET_MODE + .chain_id = ALC269VC_FIXUP_ACER_VCOPPERBOX_LINE_SELECT }, [ALC294_FIXUP_ASUS_SPK_NOISE] = { .type = HDA_FIXUP_VERBS,
participants (2)
-
Daniel Drake
-
Takashi Iwai