|
@@ -51,6 +51,7 @@
|
|
|
#include <linux/usb.h>
|
|
|
#include <linux/usb/audio.h>
|
|
|
#include <linux/usb/audio-v2.h>
|
|
|
+#include <linux/usb/audio-v3.h>
|
|
|
|
|
|
#include <sound/core.h>
|
|
|
#include <sound/control.h>
|
|
@@ -189,7 +190,7 @@ static void *find_audio_control_unit(struct mixer_build *state,
|
|
|
USB_DT_CS_INTERFACE)) != NULL) {
|
|
|
if (hdr->bLength >= 4 &&
|
|
|
hdr->bDescriptorSubtype >= UAC_INPUT_TERMINAL &&
|
|
|
- hdr->bDescriptorSubtype <= UAC2_SAMPLE_RATE_CONVERTER &&
|
|
|
+ hdr->bDescriptorSubtype <= UAC3_SAMPLE_RATE_CONVERTER &&
|
|
|
hdr->bUnitID == unit)
|
|
|
return hdr;
|
|
|
}
|
|
@@ -468,9 +469,10 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval,
|
|
|
|
|
|
validx += cval->idx_off;
|
|
|
|
|
|
+
|
|
|
if (cval->head.mixer->protocol == UAC_VERSION_1) {
|
|
|
val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1;
|
|
|
- } else { /* UAC_VERSION_2 */
|
|
|
+ } else { /* UAC_VERSION_2/3 */
|
|
|
val_len = uac2_ctl_value_size(cval->val_type);
|
|
|
|
|
|
/* FIXME */
|
|
@@ -723,6 +725,7 @@ static int get_term_name(struct mixer_build *state, struct usb_audio_term *iterm
|
|
|
static int check_input_term(struct mixer_build *state, int id,
|
|
|
struct usb_audio_term *term)
|
|
|
{
|
|
|
+ int protocol = state->mixer->protocol;
|
|
|
int err;
|
|
|
void *p1;
|
|
|
|
|
@@ -730,16 +733,104 @@ static int check_input_term(struct mixer_build *state, int id,
|
|
|
while ((p1 = find_audio_control_unit(state, id)) != NULL) {
|
|
|
unsigned char *hdr = p1;
|
|
|
term->id = id;
|
|
|
- switch (hdr[2]) {
|
|
|
- case UAC_INPUT_TERMINAL:
|
|
|
- if (state->mixer->protocol == UAC_VERSION_1) {
|
|
|
- struct uac_input_terminal_descriptor *d = p1;
|
|
|
- term->type = le16_to_cpu(d->wTerminalType);
|
|
|
- term->channels = d->bNrChannels;
|
|
|
- term->chconfig = le16_to_cpu(d->wChannelConfig);
|
|
|
- term->name = d->iTerminal;
|
|
|
- } else { /* UAC_VERSION_2 */
|
|
|
- struct uac2_input_terminal_descriptor *d = p1;
|
|
|
+
|
|
|
+ if (protocol == UAC_VERSION_1 || protocol == UAC_VERSION_2) {
|
|
|
+ switch (hdr[2]) {
|
|
|
+ case UAC_INPUT_TERMINAL:
|
|
|
+ if (protocol == UAC_VERSION_1) {
|
|
|
+ struct uac_input_terminal_descriptor *d = p1;
|
|
|
+
|
|
|
+ term->type = le16_to_cpu(d->wTerminalType);
|
|
|
+ term->channels = d->bNrChannels;
|
|
|
+ term->chconfig = le16_to_cpu(d->wChannelConfig);
|
|
|
+ term->name = d->iTerminal;
|
|
|
+ } else { /* UAC_VERSION_2 */
|
|
|
+ struct uac2_input_terminal_descriptor *d = p1;
|
|
|
+
|
|
|
+ /* call recursively to verify that the
|
|
|
+ * referenced clock entity is valid */
|
|
|
+ err = check_input_term(state, d->bCSourceID, term);
|
|
|
+ if (err < 0)
|
|
|
+ return err;
|
|
|
+
|
|
|
+ /* save input term properties after recursion,
|
|
|
+ * to ensure they are not overriden by the
|
|
|
+ * recursion calls */
|
|
|
+ term->id = id;
|
|
|
+ term->type = le16_to_cpu(d->wTerminalType);
|
|
|
+ term->channels = d->bNrChannels;
|
|
|
+ term->chconfig = le32_to_cpu(d->bmChannelConfig);
|
|
|
+ term->name = d->iTerminal;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+ case UAC_FEATURE_UNIT: {
|
|
|
+ /* the header is the same for v1 and v2 */
|
|
|
+ struct uac_feature_unit_descriptor *d = p1;
|
|
|
+
|
|
|
+ id = d->bSourceID;
|
|
|
+ break; /* continue to parse */
|
|
|
+ }
|
|
|
+ case UAC_MIXER_UNIT: {
|
|
|
+ struct uac_mixer_unit_descriptor *d = p1;
|
|
|
+
|
|
|
+ term->type = d->bDescriptorSubtype << 16; /* virtual type */
|
|
|
+ term->channels = uac_mixer_unit_bNrChannels(d);
|
|
|
+ term->chconfig = uac_mixer_unit_wChannelConfig(d, protocol);
|
|
|
+ term->name = uac_mixer_unit_iMixer(d);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ case UAC_SELECTOR_UNIT:
|
|
|
+ case UAC2_CLOCK_SELECTOR: {
|
|
|
+ struct uac_selector_unit_descriptor *d = p1;
|
|
|
+ /* call recursively to retrieve the channel info */
|
|
|
+ err = check_input_term(state, d->baSourceID[0], term);
|
|
|
+ if (err < 0)
|
|
|
+ return err;
|
|
|
+ term->type = d->bDescriptorSubtype << 16; /* virtual type */
|
|
|
+ term->id = id;
|
|
|
+ term->name = uac_selector_unit_iSelector(d);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ case UAC1_PROCESSING_UNIT:
|
|
|
+ case UAC1_EXTENSION_UNIT:
|
|
|
+ /* UAC2_PROCESSING_UNIT_V2 */
|
|
|
+ /* UAC2_EFFECT_UNIT */
|
|
|
+ case UAC2_EXTENSION_UNIT_V2: {
|
|
|
+ struct uac_processing_unit_descriptor *d = p1;
|
|
|
+
|
|
|
+ if (protocol == UAC_VERSION_2 &&
|
|
|
+ hdr[2] == UAC2_EFFECT_UNIT) {
|
|
|
+ /* UAC2/UAC1 unit IDs overlap here in an
|
|
|
+ * uncompatible way. Ignore this unit for now.
|
|
|
+ */
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (d->bNrInPins) {
|
|
|
+ id = d->baSourceID[0];
|
|
|
+ break; /* continue to parse */
|
|
|
+ }
|
|
|
+ term->type = d->bDescriptorSubtype << 16; /* virtual type */
|
|
|
+ term->channels = uac_processing_unit_bNrChannels(d);
|
|
|
+ term->chconfig = uac_processing_unit_wChannelConfig(d, protocol);
|
|
|
+ term->name = uac_processing_unit_iProcessing(d, protocol);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ case UAC2_CLOCK_SOURCE: {
|
|
|
+ struct uac_clock_source_descriptor *d = p1;
|
|
|
+
|
|
|
+ term->type = d->bDescriptorSubtype << 16; /* virtual type */
|
|
|
+ term->id = id;
|
|
|
+ term->name = d->iClockSource;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ return -ENODEV;
|
|
|
+ }
|
|
|
+ } else { /* UAC_VERSION_3 */
|
|
|
+ switch (hdr[2]) {
|
|
|
+ case UAC_INPUT_TERMINAL: {
|
|
|
+ struct uac3_input_terminal_descriptor *d = p1;
|
|
|
|
|
|
/* call recursively to verify that the
|
|
|
* referenced clock entity is valid */
|
|
@@ -752,71 +843,31 @@ static int check_input_term(struct mixer_build *state, int id,
|
|
|
* recursion calls */
|
|
|
term->id = id;
|
|
|
term->type = le16_to_cpu(d->wTerminalType);
|
|
|
- term->channels = d->bNrChannels;
|
|
|
- term->chconfig = le32_to_cpu(d->bmChannelConfig);
|
|
|
- term->name = d->iTerminal;
|
|
|
- }
|
|
|
- return 0;
|
|
|
- case UAC_FEATURE_UNIT: {
|
|
|
- /* the header is the same for v1 and v2 */
|
|
|
- struct uac_feature_unit_descriptor *d = p1;
|
|
|
- id = d->bSourceID;
|
|
|
- break; /* continue to parse */
|
|
|
- }
|
|
|
- case UAC_MIXER_UNIT: {
|
|
|
- struct uac_mixer_unit_descriptor *d = p1;
|
|
|
- term->type = d->bDescriptorSubtype << 16; /* virtual type */
|
|
|
- term->channels = uac_mixer_unit_bNrChannels(d);
|
|
|
- term->chconfig = uac_mixer_unit_wChannelConfig(d, state->mixer->protocol);
|
|
|
- term->name = uac_mixer_unit_iMixer(d);
|
|
|
- return 0;
|
|
|
- }
|
|
|
- case UAC_SELECTOR_UNIT:
|
|
|
- case UAC2_CLOCK_SELECTOR: {
|
|
|
- struct uac_selector_unit_descriptor *d = p1;
|
|
|
- /* call recursively to retrieve the channel info */
|
|
|
- err = check_input_term(state, d->baSourceID[0], term);
|
|
|
- if (err < 0)
|
|
|
- return err;
|
|
|
- term->type = d->bDescriptorSubtype << 16; /* virtual type */
|
|
|
- term->id = id;
|
|
|
- term->name = uac_selector_unit_iSelector(d);
|
|
|
- return 0;
|
|
|
- }
|
|
|
- case UAC1_PROCESSING_UNIT:
|
|
|
- case UAC1_EXTENSION_UNIT:
|
|
|
- /* UAC2_PROCESSING_UNIT_V2 */
|
|
|
- /* UAC2_EFFECT_UNIT */
|
|
|
- case UAC2_EXTENSION_UNIT_V2: {
|
|
|
- struct uac_processing_unit_descriptor *d = p1;
|
|
|
-
|
|
|
- if (state->mixer->protocol == UAC_VERSION_2 &&
|
|
|
- hdr[2] == UAC2_EFFECT_UNIT) {
|
|
|
- /* UAC2/UAC1 unit IDs overlap here in an
|
|
|
- * uncompatible way. Ignore this unit for now.
|
|
|
- */
|
|
|
+
|
|
|
+ /* REVISIT: UAC3 IT doesn't have channels/cfg */
|
|
|
+ term->channels = 0;
|
|
|
+ term->chconfig = 0;
|
|
|
+
|
|
|
+ term->name = le16_to_cpu(d->wTerminalDescrStr);
|
|
|
return 0;
|
|
|
}
|
|
|
+ case UAC3_FEATURE_UNIT: {
|
|
|
+ struct uac3_feature_unit_descriptor *d = p1;
|
|
|
|
|
|
- if (d->bNrInPins) {
|
|
|
- id = d->baSourceID[0];
|
|
|
+ id = d->bSourceID;
|
|
|
break; /* continue to parse */
|
|
|
}
|
|
|
- term->type = d->bDescriptorSubtype << 16; /* virtual type */
|
|
|
- term->channels = uac_processing_unit_bNrChannels(d);
|
|
|
- term->chconfig = uac_processing_unit_wChannelConfig(d, state->mixer->protocol);
|
|
|
- term->name = uac_processing_unit_iProcessing(d, state->mixer->protocol);
|
|
|
- return 0;
|
|
|
- }
|
|
|
- case UAC2_CLOCK_SOURCE: {
|
|
|
- struct uac_clock_source_descriptor *d = p1;
|
|
|
- term->type = d->bDescriptorSubtype << 16; /* virtual type */
|
|
|
- term->id = id;
|
|
|
- term->name = d->iClockSource;
|
|
|
- return 0;
|
|
|
- }
|
|
|
- default:
|
|
|
- return -ENODEV;
|
|
|
+ case UAC3_CLOCK_SOURCE: {
|
|
|
+ struct uac3_clock_source_descriptor *d = p1;
|
|
|
+
|
|
|
+ term->type = d->bDescriptorSubtype << 16; /* virtual type */
|
|
|
+ term->id = id;
|
|
|
+ term->name = le16_to_cpu(d->wClockSourceStr);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ return -ENODEV;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
return -ENODEV;
|
|
@@ -828,26 +879,27 @@ static int check_input_term(struct mixer_build *state, int id,
|
|
|
|
|
|
/* feature unit control information */
|
|
|
struct usb_feature_control_info {
|
|
|
+ int control;
|
|
|
const char *name;
|
|
|
int type; /* data type for uac1 */
|
|
|
int type_uac2; /* data type for uac2 if different from uac1, else -1 */
|
|
|
};
|
|
|
|
|
|
static struct usb_feature_control_info audio_feature_info[] = {
|
|
|
- { "Mute", USB_MIXER_INV_BOOLEAN, -1 },
|
|
|
- { "Volume", USB_MIXER_S16, -1 },
|
|
|
- { "Tone Control - Bass", USB_MIXER_S8, -1 },
|
|
|
- { "Tone Control - Mid", USB_MIXER_S8, -1 },
|
|
|
- { "Tone Control - Treble", USB_MIXER_S8, -1 },
|
|
|
- { "Graphic Equalizer", USB_MIXER_S8, -1 }, /* FIXME: not implemeted yet */
|
|
|
- { "Auto Gain Control", USB_MIXER_BOOLEAN, -1 },
|
|
|
- { "Delay Control", USB_MIXER_U16, USB_MIXER_U32 },
|
|
|
- { "Bass Boost", USB_MIXER_BOOLEAN, -1 },
|
|
|
- { "Loudness", USB_MIXER_BOOLEAN, -1 },
|
|
|
+ { UAC_FU_MUTE, "Mute", USB_MIXER_INV_BOOLEAN, -1 },
|
|
|
+ { UAC_FU_VOLUME, "Volume", USB_MIXER_S16, -1 },
|
|
|
+ { UAC_FU_BASS, "Tone Control - Bass", USB_MIXER_S8, -1 },
|
|
|
+ { UAC_FU_MID, "Tone Control - Mid", USB_MIXER_S8, -1 },
|
|
|
+ { UAC_FU_TREBLE, "Tone Control - Treble", USB_MIXER_S8, -1 },
|
|
|
+ { UAC_FU_GRAPHIC_EQUALIZER, "Graphic Equalizer", USB_MIXER_S8, -1 }, /* FIXME: not implemented yet */
|
|
|
+ { UAC_FU_AUTOMATIC_GAIN, "Auto Gain Control", USB_MIXER_BOOLEAN, -1 },
|
|
|
+ { UAC_FU_DELAY, "Delay Control", USB_MIXER_U16, USB_MIXER_U32 },
|
|
|
+ { UAC_FU_BASS_BOOST, "Bass Boost", USB_MIXER_BOOLEAN, -1 },
|
|
|
+ { UAC_FU_LOUDNESS, "Loudness", USB_MIXER_BOOLEAN, -1 },
|
|
|
/* UAC2 specific */
|
|
|
- { "Input Gain Control", USB_MIXER_S16, -1 },
|
|
|
- { "Input Gain Pad Control", USB_MIXER_S16, -1 },
|
|
|
- { "Phase Inverter Control", USB_MIXER_BOOLEAN, -1 },
|
|
|
+ { UAC2_FU_INPUT_GAIN, "Input Gain Control", USB_MIXER_S16, -1 },
|
|
|
+ { UAC2_FU_INPUT_GAIN_PAD, "Input Gain Pad Control", USB_MIXER_S16, -1 },
|
|
|
+ { UAC2_FU_PHASE_INVERTER, "Phase Inverter Control", USB_MIXER_BOOLEAN, -1 },
|
|
|
};
|
|
|
|
|
|
/* private_free callback */
|
|
@@ -1183,6 +1235,21 @@ static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol,
|
|
|
return changed;
|
|
|
}
|
|
|
|
|
|
+/* get the boolean value from the master channel of a UAC control */
|
|
|
+static int mixer_ctl_master_bool_get(struct snd_kcontrol *kcontrol,
|
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
|
+{
|
|
|
+ struct usb_mixer_elem_info *cval = kcontrol->private_data;
|
|
|
+ int val, err;
|
|
|
+
|
|
|
+ err = snd_usb_get_cur_mix_value(cval, 0, 0, &val);
|
|
|
+ if (err < 0)
|
|
|
+ return filter_error(cval, err);
|
|
|
+ val = (val != 0);
|
|
|
+ ucontrol->value.integer.value[0] = val;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
static struct snd_kcontrol_new usb_feature_unit_ctl = {
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
.name = "", /* will be filled later manually */
|
|
@@ -1200,6 +1267,19 @@ static const struct snd_kcontrol_new usb_feature_unit_ctl_ro = {
|
|
|
.put = NULL,
|
|
|
};
|
|
|
|
|
|
+/*
|
|
|
+ * A control which shows the boolean value from reading a UAC control on
|
|
|
+ * the master channel.
|
|
|
+ */
|
|
|
+static struct snd_kcontrol_new usb_bool_master_control_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 = mixer_ctl_master_bool_get,
|
|
|
+ .put = NULL,
|
|
|
+};
|
|
|
+
|
|
|
/*
|
|
|
* This symbol is exported in order to allow the mixer quirks to
|
|
|
* hook up to the standard feature unit control mechanism
|
|
@@ -1242,6 +1322,17 @@ static void check_no_speaker_on_headset(struct snd_kcontrol *kctl,
|
|
|
strlcpy(kctl->id.name, "Headphone", sizeof(kctl->id.name));
|
|
|
}
|
|
|
|
|
|
+static struct usb_feature_control_info *get_feature_control_info(int control)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+
|
|
|
+ for (i = 0; i < ARRAY_SIZE(audio_feature_info); ++i) {
|
|
|
+ if (audio_feature_info[i].control == control)
|
|
|
+ return &audio_feature_info[i];
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
|
|
|
unsigned int ctl_mask, int control,
|
|
|
struct usb_audio_term *iterm, int unitid,
|
|
@@ -1257,8 +1348,6 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
|
|
|
const struct usbmix_name_map *map;
|
|
|
unsigned int range;
|
|
|
|
|
|
- control++; /* change from zero-based to 1-based value */
|
|
|
-
|
|
|
if (control == UAC_FU_GRAPHIC_EQUALIZER) {
|
|
|
/* FIXME: not supported yet */
|
|
|
return;
|
|
@@ -1274,7 +1363,12 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
|
|
|
snd_usb_mixer_elem_init_std(&cval->head, state->mixer, unitid);
|
|
|
cval->control = control;
|
|
|
cval->cmask = ctl_mask;
|
|
|
- ctl_info = &audio_feature_info[control-1];
|
|
|
+
|
|
|
+ ctl_info = get_feature_control_info(control);
|
|
|
+ if (!ctl_info) {
|
|
|
+ kfree(cval);
|
|
|
+ return;
|
|
|
+ }
|
|
|
if (state->mixer->protocol == UAC_VERSION_1)
|
|
|
cval->val_type = ctl_info->type;
|
|
|
else /* UAC_VERSION_2 */
|
|
@@ -1400,6 +1494,60 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
|
|
|
snd_usb_mixer_add_control(&cval->head, kctl);
|
|
|
}
|
|
|
|
|
|
+static void get_connector_control_name(struct mixer_build *state,
|
|
|
+ struct usb_audio_term *term,
|
|
|
+ bool is_input, char *name, int name_size)
|
|
|
+{
|
|
|
+ int name_len = get_term_name(state, term, name, name_size, 0);
|
|
|
+
|
|
|
+ if (name_len == 0)
|
|
|
+ strlcpy(name, "Unknown", name_size);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * sound/core/ctljack.c has a convention of naming jack controls
|
|
|
+ * by ending in " Jack". Make it slightly more useful by
|
|
|
+ * indicating Input or Output after the terminal name.
|
|
|
+ */
|
|
|
+ if (is_input)
|
|
|
+ strlcat(name, " - Input Jack", name_size);
|
|
|
+ else
|
|
|
+ strlcat(name, " - Output Jack", name_size);
|
|
|
+}
|
|
|
+
|
|
|
+/* Build a mixer control for a UAC connector control (jack-detect) */
|
|
|
+static void build_connector_control(struct mixer_build *state,
|
|
|
+ struct usb_audio_term *term, bool is_input)
|
|
|
+{
|
|
|
+ struct snd_kcontrol *kctl;
|
|
|
+ struct usb_mixer_elem_info *cval;
|
|
|
+
|
|
|
+ cval = kzalloc(sizeof(*cval), GFP_KERNEL);
|
|
|
+ if (!cval)
|
|
|
+ return;
|
|
|
+ snd_usb_mixer_elem_init_std(&cval->head, state->mixer, term->id);
|
|
|
+ /*
|
|
|
+ * The first byte from reading the UAC2_TE_CONNECTOR control returns the
|
|
|
+ * number of channels connected. This boolean ctl will simply report
|
|
|
+ * if any channels are connected or not.
|
|
|
+ * (Audio20_final.pdf Table 5-10: Connector Control CUR Parameter Block)
|
|
|
+ */
|
|
|
+ cval->control = UAC2_TE_CONNECTOR;
|
|
|
+ cval->val_type = USB_MIXER_BOOLEAN;
|
|
|
+ cval->channels = 1; /* report true if any channel is connected */
|
|
|
+ cval->min = 0;
|
|
|
+ cval->max = 1;
|
|
|
+ kctl = snd_ctl_new1(&usb_bool_master_control_ctl_ro, cval);
|
|
|
+ if (!kctl) {
|
|
|
+ usb_audio_err(state->chip, "cannot malloc kcontrol\n");
|
|
|
+ kfree(cval);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ get_connector_control_name(state, term, is_input, kctl->id.name,
|
|
|
+ sizeof(kctl->id.name));
|
|
|
+ kctl->private_free = snd_usb_mixer_elem_free;
|
|
|
+ snd_usb_mixer_add_control(&cval->head, kctl);
|
|
|
+}
|
|
|
+
|
|
|
static int parse_clock_source_unit(struct mixer_build *state, int unitid,
|
|
|
void *_ftr)
|
|
|
{
|
|
@@ -1423,8 +1571,8 @@ static int parse_clock_source_unit(struct mixer_build *state, int unitid,
|
|
|
* The only property of this unit we are interested in is the
|
|
|
* clock source validity. If that isn't readable, just bail out.
|
|
|
*/
|
|
|
- if (!uac2_control_is_readable(hdr->bmControls,
|
|
|
- ilog2(UAC2_CS_CONTROL_CLOCK_VALID)))
|
|
|
+ if (!uac_v2v3_control_is_readable(hdr->bmControls,
|
|
|
+ UAC2_CS_CONTROL_CLOCK_VALID))
|
|
|
return 0;
|
|
|
|
|
|
cval = kzalloc(sizeof(*cval), GFP_KERNEL);
|
|
@@ -1439,13 +1587,9 @@ static int parse_clock_source_unit(struct mixer_build *state, int unitid,
|
|
|
cval->val_type = USB_MIXER_BOOLEAN;
|
|
|
cval->control = UAC2_CS_CONTROL_CLOCK_VALID;
|
|
|
|
|
|
- if (uac2_control_is_writeable(hdr->bmControls,
|
|
|
- ilog2(UAC2_CS_CONTROL_CLOCK_VALID)))
|
|
|
- kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval);
|
|
|
- else {
|
|
|
- cval->master_readonly = 1;
|
|
|
- kctl = snd_ctl_new1(&usb_feature_unit_ctl_ro, cval);
|
|
|
- }
|
|
|
+ cval->master_readonly = 1;
|
|
|
+ /* From UAC2 5.2.5.1.2 "Only the get request is supported." */
|
|
|
+ kctl = snd_ctl_new1(&usb_bool_master_control_ctl_ro, cval);
|
|
|
|
|
|
if (!kctl) {
|
|
|
kfree(cval);
|
|
@@ -1502,7 +1646,7 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid,
|
|
|
unitid);
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
- } else {
|
|
|
+ } else if (state->mixer->protocol == UAC_VERSION_2) {
|
|
|
struct uac2_feature_unit_descriptor *ftr = _ftr;
|
|
|
if (hdr->bLength < 6) {
|
|
|
usb_audio_err(state->chip,
|
|
@@ -1519,6 +1663,24 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid,
|
|
|
unitid);
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
+ } else { /* UAC_VERSION_3 */
|
|
|
+ struct uac3_feature_unit_descriptor *ftr = _ftr;
|
|
|
+
|
|
|
+ if (hdr->bLength < 7) {
|
|
|
+ usb_audio_err(state->chip,
|
|
|
+ "unit %u: invalid UAC3_FEATURE_UNIT descriptor\n",
|
|
|
+ unitid);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+ csize = 4;
|
|
|
+ channels = (ftr->bLength - 7) / 4 - 1;
|
|
|
+ bmaControls = ftr->bmaControls;
|
|
|
+ if (hdr->bLength < 7 + csize) {
|
|
|
+ usb_audio_err(state->chip,
|
|
|
+ "unit %u: invalid UAC3_FEATURE_UNIT descriptor\n",
|
|
|
+ unitid);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/* parse the source unit */
|
|
@@ -1556,6 +1718,8 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid,
|
|
|
/* check all control types */
|
|
|
for (i = 0; i < 10; i++) {
|
|
|
unsigned int ch_bits = 0;
|
|
|
+ int control = audio_feature_info[i].control;
|
|
|
+
|
|
|
for (j = 0; j < channels; j++) {
|
|
|
unsigned int mask;
|
|
|
|
|
@@ -1571,25 +1735,26 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid,
|
|
|
* (for ease of programming).
|
|
|
*/
|
|
|
if (ch_bits & 1)
|
|
|
- build_feature_ctl(state, _ftr, ch_bits, i,
|
|
|
+ build_feature_ctl(state, _ftr, ch_bits, control,
|
|
|
&iterm, unitid, 0);
|
|
|
if (master_bits & (1 << i))
|
|
|
- build_feature_ctl(state, _ftr, 0, i, &iterm,
|
|
|
- unitid, 0);
|
|
|
+ build_feature_ctl(state, _ftr, 0, control,
|
|
|
+ &iterm, unitid, 0);
|
|
|
}
|
|
|
- } else { /* UAC_VERSION_2 */
|
|
|
+ } else { /* UAC_VERSION_2/3 */
|
|
|
for (i = 0; i < ARRAY_SIZE(audio_feature_info); i++) {
|
|
|
unsigned int ch_bits = 0;
|
|
|
unsigned int ch_read_only = 0;
|
|
|
+ int control = audio_feature_info[i].control;
|
|
|
|
|
|
for (j = 0; j < channels; j++) {
|
|
|
unsigned int mask;
|
|
|
|
|
|
mask = snd_usb_combine_bytes(bmaControls +
|
|
|
csize * (j+1), csize);
|
|
|
- if (uac2_control_is_readable(mask, i)) {
|
|
|
+ if (uac_v2v3_control_is_readable(mask, control)) {
|
|
|
ch_bits |= (1 << j);
|
|
|
- if (!uac2_control_is_writeable(mask, i))
|
|
|
+ if (!uac_v2v3_control_is_writeable(mask, control))
|
|
|
ch_read_only |= (1 << j);
|
|
|
}
|
|
|
}
|
|
@@ -1608,11 +1773,12 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid,
|
|
|
* (for ease of programming).
|
|
|
*/
|
|
|
if (ch_bits & 1)
|
|
|
- build_feature_ctl(state, _ftr, ch_bits, i,
|
|
|
+ build_feature_ctl(state, _ftr, ch_bits, control,
|
|
|
&iterm, unitid, ch_read_only);
|
|
|
- if (uac2_control_is_readable(master_bits, i))
|
|
|
+ if (uac_v2v3_control_is_readable(master_bits, control))
|
|
|
build_feature_ctl(state, _ftr, 0, i, &iterm, unitid,
|
|
|
- !uac2_control_is_writeable(master_bits, i));
|
|
|
+ !uac_v2v3_control_is_writeable(master_bits,
|
|
|
+ control));
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1684,6 +1850,23 @@ static void build_mixer_unit_ctl(struct mixer_build *state,
|
|
|
snd_usb_mixer_add_control(&cval->head, kctl);
|
|
|
}
|
|
|
|
|
|
+static int parse_audio_input_terminal(struct mixer_build *state, int unitid,
|
|
|
+ void *raw_desc)
|
|
|
+{
|
|
|
+ struct usb_audio_term iterm;
|
|
|
+ struct uac2_input_terminal_descriptor *d = raw_desc;
|
|
|
+
|
|
|
+ check_input_term(state, d->bTerminalID, &iterm);
|
|
|
+ if (state->mixer->protocol == UAC_VERSION_2) {
|
|
|
+ /* Check for jack detection. */
|
|
|
+ if (uac_v2v3_control_is_readable(d->bmControls,
|
|
|
+ UAC2_TE_CONNECTOR)) {
|
|
|
+ build_connector_control(state, &iterm, true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* parse a mixer unit
|
|
|
*/
|
|
@@ -2220,6 +2403,7 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid,
|
|
|
static int parse_audio_unit(struct mixer_build *state, int unitid)
|
|
|
{
|
|
|
unsigned char *p1;
|
|
|
+ int protocol = state->mixer->protocol;
|
|
|
|
|
|
if (test_and_set_bit(unitid, state->unitbitmap))
|
|
|
return 0; /* the unit already visited */
|
|
@@ -2230,36 +2414,61 @@ static int parse_audio_unit(struct mixer_build *state, int unitid)
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
|
|
|
- switch (p1[2]) {
|
|
|
- case UAC_INPUT_TERMINAL:
|
|
|
- return 0; /* NOP */
|
|
|
- case UAC_MIXER_UNIT:
|
|
|
- return parse_audio_mixer_unit(state, unitid, p1);
|
|
|
- case UAC2_CLOCK_SOURCE:
|
|
|
- return parse_clock_source_unit(state, unitid, p1);
|
|
|
- case UAC_SELECTOR_UNIT:
|
|
|
- case UAC2_CLOCK_SELECTOR:
|
|
|
- return parse_audio_selector_unit(state, unitid, p1);
|
|
|
- case UAC_FEATURE_UNIT:
|
|
|
- return parse_audio_feature_unit(state, unitid, p1);
|
|
|
- case UAC1_PROCESSING_UNIT:
|
|
|
- /* UAC2_EFFECT_UNIT has the same value */
|
|
|
- if (state->mixer->protocol == UAC_VERSION_1)
|
|
|
- return parse_audio_processing_unit(state, unitid, p1);
|
|
|
- else
|
|
|
- return 0; /* FIXME - effect units not implemented yet */
|
|
|
- case UAC1_EXTENSION_UNIT:
|
|
|
- /* UAC2_PROCESSING_UNIT_V2 has the same value */
|
|
|
- if (state->mixer->protocol == UAC_VERSION_1)
|
|
|
+ if (protocol == UAC_VERSION_1 || protocol == UAC_VERSION_2) {
|
|
|
+ switch (p1[2]) {
|
|
|
+ case UAC_INPUT_TERMINAL:
|
|
|
+ return parse_audio_input_terminal(state, unitid, p1);
|
|
|
+ case UAC_MIXER_UNIT:
|
|
|
+ return parse_audio_mixer_unit(state, unitid, p1);
|
|
|
+ case UAC2_CLOCK_SOURCE:
|
|
|
+ return parse_clock_source_unit(state, unitid, p1);
|
|
|
+ case UAC_SELECTOR_UNIT:
|
|
|
+ case UAC2_CLOCK_SELECTOR:
|
|
|
+ return parse_audio_selector_unit(state, unitid, p1);
|
|
|
+ case UAC_FEATURE_UNIT:
|
|
|
+ return parse_audio_feature_unit(state, unitid, p1);
|
|
|
+ case UAC1_PROCESSING_UNIT:
|
|
|
+ /* UAC2_EFFECT_UNIT has the same value */
|
|
|
+ if (protocol == UAC_VERSION_1)
|
|
|
+ return parse_audio_processing_unit(state, unitid, p1);
|
|
|
+ else
|
|
|
+ return 0; /* FIXME - effect units not implemented yet */
|
|
|
+ case UAC1_EXTENSION_UNIT:
|
|
|
+ /* UAC2_PROCESSING_UNIT_V2 has the same value */
|
|
|
+ if (protocol == UAC_VERSION_1)
|
|
|
+ return parse_audio_extension_unit(state, unitid, p1);
|
|
|
+ else /* UAC_VERSION_2 */
|
|
|
+ return parse_audio_processing_unit(state, unitid, p1);
|
|
|
+ case UAC2_EXTENSION_UNIT_V2:
|
|
|
return parse_audio_extension_unit(state, unitid, p1);
|
|
|
- else /* UAC_VERSION_2 */
|
|
|
+ default:
|
|
|
+ usb_audio_err(state->chip,
|
|
|
+ "unit %u: unexpected type 0x%02x\n", unitid, p1[2]);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+ } else { /* UAC_VERSION_3 */
|
|
|
+ switch (p1[2]) {
|
|
|
+ case UAC_INPUT_TERMINAL:
|
|
|
+ return 0; /* NOP */
|
|
|
+ case UAC3_MIXER_UNIT:
|
|
|
+ return parse_audio_mixer_unit(state, unitid, p1);
|
|
|
+ case UAC3_CLOCK_SOURCE:
|
|
|
+ return parse_clock_source_unit(state, unitid, p1);
|
|
|
+ case UAC3_CLOCK_SELECTOR:
|
|
|
+ return parse_audio_selector_unit(state, unitid, p1);
|
|
|
+ case UAC3_FEATURE_UNIT:
|
|
|
+ return parse_audio_feature_unit(state, unitid, p1);
|
|
|
+ case UAC3_EFFECT_UNIT:
|
|
|
+ return 0; /* FIXME - effect units not implemented yet */
|
|
|
+ case UAC3_PROCESSING_UNIT:
|
|
|
return parse_audio_processing_unit(state, unitid, p1);
|
|
|
- case UAC2_EXTENSION_UNIT_V2:
|
|
|
- return parse_audio_extension_unit(state, unitid, p1);
|
|
|
- default:
|
|
|
- usb_audio_err(state->chip,
|
|
|
- "unit %u: unexpected type 0x%02x\n", unitid, p1[2]);
|
|
|
- return -EINVAL;
|
|
|
+ case UAC3_EXTENSION_UNIT:
|
|
|
+ return parse_audio_extension_unit(state, unitid, p1);
|
|
|
+ default:
|
|
|
+ usb_audio_err(state->chip,
|
|
|
+ "unit %u: unexpected type 0x%02x\n", unitid, p1[2]);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -2330,7 +2539,7 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer)
|
|
|
err = parse_audio_unit(&state, desc->bSourceID);
|
|
|
if (err < 0 && err != -EINVAL)
|
|
|
return err;
|
|
|
- } else { /* UAC_VERSION_2 */
|
|
|
+ } else if (mixer->protocol == UAC_VERSION_2) {
|
|
|
struct uac2_output_terminal_descriptor *desc = p;
|
|
|
|
|
|
if (desc->bLength < sizeof(*desc))
|
|
@@ -2351,6 +2560,33 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer)
|
|
|
err = parse_audio_unit(&state, desc->bCSourceID);
|
|
|
if (err < 0 && err != -EINVAL)
|
|
|
return err;
|
|
|
+
|
|
|
+ if (uac_v2v3_control_is_readable(desc->bmControls,
|
|
|
+ UAC2_TE_CONNECTOR)) {
|
|
|
+ build_connector_control(&state, &state.oterm,
|
|
|
+ false);
|
|
|
+ }
|
|
|
+ } else { /* UAC_VERSION_3 */
|
|
|
+ struct uac3_output_terminal_descriptor *desc = p;
|
|
|
+
|
|
|
+ if (desc->bLength < sizeof(*desc))
|
|
|
+ continue; /* invalid descriptor? */
|
|
|
+ /* mark terminal ID as visited */
|
|
|
+ set_bit(desc->bTerminalID, state.unitbitmap);
|
|
|
+ state.oterm.id = desc->bTerminalID;
|
|
|
+ state.oterm.type = le16_to_cpu(desc->wTerminalType);
|
|
|
+ state.oterm.name = le16_to_cpu(desc->wTerminalDescrStr);
|
|
|
+ err = parse_audio_unit(&state, desc->bSourceID);
|
|
|
+ if (err < 0 && err != -EINVAL)
|
|
|
+ return err;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * For UAC3, use the same approach to also add the
|
|
|
+ * clock selectors
|
|
|
+ */
|
|
|
+ err = parse_audio_unit(&state, desc->bCSourceID);
|
|
|
+ if (err < 0 && err != -EINVAL)
|
|
|
+ return err;
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -2597,6 +2833,9 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
|
|
|
case UAC_VERSION_2:
|
|
|
mixer->protocol = UAC_VERSION_2;
|
|
|
break;
|
|
|
+ case UAC_VERSION_3:
|
|
|
+ mixer->protocol = UAC_VERSION_3;
|
|
|
+ break;
|
|
|
}
|
|
|
|
|
|
if ((err = snd_usb_mixer_controls(mixer)) < 0 ||
|