Commit 92099de8 authored by Tasos Sahanidis's avatar Tasos Sahanidis Committed by Takashi Iwai
Browse files

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: default avatarTasos Sahanidis <tasos@tasossah.com>
Link: https://patch.msgid.link/20251126003805.2705503-3-tasos@tasossah.com


Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent fba27476
Loading
Loading
Loading
Loading
+102 −0
Original line number Diff line number Diff line
@@ -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;