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>
This commit is contained in:
parent
4a1e6ac7d5
commit
4b8ea38fab
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user