ALSA: usb-audio: Implement jack detection for HP Thunderbolt Dock G2

The HP Thunderbolt Dock G2 includes a headset jack with support for
jack detection. However, this being a UAC1 device, detection is
implemented via vendor-defined URB Controls.

Implement it in a similar way to the Dell WD15/19 docks, but with
different commands.

Signed-off-by: Tasos Sahanidis <tasos@tasossah.com>
Link: https://patch.msgid.link/20251126003805.2705503-3-tasos@tasossah.com
Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Tasos Sahanidis 2025-11-26 02:29:57 +02:00 committed by Takashi Iwai
parent fba274760d
commit 92099de846
1 changed files with 102 additions and 0 deletions

View File

@ -2363,6 +2363,105 @@ static int dell_dock_mixer_init(struct usb_mixer_interface *mixer)
return 0;
}
/*
* HP Thunderbolt Dock G2 jack detection
*
* Similar to the Dell WD15/WD19, but with different commands.
*/
#define HP_DOCK_JACK_INTERRUPT_NODE 7
#define HP_DOCK_GET 37
#define HP_DOCK_JACK_PRESENCE 0xffb8
#define HP_DOCK_JACK_PRESENCE_BIT BIT(2)
#define HP_DOCK_MIC_SENSE 0xf753
#define HP_DOCK_MIC_SENSE_COMPLETE_BIT BIT(4)
#define HP_DOCK_MIC_SENSE_MASK (BIT(2) | BIT(1) | BIT(0))
/* #define HP_DOCK_MIC_SENSE_PRESENT 0x2 */
#define HP_DOCK_MIC_SENSE_NOT_PRESENT 0x4
static int hp_dock_ctl_connector_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol);
struct snd_usb_audio *chip = cval->head.mixer->chip;
u32 pv = kcontrol->private_value;
bool presence;
int err;
u8 buf;
CLASS(snd_usb_lock, pm)(chip);
if (pm.err < 0)
return pm.err;
err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0),
HP_DOCK_GET,
USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_IN,
0, HP_DOCK_JACK_PRESENCE, &buf, sizeof(buf));
if (err < 0)
return err;
presence = !(buf & HP_DOCK_JACK_PRESENCE_BIT);
if (pv && presence) {
for (int i = 0; i < 20; i++) {
err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0),
HP_DOCK_GET,
USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_IN,
0, HP_DOCK_MIC_SENSE, &buf, sizeof(buf));
if (err < 0)
return err;
/* Mic sense is complete, we have a result. */
if (buf & HP_DOCK_MIC_SENSE_COMPLETE_BIT)
break;
msleep(100);
}
/*
* If we reach the retry limit without mic sense having
* completed, buf will contain HP_DOCK_MIC_SENSE_PRESENT,
* thus presence remains true even when detection fails.
*/
if ((buf & HP_DOCK_MIC_SENSE_MASK) == HP_DOCK_MIC_SENSE_NOT_PRESENT)
presence = false;
}
ucontrol->value.integer.value[0] = presence;
return 0;
}
static const struct snd_kcontrol_new hp_dock_connector_ctl_ro = {
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
.name = "", /* will be filled later manually */
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.info = snd_ctl_boolean_mono_info,
.get = hp_dock_ctl_connector_get,
};
static int hp_dock_mixer_create(struct usb_mixer_interface *mixer)
{
int err;
err = realtek_add_jack(mixer, "Headsets Playback Jack", 0,
HP_DOCK_JACK_INTERRUPT_NODE,
&hp_dock_connector_ctl_ro);
if (err < 0)
return err;
err = realtek_add_jack(mixer, "Headset Capture Jack", 1,
HP_DOCK_JACK_INTERRUPT_NODE,
&hp_dock_connector_ctl_ro);
if (err < 0)
return err;
return 0;
}
/* RME Class Compliant device quirks */
#define SND_RME_GET_STATUS1 23
@ -4414,6 +4513,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
case USB_ID(0x2b73, 0x0034): /* Pioneer DJ DJM-V10 */
err = snd_djm_controls_create(mixer, SND_DJM_V10_IDX);
break;
case USB_ID(0x03f0, 0x0269): /* HP TB Dock G2 */
err = hp_dock_mixer_create(mixer);
break;
}
return err;