summaryrefslogtreecommitdiff
path: root/sound/usb
diff options
context:
space:
mode:
authorJan Schär <jan@jschaer.ch>2022-06-27 20:18:54 +0300
committerTakashi Iwai <tiwai@suse.de>2022-07-04 15:16:07 +0300
commit4b8ea38fabab45ad911a32a336416062553dfe9c (patch)
tree98a76f12480f754edc20eecbd575a16c730c8270 /sound/usb
parent4a1e6ac7d5a014fcc14bf25b6cd14328619a6233 (diff)
downloadlinux-4b8ea38fabab45ad911a32a336416062553dfe9c.tar.xz
ALSA: usb-audio: Support jack detection on Dell dock
The Dell WD15 dock has a headset and a line out port. Add support for detecting if a jack is inserted into one of these ports. For the headset jack, additionally determine if a mic is present. The WD15 contains an ALC4020 USB audio controller and ALC3263 audio codec from Realtek. It is a UAC 1 device, and UAC 1 does not support jack detection. Instead, jack detection works by sending HD Audio commands over vendor-type USB messages. I found out how it works by looking at USB captures on Windows. The audio codec is very similar to the one supported by sound/soc/codecs/rt298.c / rt298.h, some constant names and the mic detection are adapted from there. The realtek_add_jack function is adapted from build_connector_control in sound/usb/mixer.c. I tested this on a WD15 dock with the latest firmware. Signed-off-by: Jan Schär <jan@jschaer.ch> Link: https://lore.kernel.org/r/20220627171855.42338-1-jan@jschaer.ch Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/usb')
-rw-r--r--sound/usb/mixer_quirks.c167
1 files changed, 167 insertions, 0 deletions
diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c
index d35cf54cab33..66b6476994eb 100644
--- a/sound/usb/mixer_quirks.c
+++ b/sound/usb/mixer_quirks.c
@@ -24,6 +24,7 @@
#include <sound/asoundef.h>
#include <sound/core.h>
#include <sound/control.h>
+#include <sound/hda_verbs.h>
#include <sound/hwdep.h>
#include <sound/info.h>
#include <sound/tlv.h>
@@ -1934,6 +1935,169 @@ static int snd_soundblaster_e1_switch_create(struct usb_mixer_interface *mixer)
NULL);
}
+/*
+ * Dell WD15 dock jack detection
+ *
+ * The WD15 contains an ALC4020 USB audio controller and ALC3263 audio codec
+ * from Realtek. It is a UAC 1 device, and UAC 1 does not support jack
+ * detection. Instead, jack detection works by sending HD Audio commands over
+ * vendor-type USB messages.
+ */
+
+#define HDA_VERB_CMD(V, N, D) (((N) << 20) | ((V) << 8) | (D))
+
+#define REALTEK_HDA_VALUE 0x0038
+
+#define REALTEK_HDA_SET 62
+#define REALTEK_HDA_GET_OUT 88
+#define REALTEK_HDA_GET_IN 89
+
+#define REALTEK_LINE1 0x1a
+#define REALTEK_VENDOR_REGISTERS 0x20
+#define REALTEK_HP_OUT 0x21
+
+#define REALTEK_CBJ_CTRL2 0x50
+
+#define REALTEK_JACK_INTERRUPT_NODE 5
+
+#define REALTEK_MIC_FLAG 0x100
+
+static int realtek_hda_set(struct snd_usb_audio *chip, u32 cmd)
+{
+ struct usb_device *dev = chip->dev;
+ u32 buf = cpu_to_be32(cmd);
+
+ return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), REALTEK_HDA_SET,
+ USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_OUT,
+ REALTEK_HDA_VALUE, 0, &buf, sizeof(buf));
+}
+
+static int realtek_hda_get(struct snd_usb_audio *chip, u32 cmd, u32 *value)
+{
+ struct usb_device *dev = chip->dev;
+ int err;
+ u32 buf = cpu_to_be32(cmd);
+
+ err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), REALTEK_HDA_GET_OUT,
+ USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_OUT,
+ REALTEK_HDA_VALUE, 0, &buf, sizeof(buf));
+ if (err < 0)
+ return err;
+ err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), REALTEK_HDA_GET_IN,
+ USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_IN,
+ REALTEK_HDA_VALUE, 0, &buf, sizeof(buf));
+ if (err < 0)
+ return err;
+
+ *value = be32_to_cpu(buf);
+ return 0;
+}
+
+static int realtek_ctl_connector_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_info *cval = kcontrol->private_data;
+ struct snd_usb_audio *chip = cval->head.mixer->chip;
+ u32 pv = kcontrol->private_value;
+ u32 node_id = pv & 0xff;
+ u32 sense;
+ u32 cbj_ctrl2;
+ bool presence;
+ int err;
+
+ err = snd_usb_lock_shutdown(chip);
+ if (err < 0)
+ return err;
+ err = realtek_hda_get(chip,
+ HDA_VERB_CMD(AC_VERB_GET_PIN_SENSE, node_id, 0),
+ &sense);
+ if (err < 0)
+ goto err;
+ if (pv & REALTEK_MIC_FLAG) {
+ err = realtek_hda_set(chip,
+ HDA_VERB_CMD(AC_VERB_SET_COEF_INDEX,
+ REALTEK_VENDOR_REGISTERS,
+ REALTEK_CBJ_CTRL2));
+ if (err < 0)
+ goto err;
+ err = realtek_hda_get(chip,
+ HDA_VERB_CMD(AC_VERB_GET_PROC_COEF,
+ REALTEK_VENDOR_REGISTERS, 0),
+ &cbj_ctrl2);
+ if (err < 0)
+ goto err;
+ }
+err:
+ snd_usb_unlock_shutdown(chip);
+ if (err < 0)
+ return err;
+
+ presence = sense & AC_PINSENSE_PRESENCE;
+ if (pv & REALTEK_MIC_FLAG)
+ presence = presence && (cbj_ctrl2 & 0x0070) == 0x0070;
+ ucontrol->value.integer.value[0] = presence;
+ return 0;
+}
+
+static const struct snd_kcontrol_new realtek_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 = realtek_ctl_connector_get,
+};
+
+static int realtek_resume_jack(struct usb_mixer_elem_list *list)
+{
+ snd_ctl_notify(list->mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+ &list->kctl->id);
+ return 0;
+}
+
+static int realtek_add_jack(struct usb_mixer_interface *mixer,
+ char *name, u32 val)
+{
+ struct usb_mixer_elem_info *cval;
+ struct snd_kcontrol *kctl;
+
+ cval = kzalloc(sizeof(*cval), GFP_KERNEL);
+ if (!cval)
+ return -ENOMEM;
+ snd_usb_mixer_elem_init_std(&cval->head, mixer,
+ REALTEK_JACK_INTERRUPT_NODE);
+ cval->head.resume = realtek_resume_jack;
+ cval->val_type = USB_MIXER_BOOLEAN;
+ cval->channels = 1;
+ cval->min = 0;
+ cval->max = 1;
+ kctl = snd_ctl_new1(&realtek_connector_ctl_ro, cval);
+ if (!kctl) {
+ kfree(cval);
+ return -ENOMEM;
+ }
+ kctl->private_value = val;
+ strscpy(kctl->id.name, name, sizeof(kctl->id.name));
+ kctl->private_free = snd_usb_mixer_elem_free;
+ return snd_usb_mixer_add_control(&cval->head, kctl);
+}
+
+static int dell_dock_mixer_create(struct usb_mixer_interface *mixer)
+{
+ int err;
+
+ err = realtek_add_jack(mixer, "Line Out Jack", REALTEK_LINE1);
+ if (err < 0)
+ return err;
+ err = realtek_add_jack(mixer, "Headphone Jack", REALTEK_HP_OUT);
+ if (err < 0)
+ return err;
+ err = realtek_add_jack(mixer, "Headset Mic Jack",
+ REALTEK_HP_OUT | REALTEK_MIC_FLAG);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
static void dell_dock_init_vol(struct snd_usb_audio *chip, int ch, int id)
{
u16 buf = 0;
@@ -3245,6 +3409,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
err = snd_soundblaster_e1_switch_create(mixer);
break;
case USB_ID(0x0bda, 0x4014): /* Dell WD15 dock */
+ err = dell_dock_mixer_create(mixer);
+ if (err < 0)
+ break;
err = dell_dock_mixer_init(mixer);
break;