#include <linux/string.h>
#include <linux/usb.h>
#include <linux/usb/audio.h>
+#include <linux/usb/audio-v2.h>
#include <sound/core.h>
#include <sound/control.h>
USB_MIXER_U16,
};
-enum {
- USB_PROC_UPDOWN = 1,
- USB_PROC_UPDOWN_SWITCH = 1,
- USB_PROC_UPDOWN_MODE_SEL = 2,
-
- USB_PROC_PROLOGIC = 2,
- USB_PROC_PROLOGIC_SWITCH = 1,
- USB_PROC_PROLOGIC_MODE_SEL = 2,
-
- USB_PROC_3DENH = 3,
- USB_PROC_3DENH_SWITCH = 1,
- USB_PROC_3DENH_SPACE = 2,
-
- USB_PROC_REVERB = 4,
- USB_PROC_REVERB_SWITCH = 1,
- USB_PROC_REVERB_LEVEL = 2,
- USB_PROC_REVERB_TIME = 3,
- USB_PROC_REVERB_DELAY = 4,
-
- USB_PROC_CHORUS = 5,
- USB_PROC_CHORUS_SWITCH = 1,
- USB_PROC_CHORUS_LEVEL = 2,
- USB_PROC_CHORUS_RATE = 3,
- USB_PROC_CHORUS_DEPTH = 4,
-
- USB_PROC_DCR = 6,
- USB_PROC_DCR_SWITCH = 1,
- USB_PROC_DCR_RATIO = 2,
- USB_PROC_DCR_MAX_AMP = 3,
- USB_PROC_DCR_THRESHOLD = 4,
- USB_PROC_DCR_ATTACK = 5,
- USB_PROC_DCR_RELEASE = 6,
-};
/*E-mu 0202(0404) eXtension Unit(XU) control*/
enum {
/*
* find an audio control unit with the given unit id
+ * this doesn't return any clock related units, so they need to be handled elsewhere
*/
static void *find_audio_control_unit(struct mixer_build *state, unsigned char unit)
{
p = NULL;
while ((p = snd_usb_find_desc(state->buffer, state->buflen, p,
USB_DT_CS_INTERFACE)) != NULL) {
- if (p[0] >= 4 && p[2] >= UAC_INPUT_TERMINAL && p[2] <= UAC_EXTENSION_UNIT_V1 && p[3] == unit)
+ if (p[0] >= 4 && p[2] >= UAC_INPUT_TERMINAL && p[2] <= UAC2_EXTENSION_UNIT_V2 && p[3] == unit)
return p;
}
return NULL;
* retrieve a mixer value
*/
-static int get_ctl_value(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret)
+static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret)
{
unsigned char buf[2];
int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1;
return -EINVAL;
}
+static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret)
+{
+ unsigned char buf[14]; /* enough space for one range of 4 bytes */
+ unsigned char *val;
+ int ret;
+ __u8 bRequest;
+
+ bRequest = (request == UAC_GET_CUR) ?
+ UAC2_CS_CUR : UAC2_CS_RANGE;
+
+ ret = snd_usb_ctl_msg(cval->mixer->chip->dev,
+ usb_rcvctrlpipe(cval->mixer->chip->dev, 0),
+ bRequest,
+ USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+ validx, cval->mixer->ctrlif | (cval->id << 8),
+ buf, sizeof(buf), 1000);
+
+ if (ret < 0) {
+ snd_printdd(KERN_ERR "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n",
+ request, validx, cval->mixer->ctrlif | (cval->id << 8), cval->val_type);
+ return ret;
+ }
+
+ switch (request) {
+ case UAC_GET_CUR:
+ val = buf;
+ break;
+ case UAC_GET_MIN:
+ val = buf + sizeof(__u16);
+ break;
+ case UAC_GET_MAX:
+ val = buf + sizeof(__u16) * 2;
+ break;
+ case UAC_GET_RES:
+ val = buf + sizeof(__u16) * 3;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ *value_ret = convert_signed_value(cval, snd_usb_combine_bytes(val, sizeof(__u16)));
+
+ return 0;
+}
+
+static int get_ctl_value(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret)
+{
+ return (cval->mixer->protocol == UAC_VERSION_1) ?
+ get_ctl_value_v1(cval, request, validx, value_ret) :
+ get_ctl_value_v2(cval, request, validx, value_ret);
+}
+
static int get_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int *value)
{
return get_ctl_value(cval, UAC_GET_CUR, validx, value);
err = get_cur_mix_raw(cval, channel, value);
if (err < 0) {
if (!cval->mixer->ignore_ctl_error)
- snd_printd(KERN_ERR "cannot get current value for "
- "control %d ch %d: err = %d\n",
+ snd_printd(KERN_ERR "cannot get current value for control %d ch %d: err = %d\n",
cval->control, channel, err);
return err;
}
int request, int validx, int value_set)
{
unsigned char buf[2];
- int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1;
- int timeout = 10;
+ int val_len, timeout = 10;
+
+ if (cval->mixer->protocol == UAC_VERSION_1) {
+ val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1;
+ } else { /* UAC_VERSION_2 */
+ /* audio class v2 controls are always 2 bytes in size */
+ val_len = sizeof(__u16);
+
+ /* FIXME */
+ if (request != UAC_SET_CUR) {
+ snd_printdd(KERN_WARNING "RANGE setting not yet supported\n");
+ return -EINVAL;
+ }
+
+ request = UAC2_CS_CUR;
+ }
value_set = convert_bytes_value(cval, value_set);
buf[0] = value_set & 0xff;
int index, int value)
{
int err;
+ unsigned int read_only = (channel == 0) ?
+ cval->master_readonly :
+ cval->ch_readonly & (1 << (channel - 1));
+
+ if (read_only) {
+ snd_printdd(KERN_INFO "%s(): channel %d of control %d is read_only\n",
+ __func__, channel, cval->control);
+ return 0;
+ }
+
err = snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, (cval->control << 8) | channel,
value);
if (err < 0)
*/
static int check_input_term(struct mixer_build *state, int id, struct usb_audio_term *term)
{
- unsigned char *p1;
+ void *p1;
memset(term, 0, sizeof(*term));
while ((p1 = find_audio_control_unit(state, id)) != NULL) {
+ unsigned char *hdr = p1;
term->id = id;
- switch (p1[2]) {
+ switch (hdr[2]) {
case UAC_INPUT_TERMINAL:
- term->type = combine_word(p1 + 4);
- term->channels = p1[7];
- term->chconfig = combine_word(p1 + 8);
- term->name = p1[11];
+ 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;
+ 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:
- id = p1[4];
+ 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:
- term->type = p1[2] << 16; /* virtual type */
- term->channels = p1[5 + p1[4]];
- term->chconfig = combine_word(p1 + 6 + p1[4]);
- term->name = p1[p1[0] - 1];
+ }
+ 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 UAC_SELECTOR_UNIT: {
+ struct uac_selector_unit_descriptor *d = p1;
/* call recursively to retrieve the channel info */
- if (check_input_term(state, p1[5], term) < 0)
+ if (check_input_term(state, d->baSourceID[0], term) < 0)
return -ENODEV;
- term->type = p1[2] << 16; /* virtual type */
+ term->type = d->bDescriptorSubtype << 16; /* virtual type */
term->id = id;
- term->name = p1[9 + p1[0] - 1];
+ term->name = uac_selector_unit_iSelector(d);
return 0;
+ }
case UAC_PROCESSING_UNIT_V1:
- case UAC_EXTENSION_UNIT_V1:
- if (p1[6] == 1) {
- id = p1[7];
+ case UAC_EXTENSION_UNIT_V1: {
+ struct uac_processing_unit_descriptor *d = p1;
+ if (d->bNrInPins) {
+ id = d->baSourceID[0];
break; /* continue to parse */
}
- term->type = p1[2] << 16; /* virtual type */
- term->channels = p1[7 + p1[6]];
- term->chconfig = combine_word(p1 + 8 + p1[6]);
- term->name = p1[12 + p1[6] + p1[11 + p1[6]]];
+ 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;
+ }
default:
return -ENODEV;
}
};
static struct usb_feature_control_info audio_feature_info[] = {
- { "Mute", USB_MIXER_INV_BOOLEAN },
- { "Volume", USB_MIXER_S16 },
+ { "Mute", USB_MIXER_INV_BOOLEAN },
+ { "Volume", USB_MIXER_S16 },
{ "Tone Control - Bass", USB_MIXER_S8 },
{ "Tone Control - Mid", USB_MIXER_S8 },
{ "Tone Control - Treble", USB_MIXER_S8 },
{ "Graphic Equalizer", USB_MIXER_S8 }, /* FIXME: not implemeted yet */
- { "Auto Gain Control", USB_MIXER_BOOLEAN },
- { "Delay Control", USB_MIXER_U16 },
- { "Bass Boost", USB_MIXER_BOOLEAN },
- { "Loudness", USB_MIXER_BOOLEAN },
+ { "Auto Gain Control", USB_MIXER_BOOLEAN },
+ { "Delay Control", USB_MIXER_U16 },
+ { "Bass Boost", USB_MIXER_BOOLEAN },
+ { "Loudness", USB_MIXER_BOOLEAN },
+ /* UAC2 specific */
+ { "Input Gain Control", USB_MIXER_U16 },
+ { "Input Gain Pad Control", USB_MIXER_BOOLEAN },
+ { "Phase Inverter Control", USB_MIXER_BOOLEAN },
};
.put = mixer_ctl_feature_put,
};
+/* the read-only variant */
+static struct snd_kcontrol_new usb_feature_unit_ctl_ro = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "", /* will be filled later manually */
+ .info = mixer_ctl_feature_info,
+ .get = mixer_ctl_feature_get,
+ .put = NULL,
+};
+
/*
* build a feature control
return strlcat(kctl->id.name, str, sizeof(kctl->id.name));
}
-static void build_feature_ctl(struct mixer_build *state, unsigned char *desc,
+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)
+ struct usb_audio_term *iterm, int unitid,
+ int readonly_mask)
{
+ struct uac_feature_unit_descriptor *desc = raw_desc;
unsigned int len = 0;
int mapped_name = 0;
- int nameid = desc[desc[0] - 1];
+ int nameid = uac_feature_unit_iFeature(desc);
struct snd_kcontrol *kctl;
struct usb_mixer_elem_info *cval;
const struct usbmix_name_map *map;
control++; /* change from zero-based to 1-based value */
- if (control == UAC_GRAPHIC_EQUALIZER_CONTROL) {
+ if (control == UAC_FU_GRAPHIC_EQUALIZER) {
/* FIXME: not supported yet */
return;
}
cval->control = control;
cval->cmask = ctl_mask;
cval->val_type = audio_feature_info[control-1].type;
- if (ctl_mask == 0)
+ if (ctl_mask == 0) {
cval->channels = 1; /* master channel */
- else {
+ cval->master_readonly = readonly_mask;
+ } else {
int i, c = 0;
for (i = 0; i < 16; i++)
if (ctl_mask & (1 << i))
c++;
cval->channels = c;
+ cval->ch_readonly = readonly_mask;
}
/* get min/max values */
get_min_max(cval, 0);
- kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval);
+ /* if all channels in the mask are marked read-only, make the control
+ * read-only. set_cur_mix_value() will check the mask again and won't
+ * issue write commands to read-only channels. */
+ if (cval->channels == readonly_mask)
+ kctl = snd_ctl_new1(&usb_feature_unit_ctl_ro, cval);
+ else
+ kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval);
+
if (! kctl) {
snd_printk(KERN_ERR "cannot malloc kcontrol\n");
kfree(cval);
kctl->id.name, sizeof(kctl->id.name));
switch (control) {
- case UAC_MUTE_CONTROL:
- case UAC_VOLUME_CONTROL:
+ case UAC_FU_MUTE:
+ case UAC_FU_VOLUME:
/* determine the control name. the rule is:
* - if a name id is given in descriptor, use it.
* - if the connected input can be determined, then use the name
len = append_ctl_name(kctl, " Playback");
}
}
- append_ctl_name(kctl, control == UAC_MUTE_CONTROL ?
+ append_ctl_name(kctl, control == UAC_FU_MUTE ?
" Switch" : " Volume");
- if (control == UAC_VOLUME_CONTROL) {
+ if (control == UAC_FU_VOLUME) {
kctl->tlv.c = mixer_vol_tlv;
kctl->vd[0].access |=
SNDRV_CTL_ELEM_ACCESS_TLV_READ |
struct usb_audio_term iterm;
unsigned int master_bits, first_ch_bits;
int err, csize;
- struct uac_feature_unit_descriptor *ftr = _ftr;
+ struct uac_feature_unit_descriptor *hdr = _ftr;
+ __u8 *bmaControls;
- if (ftr->bLength < 7 || ! (csize = ftr->bControlSize) || ftr->bLength < 7 + csize) {
+ if (state->mixer->protocol == UAC_VERSION_1) {
+ csize = hdr->bControlSize;
+ channels = (hdr->bLength - 7) / csize - 1;
+ bmaControls = hdr->bmaControls;
+ } else {
+ struct uac2_feature_unit_descriptor *ftr = _ftr;
+ csize = 4;
+ channels = (hdr->bLength - 6) / 4 - 1;
+ bmaControls = ftr->bmaControls;
+ }
+
+ if (hdr->bLength < 7 || !csize || hdr->bLength < 7 + csize) {
snd_printk(KERN_ERR "usbaudio: unit %u: invalid UAC_FEATURE_UNIT descriptor\n", unitid);
return -EINVAL;
}
/* parse the source unit */
- if ((err = parse_audio_unit(state, ftr->bSourceID)) < 0)
+ if ((err = parse_audio_unit(state, hdr->bSourceID)) < 0)
return err;
/* determine the input source type and name */
- if (check_input_term(state, ftr->bSourceID, &iterm) < 0)
+ if (check_input_term(state, hdr->bSourceID, &iterm) < 0)
return -EINVAL;
- channels = (ftr->bLength - 7) / csize - 1;
-
- master_bits = snd_usb_combine_bytes(ftr->controls, csize);
+ master_bits = snd_usb_combine_bytes(bmaControls, csize);
/* master configuration quirks */
switch (state->chip->usb_id) {
case USB_ID(0x08bb, 0x2702):
snd_printk(KERN_INFO
"usbmixer: master volume quirk for PCM2702 chip\n");
/* disable non-functional volume control */
- master_bits &= ~UAC_FU_VOLUME;
+ master_bits &= ~UAC_CONTROL_BIT(UAC_FU_VOLUME);
break;
}
if (channels > 0)
- first_ch_bits = snd_usb_combine_bytes(ftr->controls + csize, csize);
+ first_ch_bits = snd_usb_combine_bytes(bmaControls + csize, csize);
else
first_ch_bits = 0;
- /* check all control types */
- for (i = 0; i < 10; i++) {
- unsigned int ch_bits = 0;
- for (j = 0; j < channels; j++) {
- unsigned int mask = snd_usb_combine_bytes(ftr->controls + csize * (j+1), csize);
- if (mask & (1 << i))
- ch_bits |= (1 << j);
+
+ if (state->mixer->protocol == UAC_VERSION_1) {
+ /* check all control types */
+ for (i = 0; i < 10; i++) {
+ unsigned int ch_bits = 0;
+ for (j = 0; j < channels; j++) {
+ unsigned int mask = snd_usb_combine_bytes(bmaControls + csize * (j+1), csize);
+ if (mask & (1 << i))
+ ch_bits |= (1 << j);
+ }
+ /* audio class v1 controls are never read-only */
+ if (ch_bits & 1) /* the first channel must be set (for ease of programming) */
+ build_feature_ctl(state, _ftr, ch_bits, i, &iterm, unitid, 0);
+ if (master_bits & (1 << i))
+ build_feature_ctl(state, _ftr, 0, i, &iterm, unitid, 0);
+ }
+ } else { /* UAC_VERSION_2 */
+ for (i = 0; i < 30/2; i++) {
+ /* From the USB Audio spec v2.0:
+ bmaControls() is a (ch+1)-element array of 4-byte bitmaps,
+ each containing a set of bit pairs. If a Control is present,
+ it must be Host readable. If a certain Control is not
+ present then the bit pair must be set to 0b00.
+ If a Control is present but read-only, the bit pair must be
+ set to 0b01. If a Control is also Host programmable, the bit
+ pair must be set to 0b11. The value 0b10 is not allowed. */
+ unsigned int ch_bits = 0;
+ unsigned int ch_read_only = 0;
+
+ for (j = 0; j < channels; j++) {
+ unsigned int mask = snd_usb_combine_bytes(bmaControls + csize * (j+1), csize);
+ if (uac2_control_is_readable(mask, i)) {
+ ch_bits |= (1 << j);
+ if (!uac2_control_is_writeable(mask, i))
+ ch_read_only |= (1 << j);
+ }
+ }
+
+ /* NOTE: build_feature_ctl() will mark the control read-only if all channels
+ * are marked read-only in the descriptors. Otherwise, the control will be
+ * reported as writeable, but the driver will not actually issue a write
+ * command for read-only channels */
+ if (ch_bits & 1) /* the first channel must be set (for ease of programming) */
+ build_feature_ctl(state, _ftr, ch_bits, i, &iterm, unitid, ch_read_only);
+ if (uac2_control_is_readable(master_bits, i))
+ build_feature_ctl(state, _ftr, 0, i, &iterm, unitid,
+ !uac2_control_is_writeable(master_bits, i));
}
- if (ch_bits & 1) /* the first channel must be set (for ease of programming) */
- build_feature_ctl(state, _ftr, ch_bits, i, &iterm, unitid);
- if (master_bits & (1 << i))
- build_feature_ctl(state, _ftr, 0, i, &iterm, unitid);
}
return 0;
* input channel number (zero based) is given in control field instead.
*/
-static void build_mixer_unit_ctl(struct mixer_build *state, unsigned char *desc,
+static void build_mixer_unit_ctl(struct mixer_build *state,
+ struct uac_mixer_unit_descriptor *desc,
int in_pin, int in_ch, int unitid,
struct usb_audio_term *iterm)
{
struct usb_mixer_elem_info *cval;
- unsigned int input_pins = desc[4];
- unsigned int num_outs = desc[5 + input_pins];
+ unsigned int num_outs = uac_mixer_unit_bNrChannels(desc);
unsigned int i, len;
struct snd_kcontrol *kctl;
const struct usbmix_name_map *map;
cval->control = in_ch + 1; /* based on 1 */
cval->val_type = USB_MIXER_S16;
for (i = 0; i < num_outs; i++) {
- if (check_matrix_bitmap(desc + 9 + input_pins, in_ch, i, num_outs)) {
+ if (check_matrix_bitmap(uac_mixer_unit_bmControls(desc, state->mixer->protocol), in_ch, i, num_outs)) {
cval->cmask |= (1 << i);
cval->channels++;
}
/*
* parse a mixer unit
*/
-static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, unsigned char *desc)
+static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, void *raw_desc)
{
+ struct uac_mixer_unit_descriptor *desc = raw_desc;
struct usb_audio_term iterm;
int input_pins, num_ins, num_outs;
int pin, ich, err;
- if (desc[0] < 11 || ! (input_pins = desc[4]) || ! (num_outs = desc[5 + input_pins])) {
+ if (desc->bLength < 11 || ! (input_pins = desc->bNrInPins) || ! (num_outs = uac_mixer_unit_bNrChannels(desc))) {
snd_printk(KERN_ERR "invalid MIXER UNIT descriptor %d\n", unitid);
return -EINVAL;
}
/* no bmControls field (e.g. Maya44) -> ignore */
- if (desc[0] <= 10 + input_pins) {
+ if (desc->bLength <= 10 + input_pins) {
snd_printdd(KERN_INFO "MU %d has no bmControls field\n", unitid);
return 0;
}
num_ins = 0;
ich = 0;
for (pin = 0; pin < input_pins; pin++) {
- err = parse_audio_unit(state, desc[5 + pin]);
+ err = parse_audio_unit(state, desc->baSourceID[pin]);
if (err < 0)
return err;
- err = check_input_term(state, desc[5 + pin], &iterm);
+ err = check_input_term(state, desc->baSourceID[pin], &iterm);
if (err < 0)
return err;
num_ins += iterm.channels;
int och, ich_has_controls = 0;
for (och = 0; och < num_outs; ++och) {
- if (check_matrix_bitmap(desc + 9 + input_pins,
+ if (check_matrix_bitmap(uac_mixer_unit_bmControls(desc, state->mixer->protocol),
ich, och, num_outs)) {
ich_has_controls = 1;
break;
};
static struct procunit_value_info updown_proc_info[] = {
- { USB_PROC_UPDOWN_SWITCH, "Switch", USB_MIXER_BOOLEAN },
- { USB_PROC_UPDOWN_MODE_SEL, "Mode Select", USB_MIXER_U8, 1 },
+ { UAC_UD_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+ { UAC_UD_MODE_SELECT, "Mode Select", USB_MIXER_U8, 1 },
{ 0 }
};
static struct procunit_value_info prologic_proc_info[] = {
- { USB_PROC_PROLOGIC_SWITCH, "Switch", USB_MIXER_BOOLEAN },
- { USB_PROC_PROLOGIC_MODE_SEL, "Mode Select", USB_MIXER_U8, 1 },
+ { UAC_DP_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+ { UAC_DP_MODE_SELECT, "Mode Select", USB_MIXER_U8, 1 },
{ 0 }
};
static struct procunit_value_info threed_enh_proc_info[] = {
- { USB_PROC_3DENH_SWITCH, "Switch", USB_MIXER_BOOLEAN },
- { USB_PROC_3DENH_SPACE, "Spaciousness", USB_MIXER_U8 },
+ { UAC_3D_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+ { UAC_3D_SPACE, "Spaciousness", USB_MIXER_U8 },
{ 0 }
};
static struct procunit_value_info reverb_proc_info[] = {
- { USB_PROC_REVERB_SWITCH, "Switch", USB_MIXER_BOOLEAN },
- { USB_PROC_REVERB_LEVEL, "Level", USB_MIXER_U8 },
- { USB_PROC_REVERB_TIME, "Time", USB_MIXER_U16 },
- { USB_PROC_REVERB_DELAY, "Delay", USB_MIXER_U8 },
+ { UAC_REVERB_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+ { UAC_REVERB_LEVEL, "Level", USB_MIXER_U8 },
+ { UAC_REVERB_TIME, "Time", USB_MIXER_U16 },
+ { UAC_REVERB_FEEDBACK, "Feedback", USB_MIXER_U8 },
{ 0 }
};
static struct procunit_value_info chorus_proc_info[] = {
- { USB_PROC_CHORUS_SWITCH, "Switch", USB_MIXER_BOOLEAN },
- { USB_PROC_CHORUS_LEVEL, "Level", USB_MIXER_U8 },
- { USB_PROC_CHORUS_RATE, "Rate", USB_MIXER_U16 },
- { USB_PROC_CHORUS_DEPTH, "Depth", USB_MIXER_U16 },
+ { UAC_CHORUS_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+ { UAC_CHORUS_LEVEL, "Level", USB_MIXER_U8 },
+ { UAC_CHORUS_RATE, "Rate", USB_MIXER_U16 },
+ { UAC_CHORUS_DEPTH, "Depth", USB_MIXER_U16 },
{ 0 }
};
static struct procunit_value_info dcr_proc_info[] = {
- { USB_PROC_DCR_SWITCH, "Switch", USB_MIXER_BOOLEAN },
- { USB_PROC_DCR_RATIO, "Ratio", USB_MIXER_U16 },
- { USB_PROC_DCR_MAX_AMP, "Max Amp", USB_MIXER_S16 },
- { USB_PROC_DCR_THRESHOLD, "Threshold", USB_MIXER_S16 },
- { USB_PROC_DCR_ATTACK, "Attack Time", USB_MIXER_U16 },
- { USB_PROC_DCR_RELEASE, "Release Time", USB_MIXER_U16 },
+ { UAC_DCR_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+ { UAC_DCR_RATE, "Ratio", USB_MIXER_U16 },
+ { UAC_DCR_MAXAMPL, "Max Amp", USB_MIXER_S16 },
+ { UAC_DCR_THRESHOLD, "Threshold", USB_MIXER_S16 },
+ { UAC_DCR_ATTACK_TIME, "Attack Time", USB_MIXER_U16 },
+ { UAC_DCR_RELEASE_TIME, "Release Time", USB_MIXER_U16 },
{ 0 }
};
static struct procunit_info procunits[] = {
- { USB_PROC_UPDOWN, "Up Down", updown_proc_info },
- { USB_PROC_PROLOGIC, "Dolby Prologic", prologic_proc_info },
- { USB_PROC_3DENH, "3D Stereo Extender", threed_enh_proc_info },
- { USB_PROC_REVERB, "Reverb", reverb_proc_info },
- { USB_PROC_CHORUS, "Chorus", chorus_proc_info },
- { USB_PROC_DCR, "DCR", dcr_proc_info },
+ { UAC_PROCESS_UP_DOWNMIX, "Up Down", updown_proc_info },
+ { UAC_PROCESS_DOLBY_PROLOGIC, "Dolby Prologic", prologic_proc_info },
+ { UAC_PROCESS_STEREO_EXTENDER, "3D Stereo Extender", threed_enh_proc_info },
+ { UAC_PROCESS_REVERB, "Reverb", reverb_proc_info },
+ { UAC_PROCESS_CHORUS, "Chorus", chorus_proc_info },
+ { UAC_PROCESS_DYN_RANGE_COMP, "DCR", dcr_proc_info },
{ 0 },
};
/*
* predefined data for extension units
*/
static struct procunit_value_info clock_rate_xu_info[] = {
- { USB_XU_CLOCK_RATE_SELECTOR, "Selector", USB_MIXER_U8, 0 },
- { 0 }
+ { USB_XU_CLOCK_RATE_SELECTOR, "Selector", USB_MIXER_U8, 0 },
+ { 0 }
};
static struct procunit_value_info clock_source_xu_info[] = {
{ USB_XU_CLOCK_SOURCE_SELECTOR, "External", USB_MIXER_BOOLEAN },
/*
* build a processing/extension unit
*/
-static int build_audio_procunit(struct mixer_build *state, int unitid, unsigned char *dsc, struct procunit_info *list, char *name)
+static int build_audio_procunit(struct mixer_build *state, int unitid, void *raw_desc, struct procunit_info *list, char *name)
{
- int num_ins = dsc[6];
+ struct uac_processing_unit_descriptor *desc = raw_desc;
+ int num_ins = desc->bNrInPins;
struct usb_mixer_elem_info *cval;
struct snd_kcontrol *kctl;
int i, err, nameid, type, len;
0, NULL, default_value_info
};
- if (dsc[0] < 13 || dsc[0] < 13 + num_ins || dsc[0] < num_ins + dsc[11 + num_ins]) {
+ if (desc->bLength < 13 || desc->bLength < 13 + num_ins ||
+ desc->bLength < num_ins + uac_processing_unit_bControlSize(desc, state->mixer->protocol)) {
snd_printk(KERN_ERR "invalid %s descriptor (id %d)\n", name, unitid);
return -EINVAL;
}
for (i = 0; i < num_ins; i++) {
- if ((err = parse_audio_unit(state, dsc[7 + i])) < 0)
+ if ((err = parse_audio_unit(state, desc->baSourceID[i])) < 0)
return err;
}
- type = combine_word(&dsc[4]);
+ type = le16_to_cpu(desc->wProcessType);
for (info = list; info && info->type; info++)
if (info->type == type)
break;
info = &default_info;
for (valinfo = info->values; valinfo->control; valinfo++) {
- /* FIXME: bitmap might be longer than 8bit */
- if (! (dsc[12 + num_ins] & (1 << (valinfo->control - 1))))
+ __u8 *controls = uac_processing_unit_bmControls(desc, state->mixer->protocol);
+
+ if (! (controls[valinfo->control / 8] & (1 << ((valinfo->control % 8) - 1))))
continue;
map = find_map(state, unitid, valinfo->control);
if (check_ignored_ctl(map))
cval->channels = 1;
/* get min/max values */
- if (type == USB_PROC_UPDOWN && cval->control == USB_PROC_UPDOWN_MODE_SEL) {
+ if (type == UAC_PROCESS_UP_DOWNMIX && cval->control == UAC_UD_MODE_SELECT) {
+ __u8 *control_spec = uac_processing_unit_specific(desc, state->mixer->protocol);
/* FIXME: hard-coded */
cval->min = 1;
- cval->max = dsc[15];
+ cval->max = control_spec[0];
cval->res = 1;
cval->initialized = 1;
} else {
else if (info->name)
strlcpy(kctl->id.name, info->name, sizeof(kctl->id.name));
else {
- nameid = dsc[12 + num_ins + dsc[11 + num_ins]];
+ nameid = uac_processing_unit_iProcessing(desc, state->mixer->protocol);
len = 0;
if (nameid)
len = snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name));
}
-static int parse_audio_processing_unit(struct mixer_build *state, int unitid, unsigned char *desc)
+static int parse_audio_processing_unit(struct mixer_build *state, int unitid, void *raw_desc)
{
- return build_audio_procunit(state, unitid, desc, procunits, "Processing Unit");
+ return build_audio_procunit(state, unitid, raw_desc, procunits, "Processing Unit");
}
-static int parse_audio_extension_unit(struct mixer_build *state, int unitid, unsigned char *desc)
+static int parse_audio_extension_unit(struct mixer_build *state, int unitid, void *raw_desc)
{
- return build_audio_procunit(state, unitid, desc, extunits, "Extension Unit");
+ /* Note that we parse extension units with processing unit descriptors.
+ * That's ok as the layout is the same */
+ return build_audio_procunit(state, unitid, raw_desc, extunits, "Extension Unit");
}
/*
* parse a selector unit
*/
-static int parse_audio_selector_unit(struct mixer_build *state, int unitid, unsigned char *desc)
+static int parse_audio_selector_unit(struct mixer_build *state, int unitid, void *raw_desc)
{
- unsigned int num_ins = desc[4];
+ struct uac_selector_unit_descriptor *desc = raw_desc;
unsigned int i, nameid, len;
int err;
struct usb_mixer_elem_info *cval;
const struct usbmix_name_map *map;
char **namelist;
- if (! num_ins || desc[0] < 5 + num_ins) {
+ if (!desc->bNrInPins || desc->bLength < 5 + desc->bNrInPins) {
snd_printk(KERN_ERR "invalid SELECTOR UNIT descriptor %d\n", unitid);
return -EINVAL;
}
- for (i = 0; i < num_ins; i++) {
- if ((err = parse_audio_unit(state, desc[5 + i])) < 0)
+ for (i = 0; i < desc->bNrInPins; i++) {
+ if ((err = parse_audio_unit(state, desc->baSourceID[i])) < 0)
return err;
}
- if (num_ins == 1) /* only one ? nonsense! */
+ if (desc->bNrInPins == 1) /* only one ? nonsense! */
return 0;
map = find_map(state, unitid, 0);
cval->val_type = USB_MIXER_U8;
cval->channels = 1;
cval->min = 1;
- cval->max = num_ins;
+ cval->max = desc->bNrInPins;
cval->res = 1;
cval->initialized = 1;
- namelist = kmalloc(sizeof(char *) * num_ins, GFP_KERNEL);
+ namelist = kmalloc(sizeof(char *) * desc->bNrInPins, GFP_KERNEL);
if (! namelist) {
snd_printk(KERN_ERR "cannot malloc\n");
kfree(cval);
return -ENOMEM;
}
#define MAX_ITEM_NAME_LEN 64
- for (i = 0; i < num_ins; i++) {
+ for (i = 0; i < desc->bNrInPins; i++) {
struct usb_audio_term iterm;
len = 0;
namelist[i] = kmalloc(MAX_ITEM_NAME_LEN, GFP_KERNEL);
}
len = check_mapped_selector_name(state, unitid, i, namelist[i],
MAX_ITEM_NAME_LEN);
- if (! len && check_input_term(state, desc[5 + i], &iterm) >= 0)
+ if (! len && check_input_term(state, desc->baSourceID[i], &iterm) >= 0)
len = get_term_name(state, &iterm, namelist[i], MAX_ITEM_NAME_LEN, 0);
if (! len)
sprintf(namelist[i], "Input %d", i);
kctl->private_value = (unsigned long)namelist;
kctl->private_free = usb_mixer_selector_elem_free;
- nameid = desc[desc[0] - 1];
+ nameid = uac_selector_unit_iSelector(desc);
len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name));
if (len)
;
}
snd_printdd(KERN_INFO "[%d] SU [%s] items = %d\n",
- cval->id, kctl->id.name, num_ins);
+ cval->id, kctl->id.name, desc->bNrInPins);
if ((err = add_control_to_empty(state, kctl)) < 0)
return err;
case UAC_FEATURE_UNIT:
return parse_audio_feature_unit(state, unitid, p1);
case UAC_PROCESSING_UNIT_V1:
- return parse_audio_processing_unit(state, unitid, p1);
+ /* 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 UAC_EXTENSION_UNIT_V1:
- return parse_audio_extension_unit(state, unitid, p1);
+ /* UAC2_PROCESSING_UNIT_V2 has the same value */
+ if (state->mixer->protocol == UAC_VERSION_1)
+ return parse_audio_extension_unit(state, unitid, p1);
+ else /* UAC_VERSION_2 */
+ return parse_audio_processing_unit(state, unitid, p1);
default:
snd_printk(KERN_ERR "usbaudio: unit %u: unexpected type 0x%02x\n", unitid, p1[2]);
return -EINVAL;
*/
static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer)
{
- struct uac_output_terminal_descriptor_v1 *desc;
struct mixer_build state;
int err;
const struct usbmix_ctl_map *map;
struct usb_host_interface *hostif;
+ void *p;
hostif = &usb_ifnum_to_if(mixer->chip->dev, mixer->ctrlif)->altsetting[0];
memset(&state, 0, sizeof(state));
}
}
- desc = NULL;
- while ((desc = snd_usb_find_csint_desc(hostif->extra, hostif->extralen, desc, UAC_OUTPUT_TERMINAL)) != NULL) {
- if (desc->bLength < 9)
- continue; /* invalid descriptor? */
- set_bit(desc->bTerminalID, state.unitbitmap); /* mark terminal ID as visited */
- state.oterm.id = desc->bTerminalID;
- state.oterm.type = le16_to_cpu(desc->wTerminalType);
- state.oterm.name = desc->iTerminal;
- err = parse_audio_unit(&state, desc->bSourceID);
- if (err < 0)
- return err;
+ p = NULL;
+ while ((p = snd_usb_find_csint_desc(hostif->extra, hostif->extralen, p, UAC_OUTPUT_TERMINAL)) != NULL) {
+ if (mixer->protocol == UAC_VERSION_1) {
+ struct uac_output_terminal_descriptor_v1 *desc = p;
+
+ if (desc->bLength < sizeof(*desc))
+ continue; /* invalid descriptor? */
+ set_bit(desc->bTerminalID, state.unitbitmap); /* mark terminal ID as visited */
+ state.oterm.id = desc->bTerminalID;
+ state.oterm.type = le16_to_cpu(desc->wTerminalType);
+ state.oterm.name = desc->iTerminal;
+ err = parse_audio_unit(&state, desc->bSourceID);
+ if (err < 0)
+ return err;
+ } else { /* UAC_VERSION_2 */
+ struct uac2_output_terminal_descriptor *desc = p;
+
+ if (desc->bLength < sizeof(*desc))
+ continue; /* invalid descriptor? */
+ set_bit(desc->bTerminalID, state.unitbitmap); /* mark terminal ID as visited */
+ state.oterm.id = desc->bTerminalID;
+ state.oterm.type = le16_to_cpu(desc->wTerminalType);
+ state.oterm.name = desc->iTerminal;
+ err = parse_audio_unit(&state, desc->bSourceID);
+ if (err < 0)
+ return err;
+ }
}
+
return 0;
}
}
}
-static void snd_usb_mixer_status_complete(struct urb *urb)
+static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer,
+ int attribute, int value, int index)
+{
+ struct usb_mixer_elem_info *info;
+ __u8 unitid = (index >> 8) & 0xff;
+ __u8 control = (value >> 8) & 0xff;
+ __u8 channel = value & 0xff;
+
+ if (channel >= MAX_CHANNELS) {
+ snd_printk(KERN_DEBUG "%s(): bogus channel number %d\n",
+ __func__, channel);
+ return;
+ }
+
+ for (info = mixer->id_elems[unitid]; info; info = info->next_id_elem) {
+ if (info->control != control)
+ continue;
+
+ switch (attribute) {
+ case UAC2_CS_CUR:
+ /* invalidate cache, so the value is read from the device */
+ if (channel)
+ info->cached &= ~(1 << channel);
+ else /* master channel */
+ info->cached = 0;
+
+ snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+ info->elem_id);
+ break;
+
+ case UAC2_CS_RANGE:
+ /* TODO */
+ break;
+
+ case UAC2_CS_MEM:
+ /* TODO */
+ break;
+
+ default:
+ snd_printk(KERN_DEBUG "unknown attribute %d in interrupt\n",
+ attribute);
+ break;
+ } /* switch */
+ }
+}
+
+static void snd_usb_mixer_interrupt(struct urb *urb)
{
struct usb_mixer_interface *mixer = urb->context;
+ int len = urb->actual_length;
- if (urb->status == 0) {
- u8 *buf = urb->transfer_buffer;
- int i;
+ if (urb->status != 0)
+ goto requeue;
- for (i = urb->actual_length; i >= 2; buf += 2, i -= 2) {
+ if (mixer->protocol == UAC_VERSION_1) {
+ struct uac1_status_word *status;
+
+ for (status = urb->transfer_buffer;
+ len >= sizeof(*status);
+ len -= sizeof(*status), status++) {
snd_printd(KERN_DEBUG "status interrupt: %02x %02x\n",
- buf[0], buf[1]);
+ status->bStatusType,
+ status->bOriginator);
+
/* ignore any notifications not from the control interface */
- if ((buf[0] & 0x0f) != 0)
+ if ((status->bStatusType & UAC1_STATUS_TYPE_ORIG_MASK) !=
+ UAC1_STATUS_TYPE_ORIG_AUDIO_CONTROL_IF)
continue;
- if (!(buf[0] & 0x40))
- snd_usb_mixer_notify_id(mixer, buf[1]);
+
+ if (status->bStatusType & UAC1_STATUS_TYPE_MEM_CHANGED)
+ snd_usb_mixer_rc_memory_change(mixer, status->bOriginator);
else
- snd_usb_mixer_rc_memory_change(mixer, buf[1]);
+ snd_usb_mixer_notify_id(mixer, status->bOriginator);
+ }
+ } else { /* UAC_VERSION_2 */
+ struct uac2_interrupt_data_msg *msg;
+
+ for (msg = urb->transfer_buffer;
+ len >= sizeof(*msg);
+ len -= sizeof(*msg), msg++) {
+ /* drop vendor specific and endpoint requests */
+ if ((msg->bInfo & UAC2_INTERRUPT_DATA_MSG_VENDOR) ||
+ (msg->bInfo & UAC2_INTERRUPT_DATA_MSG_EP))
+ continue;
+
+ snd_usb_mixer_interrupt_v2(mixer, msg->bAttribute,
+ le16_to_cpu(msg->wValue),
+ le16_to_cpu(msg->wIndex));
}
}
+
+requeue:
if (urb->status != -ENOENT && urb->status != -ECONNRESET) {
urb->dev = mixer->chip->dev;
usb_submit_urb(urb, GFP_ATOMIC);
usb_fill_int_urb(mixer->urb, mixer->chip->dev,
usb_rcvintpipe(mixer->chip->dev, epnum),
transfer_buffer, buffer_length,
- snd_usb_mixer_status_complete, mixer, ep->bInterval);
+ snd_usb_mixer_interrupt, mixer, ep->bInterval);
usb_submit_urb(mixer->urb, GFP_KERNEL);
return 0;
}
struct usb_mixer_interface *mixer;
struct snd_info_entry *entry;
struct usb_host_interface *host_iface;
- int err, protocol;
+ int err;
strcpy(chip->card->mixername, "USB Mixer");
}
host_iface = &usb_ifnum_to_if(chip->dev, ctrlif)->altsetting[0];
- protocol = host_iface->desc.bInterfaceProtocol;
-
- /* FIXME! */
- if (protocol != UAC_VERSION_1) {
- snd_printk(KERN_WARNING "mixer interface protocol 0x%02x not yet supported\n",
- protocol);
- return 0;
- }
+ mixer->protocol = get_iface_desc(host_iface)->bInterfaceProtocol;
if ((err = snd_usb_mixer_controls(mixer)) < 0 ||
(err = snd_usb_mixer_status_create(mixer)) < 0)