// SPDX-License-Identifier: GPL-2.0-or-later /* * (Tentative) USB Audio Driver for ALSA * * Mixer control part * * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de> * * Many codes borrowed from audio.c by * Alan Cox (alan@lxorguk.ukuu.org.uk) * Thomas Sailer (sailer@ife.ee.ethz.ch)
*/
/* * TODOs, for both the mixer and the streaming interfaces: * * - support for UAC2 effect units * - support for graphical equalizers * - RANGE and MEM set commands (UAC2) * - RANGE and MEM interrupt dispatchers (UAC2) * - audio channel clustering (UAC2) * - audio sample rate converter units (UAC2) * - proper handling of clock multipliers (UAC2) * - dispatch clock change notifications (UAC2) * - stop PCM streams which use a clock that became invalid * - stop PCM streams which use a clock selector that has changed * - parse available sample rates again when clock sources changed
*/
/* * manual mapping of mixer names * if the mixer topology is too complicated and the parsed names are * ambiguous, add the entries in usbmixer_maps.c.
*/ #include"mixer_maps.c"
staticconststruct usbmix_name_map *
find_map(conststruct usbmix_name_map *p, int unitid, int control)
{ if (!p) return NULL;
for (; p->id; p++) { if (p->id == unitid &&
(!control || !p->control || control == p->control)) return p;
} return NULL;
}
/* get the mapped name if the unit matches */ staticint
check_mapped_name(conststruct usbmix_name_map *p, char *buf, int buflen)
{ int len;
if (!p || !p->name) return 0;
buflen--;
len = strscpy(buf, p->name, buflen); return len < 0 ? buflen : len;
}
/* ignore the error value if ignore_ctl_error flag is set */ #define filter_error(cval, err) \
((cval)->head.mixer->ignore_ctl_error ? 0 : (err))
/* check whether the control should be ignored */ staticinlineint
check_ignored_ctl(conststruct usbmix_name_map *p)
{ if (!p || p->name || p->dB) return 0; return 1;
}
/* get the mapped selector source name */ staticint check_mapped_selector_name(struct mixer_build *state, int unitid, int index, char *buf, int buflen)
{ conststruct usbmix_selector_map *p; int len;
if (!state->selector_map) return 0; for (p = state->selector_map; p->id; p++) { if (p->id == unitid && index < p->count) {
len = strscpy(buf, p->names[index], buflen); return len < 0 ? buflen : len;
}
} return 0;
}
/* * find an audio control unit with the given unit id
*/ staticvoid *find_audio_control_unit(struct mixer_build *state, unsignedchar unit)
{ /* we just parse the header */ struct uac_feature_unit_descriptor *hdr = NULL;
/* * copy a string with the given id
*/ staticint snd_usb_copy_string_desc(struct snd_usb_audio *chip, int index, char *buf, int maxlen)
{ int len = usb_string(chip->dev, index, buf, maxlen - 1);
if (len < 0) return 0;
buf[len] = 0; return len;
}
/* * convert from the byte/word on usb descriptor to the zero-based integer
*/ staticint convert_signed_value(struct usb_mixer_elem_info *cval, int val)
{ switch (cval->val_type) { case USB_MIXER_BOOLEAN: return !!val; case USB_MIXER_INV_BOOLEAN: return !val; case USB_MIXER_U8:
val &= 0xff; break; case USB_MIXER_S8:
val &= 0xff; if (val >= 0x80)
val -= 0x100; break; case USB_MIXER_U16:
val &= 0xffff; break; case USB_MIXER_S16:
val &= 0xffff; if (val >= 0x8000)
val -= 0x10000; break;
} return val;
}
/* * convert from the zero-based int to the byte/word for usb descriptor
*/ staticint convert_bytes_value(struct usb_mixer_elem_info *cval, int val)
{ switch (cval->val_type) { case USB_MIXER_BOOLEAN: return !!val; case USB_MIXER_INV_BOOLEAN: return !val; case USB_MIXER_S8: case USB_MIXER_U8: return val & 0xff; case USB_MIXER_S16: case USB_MIXER_U16: return val & 0xffff;
} return 0; /* not reached */
}
staticint get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret)
{ struct snd_usb_audio *chip = cval->head.mixer->chip; /* enough space for one range */ unsignedchar buf[sizeof(__u16) + 3 * sizeof(__u32)]; unsignedchar *val; int idx = 0, ret, val_size, size;
__u8 bRequest;
/* * TLV callback for mixer volume controls
*/ int snd_usb_mixer_vol_tlv(struct snd_kcontrol *kcontrol, int op_flag, unsignedint size, unsignedint __user *_tlv)
{ struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol);
DECLARE_TLV_DB_MINMAX(scale, 0, 0);
if (size < sizeof(scale)) return -ENOMEM; if (cval->min_mute)
scale[0] = SNDRV_CTL_TLVT_DB_MINMAX_MUTE;
scale[2] = cval->dBmin;
scale[3] = cval->dBmax; if (copy_to_user(_tlv, scale, sizeof(scale))) return -EFAULT; return 0;
}
/* * parser routines begin here...
*/
staticint parse_audio_unit(struct mixer_build *state, int unitid);
/* * check if the input/output channel routing is enabled on the given bitmap. * used for mixer unit parser
*/ staticint check_matrix_bitmap(unsignedchar *bmap, int ich, int och, int num_outs)
{ int idx = ich * num_outs + och; return bmap[idx >> 3] & (0x80 >> (idx & 7));
}
/* * add an alsa control element * search and increment the index until an empty slot is found. * * if failed, give up and free the control instance.
*/
int snd_usb_mixer_add_list(struct usb_mixer_elem_list *list, struct snd_kcontrol *kctl, bool is_std_info)
{ struct usb_mixer_interface *mixer = list->mixer; int err;
/* * Get number of channels for a Mixer Unit.
*/ staticint uac_mixer_unit_get_channels(struct mixer_build *state, struct uac_mixer_unit_descriptor *desc)
{ int mu_channels;
switch (state->mixer->protocol) { case UAC_VERSION_1: case UAC_VERSION_2: default: if (desc->bLength < sizeof(*desc) + desc->bNrInPins + 1) return 0; /* no bmControls -> skip */
mu_channels = uac_mixer_unit_bNrChannels(desc); break; case UAC_VERSION_3:
mu_channels = get_cluster_channels_v3(state,
uac3_mixer_unit_wClusterDescrID(desc)); break;
}
return mu_channels;
}
/* * Parse Input Terminal Unit
*/ staticint __check_input_term(struct mixer_build *state, int id, struct usb_audio_term *term);
staticint parse_term_uac2_iterm_unit(struct mixer_build *state, struct usb_audio_term *term, void *p1, int id)
{ struct uac2_input_terminal_descriptor *d = p1; int err;
/* call recursively to verify the referenced clock entity */
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;
}
staticint parse_term_uac3_iterm_unit(struct mixer_build *state, struct usb_audio_term *term, void *p1, int id)
{ struct uac3_input_terminal_descriptor *d = p1; int err;
/* call recursively to verify the referenced clock entity */
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);
staticint parse_term_selector_unit(struct mixer_build *state, struct usb_audio_term *term, void *p1, int id)
{ struct uac_selector_unit_descriptor *d = p1; int err;
/* call recursively to retrieve the channel info */
err = __check_input_term(state, d->baSourceID[0], term); if (err < 0) return err;
term->type = UAC3_SELECTOR_UNIT << 16; /* virtual type */
term->id = id; if (state->mixer->protocol != UAC_VERSION_3)
term->name = uac_selector_unit_iSelector(d); return 0;
}
staticint parse_term_proc_unit(struct mixer_build *state, struct usb_audio_term *term, void *p1, int id, int vtype)
{ struct uac_processing_unit_descriptor *d = p1; int protocol = state->mixer->protocol; int err;
if (d->bNrInPins) { /* call recursively to retrieve the channel info */
err = __check_input_term(state, d->baSourceID[0], term); if (err < 0) return err;
}
/* * parse the source unit recursively until it reaches to a terminal * or a branched unit.
*/ staticint __check_input_term(struct mixer_build *state, int id, struct usb_audio_term *term)
{ int protocol = state->mixer->protocol; void *p1; unsignedchar *hdr;
for (;;) { /* a loop in the terminal chain? */ if (test_and_set_bit(id, state->termbitmap)) return -EINVAL;
p1 = find_audio_control_unit(state, id); if (!p1) break; if (!snd_usb_validate_audio_desc(p1, protocol)) break; /* bad descriptor */
hdr = p1;
term->id = id;
switch (PTYPE(protocol, hdr[2])) { case PTYPE(UAC_VERSION_1, UAC_FEATURE_UNIT): case PTYPE(UAC_VERSION_2, UAC_FEATURE_UNIT): case PTYPE(UAC_VERSION_3, UAC3_FEATURE_UNIT): { /* the header is the same for all versions */ struct uac_feature_unit_descriptor *d = p1;
id = d->bSourceID; break; /* continue to parse */
} case PTYPE(UAC_VERSION_1, UAC_INPUT_TERMINAL): return parse_term_uac1_iterm_unit(state, term, p1, id); case PTYPE(UAC_VERSION_2, UAC_INPUT_TERMINAL): return parse_term_uac2_iterm_unit(state, term, p1, id); case PTYPE(UAC_VERSION_3, UAC_INPUT_TERMINAL): return parse_term_uac3_iterm_unit(state, term, p1, id); case PTYPE(UAC_VERSION_1, UAC_MIXER_UNIT): case PTYPE(UAC_VERSION_2, UAC_MIXER_UNIT): case PTYPE(UAC_VERSION_3, UAC3_MIXER_UNIT): return parse_term_mixer_unit(state, term, p1, id); case PTYPE(UAC_VERSION_1, UAC_SELECTOR_UNIT): case PTYPE(UAC_VERSION_2, UAC_SELECTOR_UNIT): case PTYPE(UAC_VERSION_2, UAC2_CLOCK_SELECTOR): case PTYPE(UAC_VERSION_3, UAC3_SELECTOR_UNIT): case PTYPE(UAC_VERSION_3, UAC3_CLOCK_SELECTOR): return parse_term_selector_unit(state, term, p1, id); case PTYPE(UAC_VERSION_1, UAC1_PROCESSING_UNIT): case PTYPE(UAC_VERSION_2, UAC2_PROCESSING_UNIT_V2): case PTYPE(UAC_VERSION_3, UAC3_PROCESSING_UNIT): return parse_term_proc_unit(state, term, p1, id,
UAC3_PROCESSING_UNIT); case PTYPE(UAC_VERSION_2, UAC2_EFFECT_UNIT): case PTYPE(UAC_VERSION_3, UAC3_EFFECT_UNIT): return parse_term_effect_unit(state, term, p1, id); case PTYPE(UAC_VERSION_1, UAC1_EXTENSION_UNIT): case PTYPE(UAC_VERSION_2, UAC2_EXTENSION_UNIT_V2): case PTYPE(UAC_VERSION_3, UAC3_EXTENSION_UNIT): return parse_term_proc_unit(state, term, p1, id,
UAC3_EXTENSION_UNIT); case PTYPE(UAC_VERSION_2, UAC2_CLOCK_SOURCE): return parse_term_uac2_clock_source(state, term, p1, id); case PTYPE(UAC_VERSION_3, UAC3_CLOCK_SOURCE): return parse_term_uac3_clock_source(state, term, p1, id); default: return -ENODEV;
}
} return -ENODEV;
}
/* feature unit control information */ struct usb_feature_control_info { int control; constchar *name; int type; /* data type for uac1 */ int type_uac2; /* data type for uac2 if different from uac1, else -1 */
};
case USB_ID(0x0763, 0x2081): /* M-Audio Fast Track Ultra 8R */ case USB_ID(0x0763, 0x2080): /* M-Audio Fast Track Ultra */ if (strcmp(kctl->id.name, "Effect Duration") == 0) {
usb_audio_info(chip, "set quirk for FTU Effect Duration\n");
cval->min = 0x0000;
cval->max = 0x7f00;
cval->res = 0x0100; break;
} if (strcmp(kctl->id.name, "Effect Volume") == 0 ||
strcmp(kctl->id.name, "Effect Feedback Volume") == 0) {
usb_audio_info(chip, "set quirks for FTU Effect Feedback/Volume\n");
cval->min = 0x00;
cval->max = 0x7f; break;
} break;
case USB_ID(0x0d8c, 0x0103): if (!strcmp(kctl->id.name, "PCM Playback Volume")) {
usb_audio_info(chip, "set volume quirk for CM102-A+/102S+\n");
cval->min = -256;
} break;
case USB_ID(0x0471, 0x0101): case USB_ID(0x0471, 0x0104): case USB_ID(0x0471, 0x0105): case USB_ID(0x0672, 0x1041): /* quirk for UDA1321/N101. * note that detection between firmware 2.1.1.7 (N101) * and later 2.1.1.21 is not very clear from datasheets. * I hope that the min value is -15360 for newer firmware --jk
*/ if (!strcmp(kctl->id.name, "PCM Playback Volume") &&
cval->min == -15616) {
usb_audio_info(chip, "set volume quirk for UDA1321/N101 chip\n");
cval->max = -256;
} break;
case USB_ID(0x046d, 0x09a4): if (!strcmp(kctl->id.name, "Mic Capture Volume")) {
usb_audio_info(chip, "set volume quirk for QuickCam E3500\n");
cval->min = 6080;
cval->max = 8768;
cval->res = 192;
} break;
case USB_ID(0x0495, 0x3042): /* ESS Technology Asus USB DAC */ if ((strstr(kctl->id.name, "Playback Volume") != NULL) ||
strstr(kctl->id.name, "Capture Volume") != NULL) {
cval->min >>= 8;
cval->max = 0;
cval->res = 1;
} break; case USB_ID(0x3302, 0x12db): /* MOONDROP Quark2 */ if (!strcmp(kctl->id.name, "PCM Playback Volume")) {
usb_audio_info(chip, "set volume quirk for MOONDROP Quark2\n");
cval->min = -14208; /* Mute under it */
} break;
}
}
/* forcibly initialize the current mixer value; if GET_CUR fails, set to * the minimum as default
*/ staticvoid init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx)
{ int val, err;
err = snd_usb_get_cur_mix_value(cval, ch, idx, &val); if (!err) return; if (!cval->head.mixer->ignore_ctl_error)
usb_audio_warn(cval->head.mixer->chip, "%d:%d: failed to get current value for ch %d (%d)\n",
cval->head.id, mixer_ctrl_intf(cval->head.mixer),
ch, err);
snd_usb_set_cur_mix_value(cval, ch, idx, cval->min);
}
/* * retrieve the minimum and maximum values for the specified control
*/ staticint get_min_max_with_quirks(struct usb_mixer_elem_info *cval, int default_min, struct snd_kcontrol *kctl)
{ int i, idx;
/* Additional checks for the proper resolution * * Some devices report smaller resolutions than actually * reacting. They don't return errors but simply clip * to the lower aligned value.
*/ if (cval->min + cval->res < cval->max) { int last_valid_res = cval->res; int saved, test, check; if (get_cur_mix_raw(cval, minchn, &saved) < 0) goto no_res_check; for (;;) {
test = saved; if (test < cval->max)
test += cval->res; else
test -= cval->res; if (test < cval->min || test > cval->max ||
snd_usb_set_cur_mix_value(cval, minchn, 0, test) ||
get_cur_mix_raw(cval, minchn, &check)) {
cval->res = last_valid_res; break;
} if (test == check) break;
cval->res *= 2;
}
snd_usb_set_cur_mix_value(cval, minchn, 0, saved);
}
no_res_check:
cval->initialized = 1;
}
if (kctl)
volume_control_quirks(cval, kctl);
/* USB descriptions contain the dB scale in 1/256 dB unit * while ALSA TLV contains in 1/100 dB unit
*/
cval->dBmin = (convert_signed_value(cval, cval->min) * 100) / 256;
cval->dBmax = (convert_signed_value(cval, cval->max) * 100) / 256; if (cval->dBmin > cval->dBmax) { /* something is wrong; assume it's either from/to 0dB */ if (cval->dBmin < 0)
cval->dBmax = 0; elseif (cval->dBmin > 0)
cval->dBmin = 0; if (cval->dBmin > cval->dBmax) { /* totally crap, return an error */ return -EINVAL;
}
} else { /* if the max volume is too low, it's likely a bogus range; * here we use -96dB as the threshold
*/ if (cval->dBmax <= -9600) {
usb_audio_info(cval->head.mixer->chip, "%d:%d: bogus dB values (%d/%d), disabling dB reporting\n",
cval->head.id, mixer_ctrl_intf(cval->head.mixer),
cval->dBmin, cval->dBmax);
cval->dBmin = cval->dBmax = 0;
}
}
/* initialize all elements */ if (!cval->cmask) {
init_cur_mix_raw(cval, 0, 0);
} else {
idx = 0; for (i = 0; i < MAX_CHANNELS; i++) { if (cval->cmask & BIT(i)) {
init_cur_mix_raw(cval, i + 1, idx);
idx++;
}
}
}
/* get the max value advertised via control API */ staticint get_max_exposed(struct usb_mixer_elem_info *cval)
{ if (!cval->max_exposed) { if (cval->res)
cval->max_exposed =
DIV_ROUND_UP(cval->max - cval->min, cval->res); else
cval->max_exposed = cval->max - cval->min;
} return cval->max_exposed;
}
/* get a feature/mixer unit info */ staticint mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{ struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol);
/* get the current value from feature/mixer unit */ staticint mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{ struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int c, cnt, val, err;
ucontrol->value.integer.value[0] = cval->min; if (cval->cmask) {
cnt = 0; for (c = 0; c < MAX_CHANNELS; c++) { if (!(cval->cmask & BIT(c))) continue;
err = snd_usb_get_cur_mix_value(cval, c + 1, cnt, &val); if (err < 0) return filter_error(cval, err);
val = get_relative_value(cval, val);
ucontrol->value.integer.value[cnt] = val;
cnt++;
} return 0;
} else { /* master channel */
err = snd_usb_get_cur_mix_value(cval, 0, 0, &val); if (err < 0) return filter_error(cval, err);
val = get_relative_value(cval, val);
ucontrol->value.integer.value[0] = val;
} return 0;
}
/* put the current value to feature/mixer unit */ staticint mixer_ctl_feature_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{ struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int max_val = get_max_exposed(cval); int c, cnt, val, oval, err; int changed = 0;
if (cval->cmask) {
cnt = 0; for (c = 0; c < MAX_CHANNELS; c++) { if (!(cval->cmask & BIT(c))) continue;
err = snd_usb_get_cur_mix_value(cval, c + 1, cnt, &oval); if (err < 0) return filter_error(cval, err);
val = ucontrol->value.integer.value[cnt]; if (val < 0 || val > max_val) return -EINVAL;
val = get_abs_value(cval, val); if (oval != val) {
snd_usb_set_cur_mix_value(cval, c + 1, cnt, val);
changed = 1;
}
cnt++;
}
} else { /* master channel */
err = snd_usb_get_cur_mix_value(cval, 0, 0, &oval); if (err < 0) return filter_error(cval, err);
val = ucontrol->value.integer.value[0]; if (val < 0 || val > max_val) return -EINVAL;
val = get_abs_value(cval, val); if (val != oval) {
snd_usb_set_cur_mix_value(cval, 0, 0, val);
changed = 1;
}
} return changed;
}
/* get the boolean value from the master channel of a UAC control */ staticint mixer_ctl_master_bool_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{ struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int val, err;
if (ret < 0) { if (name && strstr(name, "Speaker")) { if (val)
*val = 1; return 0;
}
error:
usb_audio_err(chip, "cannot get connectors status: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n",
UAC_GET_CUR, validx, idx, cval->val_type);
if (val)
*val = 0;
return filter_error(cval, ret);
}
return ret;
}
/* get the connectors status and report it as boolean type */ staticint mixer_ctl_connector_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{ struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int ret, val;
ret = get_connector_value(cval, kcontrol->id.name, &val);
staticconststruct snd_kcontrol_new usb_feature_unit_ctl = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "", /* will be filled later manually */
.info = mixer_ctl_feature_info,
.get = mixer_ctl_feature_get,
.put = mixer_ctl_feature_put,
};
/* the read-only variant */ staticconststruct 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,
};
/* * A control which shows the boolean value from reading a UAC control on * the master channel.
*/ staticconststruct 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
*/ conststruct snd_kcontrol_new *snd_usb_feature_unit_ctl = &usb_feature_unit_ctl;
/* * build a feature control
*/ static size_t append_ctl_name(struct snd_kcontrol *kctl, constchar *str)
{ return strlcat(kctl->id.name, str, sizeof(kctl->id.name));
}
/* * A lot of headsets/headphones have a "Speaker" mixer. Make sure we * rename it to "Headphone". We determine if something is a headphone * similar to how udev determines form factor.
*/ staticvoid check_no_speaker_on_headset(struct snd_kcontrol *kctl, struct snd_card *card)
{ staticconstchar * const names_to_check[] = { "Headset", "headset", "Headphone", "headphone", NULL}; constchar * const *s; bool found = false;
if (strcmp("Speaker", kctl->id.name)) return;
for (s = names_to_check; *s; s++) if (strstr(card->shortname, *s)) {
found = true; break;
}
if (!found) return;
snd_ctl_rename(card, kctl, "Headphone");
}
staticconststruct 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;
}
staticvoid __build_feature_ctl(struct usb_mixer_interface *mixer, conststruct usbmix_name_map *imap, unsignedint ctl_mask, int control, struct usb_audio_term *iterm, struct usb_audio_term *oterm, int unitid, int nameid, int readonly_mask)
{ conststruct usb_feature_control_info *ctl_info; unsignedint len = 0; int mapped_name = 0; struct snd_kcontrol *kctl; struct usb_mixer_elem_info *cval; conststruct usbmix_name_map *map; unsignedint range;
if (control == UAC_FU_GRAPHIC_EQUALIZER) { /* FIXME: not supported yet */ return;
}
map = find_map(imap, unitid, control); if (check_ignored_ctl(map)) return;
if (ctl_mask == 0) {
cval->channels = 1; /* master channel */
cval->master_readonly = readonly_mask;
} else { int i, c = 0; for (i = 0; i < 16; i++) if (ctl_mask & BIT(i))
c++;
cval->channels = c;
cval->ch_readonly = readonly_mask;
}
/* * If all channels in the mask are marked read-only, make the control * read-only. snd_usb_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);
len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name));
mapped_name = len != 0; if (!len && nameid)
len = snd_usb_copy_string_desc(mixer->chip, nameid,
kctl->id.name, sizeof(kctl->id.name));
switch (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 * of terminal type. * - if the connected output can be determined, use it. * - otherwise, anonymous name.
*/ if (!len) { if (iterm)
len = get_term_name(mixer->chip, iterm,
kctl->id.name, sizeof(kctl->id.name), 1); if (!len && oterm)
len = get_term_name(mixer->chip, oterm,
kctl->id.name, sizeof(kctl->id.name), 1); if (!len)
snprintf(kctl->id.name, sizeof(kctl->id.name), "Feature %d", unitid);
}
if (!mapped_name)
check_no_speaker_on_headset(kctl, mixer->chip->card);
/* * determine the stream direction: * if the connected output is USB stream, then it's likely a * capture stream. otherwise it should be playback (hopefully :)
*/ if (!mapped_name && oterm && !(oterm->type >> 16)) { if ((oterm->type & 0xff00) == 0x0100)
append_ctl_name(kctl, " Capture"); else
append_ctl_name(kctl, " Playback");
}
append_ctl_name(kctl, control == UAC_FU_MUTE ? " Switch" : " Volume"); break; default: if (!len)
strscpy(kctl->id.name, audio_feature_info[control-1].name, sizeof(kctl->id.name)); break;
}
/* get min/max values */
get_min_max_with_quirks(cval, 0, kctl);
/* skip a bogus volume range */ if (cval->max <= cval->min) {
usb_audio_dbg(mixer->chip, "[%d] FU [%s] skipped due to invalid volume\n",
cval->head.id, kctl->id.name);
snd_ctl_free_one(kctl); return;
}
range = (cval->max - cval->min) / cval->res; /* * Are there devices with volume range more than 255? I use a bit more * to be sure. 384 is a resolution magic number found on Logitech * devices. It will definitively catch all buggy Logitech devices.
*/ if (range > 384) {
usb_audio_warn(mixer->chip, "Warning! Unlikely big volume range (=%u), cval->res is probably wrong.",
range);
usb_audio_warn(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d",
cval->head.id, kctl->id.name, cval->channels,
cval->min, cval->max, cval->res);
}
usb_audio_dbg(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n",
cval->head.id, kctl->id.name, cval->channels,
cval->min, cval->max, cval->res);
snd_usb_mixer_add_control(&cval->head, kctl);
}
staticvoid build_feature_ctl(struct mixer_build *state, void *raw_desc, unsignedint ctl_mask, int control, struct usb_audio_term *iterm, int unitid, int readonly_mask)
{ struct uac_feature_unit_descriptor *desc = raw_desc; int nameid = uac_feature_unit_iFeature(desc);
if (name_len == 0)
strscpy(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);
}
/* get connector value to "wake up" the USB audio */ staticint connector_mixer_resume(struct usb_mixer_elem_list *list)
{ struct usb_mixer_elem_info *cval = mixer_elem_list_to_info(list);
/* Build a mixer control for a UAC connector control (jack-detect) */ staticvoid build_connector_control(struct usb_mixer_interface *mixer, conststruct usbmix_name_map *imap, struct usb_audio_term *term, bool is_input)
{ struct snd_kcontrol *kctl; struct usb_mixer_elem_info *cval; conststruct usbmix_name_map *map;
map = find_map(imap, term->id, 0); if (check_ignored_ctl(map)) return;
cval = kzalloc(sizeof(*cval), GFP_KERNEL); if (!cval) return;
snd_usb_mixer_elem_init_std(&cval->head, mixer, term->id);
/* set up a specific resume callback */
cval->head.resume = connector_mixer_resume;
/* * UAC2: The first byte from reading the UAC2_TE_CONNECTOR control returns the * number of channels connected. * * UAC3: The first byte specifies size of bitmap for the inserted controls. The * following byte(s) specifies which connectors are inserted. * * This boolean ctl will simply report if any channels are connected * or not.
*/ if (mixer->protocol == UAC_VERSION_2)
cval->control = UAC2_TE_CONNECTOR; else/* UAC_VERSION_3 */
cval->control = UAC3_TE_INSERTION;
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_connector_ctl_ro, cval); if (!kctl) {
usb_audio_err(mixer->chip, "cannot malloc kcontrol\n");
usb_mixer_elem_info_free(cval); return;
}
if (state->mixer->protocol != UAC_VERSION_2) return -EINVAL;
/* * The only property of this unit we are interested in is the * clock source validity. If that isn't readable, just bail out.
*/ if (!uac_v2v3_control_is_readable(hdr->bmControls,
UAC2_CS_CONTROL_CLOCK_VALID)) return 0;
cval = kzalloc(sizeof(*cval), GFP_KERNEL); if (!cval) return -ENOMEM;
/* * parse a feature unit * * most of controls are defined here.
*/ staticint parse_audio_feature_unit(struct mixer_build *state, int unitid, void *_ftr)
{ int channels, i, j; struct usb_audio_term iterm; unsignedint master_bits; int err, csize; struct uac_feature_unit_descriptor *hdr = _ftr;
__u8 *bmaControls;
if (channels > 32) {
usb_audio_info(state->chip, "usbmixer: too many channels (%d) in unit %d\n",
channels, unitid); return -EINVAL;
}
/* parse the source unit */
err = parse_audio_unit(state, hdr->bSourceID); if (err < 0) return err;
/* determine the input source type and name */
err = check_input_term(state, hdr->bSourceID, &iterm); if (err < 0) return err;
master_bits = snd_usb_combine_bytes(bmaControls, csize); /* master configuration quirks */ switch (state->chip->usb_id) { case USB_ID(0x08bb, 0x2702):
usb_audio_info(state->chip, "usbmixer: master volume quirk for PCM2702 chip\n"); /* disable non-functional volume control */
master_bits &= ~UAC_CONTROL_BIT(UAC_FU_VOLUME); break; case USB_ID(0x1130, 0xf211):
usb_audio_info(state->chip, "usbmixer: volume control quirk for Tenx TP6911 Audio Headset\n"); /* disable non-functional volume control */
channels = 0; break;
}
if (state->mixer->protocol == UAC_VERSION_1) { /* check all control types */ for (i = 0; i < 10; i++) { unsignedint ch_bits = 0; int control = audio_feature_info[i].control;
mask = snd_usb_combine_bytes(bmaControls +
csize * (j+1), csize); if (mask & BIT(i))
ch_bits |= BIT(j);
} /* audio class v1 controls are never read-only */
/* * The first channel must be set * (for ease of programming).
*/ if (ch_bits & 1)
build_feature_ctl(state, _ftr, ch_bits, control,
&iterm, unitid, 0); if (master_bits & BIT(i))
build_feature_ctl(state, _ftr, 0, control,
&iterm, unitid, 0);
}
} else { /* UAC_VERSION_2/3 */ for (i = 0; i < ARRAY_SIZE(audio_feature_info); i++) { unsignedint ch_bits = 0; unsignedint ch_read_only = 0; int control = audio_feature_info[i].control;
/* * 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.
*/
/* * The first channel must be set * (for ease of programming).
*/ if (ch_bits & 1)
build_feature_ctl(state, _ftr, ch_bits, control,
&iterm, unitid, ch_read_only); if (uac_v2v3_control_is_readable(master_bits, control))
build_feature_ctl(state, _ftr, 0, control,
&iterm, unitid,
!uac_v2v3_control_is_writeable(master_bits,
control));
}
}
return 0;
}
/* * Mixer Unit
*/
/* check whether the given in/out overflows bmMixerControls matrix */ staticbool mixer_bitmap_overflow(struct uac_mixer_unit_descriptor *desc, int protocol, int num_ins, int num_outs)
{
u8 *hdr = (u8 *)desc;
u8 *c = uac_mixer_unit_bmControls(desc, protocol);
size_t rest; /* remaining bytes after bmMixerControls */
/* * build a mixer unit control * * the callbacks are identical with feature unit. * input channel number (zero based) is given in control field instead.
*/ staticvoid build_mixer_unit_ctl(struct mixer_build *state, struct uac_mixer_unit_descriptor *desc, int in_pin, int in_ch, int num_outs, int unitid, struct usb_audio_term *iterm)
{ struct usb_mixer_elem_info *cval; unsignedint i, len; struct snd_kcontrol *kctl; conststruct usbmix_name_map *map;
map = find_map(state->map, unitid, 0); if (check_ignored_ctl(map)) return;
cval = kzalloc(sizeof(*cval), GFP_KERNEL); if (!cval) return;
snd_usb_mixer_elem_init_std(&cval->head, state->mixer, unitid);
cval->control = in_ch + 1; /* based on 1 */
cval->val_type = USB_MIXER_S16; for (i = 0; i < num_outs; i++) {
__u8 *c = uac_mixer_unit_bmControls(desc, state->mixer->protocol);
if (check_matrix_bitmap(c, in_ch, i, num_outs)) {
cval->cmask |= BIT(i);
cval->channels++;
}
}
num_ins = desc->bNrInPins; for (i = 0; i < num_ins; i++) {
err = parse_audio_unit(state, desc->baSourceID[i]); if (err < 0) return err;
}
type = le16_to_cpu(desc->wProcessType); for (info = list; info && info->type; info++) if (info->type == type) break; if (!info || !info->type)
info = &default_info;
staticint parse_audio_processing_unit(struct mixer_build *state, int unitid, void *raw_desc)
{ switch (state->mixer->protocol) { case UAC_VERSION_1: case UAC_VERSION_2: default: return build_audio_procunit(state, unitid, raw_desc,
procunits, false); case UAC_VERSION_3: return build_audio_procunit(state, unitid, raw_desc,
uac3_procunits, false);
}
}
staticint parse_audio_extension_unit(struct mixer_build *state, int unitid, void *raw_desc)
{ /* * 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, true);
}
/* * Selector Unit
*/
/* * info callback for selector unit * use an enumerator type for routing
*/ staticint mixer_ctl_selector_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{ struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); constchar **itemlist = (constchar **)kcontrol->private_value;
if (snd_BUG_ON(!itemlist)) return -EINVAL; return snd_ctl_enum_info(uinfo, 1, cval->max, itemlist);
}
/* get callback for selector unit */ staticint mixer_ctl_selector_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{ struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int val, err;
/* check the static mapping table at first */
len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); if (!len) { /* no mapping ? */ switch (state->mixer->protocol) { case UAC_VERSION_1: case UAC_VERSION_2: default: /* if iSelector is given, use it */
nameid = uac_selector_unit_iSelector(desc); if (nameid)
len = snd_usb_copy_string_desc(state->chip,
nameid, kctl->id.name, sizeof(kctl->id.name)); break; case UAC_VERSION_3: /* TODO: Class-Specific strings not yet supported */ break;
}
/* ... or pick up the terminal name at next */ if (!len)
len = get_term_name(state->chip, &state->oterm,
kctl->id.name, sizeof(kctl->id.name), 0); /* ... or use the fixed string "USB" as the last resort */ if (!len)
strscpy(kctl->id.name, "USB", sizeof(kctl->id.name));
error_name: for (i = 0; i < desc->bNrInPins; i++)
kfree(namelist[i]);
kfree(namelist);
error_cval:
usb_mixer_elem_info_free(cval); return err;
}
/* * parse an audio unit recursively
*/
staticint parse_audio_unit(struct mixer_build *state, int unitid)
{ unsignedchar *p1; int protocol = state->mixer->protocol;
if (test_and_set_bit(unitid, state->unitbitmap)) return 0; /* the unit already visited */
p1 = find_audio_control_unit(state, unitid); if (!p1) {
usb_audio_err(state->chip, "unit %d not found!\n", unitid); return -EINVAL;
}
if (!snd_usb_validate_audio_desc(p1, protocol)) {
usb_audio_dbg(state->chip, "invalid unit %d\n", unitid); return 0; /* skip invalid unit */
}
switch (PTYPE(protocol, p1[2])) { case PTYPE(UAC_VERSION_1, UAC_INPUT_TERMINAL): case PTYPE(UAC_VERSION_2, UAC_INPUT_TERMINAL): case PTYPE(UAC_VERSION_3, UAC_INPUT_TERMINAL): return parse_audio_input_terminal(state, unitid, p1); case PTYPE(UAC_VERSION_1, UAC_MIXER_UNIT): case PTYPE(UAC_VERSION_2, UAC_MIXER_UNIT): case PTYPE(UAC_VERSION_3, UAC3_MIXER_UNIT): return parse_audio_mixer_unit(state, unitid, p1); case PTYPE(UAC_VERSION_2, UAC2_CLOCK_SOURCE): case PTYPE(UAC_VERSION_3, UAC3_CLOCK_SOURCE): return parse_clock_source_unit(state, unitid, p1); case PTYPE(UAC_VERSION_1, UAC_SELECTOR_UNIT): case PTYPE(UAC_VERSION_2, UAC_SELECTOR_UNIT): case PTYPE(UAC_VERSION_3, UAC3_SELECTOR_UNIT): case PTYPE(UAC_VERSION_2, UAC2_CLOCK_SELECTOR): case PTYPE(UAC_VERSION_3, UAC3_CLOCK_SELECTOR): return parse_audio_selector_unit(state, unitid, p1); case PTYPE(UAC_VERSION_1, UAC_FEATURE_UNIT): case PTYPE(UAC_VERSION_2, UAC_FEATURE_UNIT): case PTYPE(UAC_VERSION_3, UAC3_FEATURE_UNIT): return parse_audio_feature_unit(state, unitid, p1); case PTYPE(UAC_VERSION_1, UAC1_PROCESSING_UNIT): case PTYPE(UAC_VERSION_2, UAC2_PROCESSING_UNIT_V2): case PTYPE(UAC_VERSION_3, UAC3_PROCESSING_UNIT): return parse_audio_processing_unit(state, unitid, p1); case PTYPE(UAC_VERSION_1, UAC1_EXTENSION_UNIT): case PTYPE(UAC_VERSION_2, UAC2_EXTENSION_UNIT_V2): case PTYPE(UAC_VERSION_3, UAC3_EXTENSION_UNIT): return parse_audio_extension_unit(state, unitid, p1); case PTYPE(UAC_VERSION_2, UAC2_EFFECT_UNIT): case PTYPE(UAC_VERSION_3, UAC3_EFFECT_UNIT): return 0; /* FIXME - effect units not implemented yet */ default:
usb_audio_err(state->chip, "unit %u: unexpected type 0x%02x\n",
unitid, p1[2]); return -EINVAL;
}
}
/* UAC3 predefined channels configuration */ struct uac3_badd_profile { int subclass; constchar *name; int c_chmask; /* capture channels mask */ int p_chmask; /* playback channels mask */ int st_chmask; /* side tone mixing channel mask */
};
staticconststruct uac3_badd_profile uac3_badd_profiles[] = {
{ /* * BAIF, BAOF or combination of both * IN: Mono or Stereo cfg, Mono alt possible * OUT: Mono or Stereo cfg, Mono alt possible
*/
.subclass = UAC3_FUNCTION_SUBCLASS_GENERIC_IO,
.name = "GENERIC IO",
.c_chmask = -1, /* dynamic channels */
.p_chmask = -1, /* dynamic channels */
},
{ /* BAOF; Stereo only cfg, Mono alt possible */
.subclass = UAC3_FUNCTION_SUBCLASS_HEADPHONE,
.name = "HEADPHONE",
.p_chmask = 3,
},
{ /* BAOF; Mono or Stereo cfg, Mono alt possible */
.subclass = UAC3_FUNCTION_SUBCLASS_SPEAKER,
.name = "SPEAKER",
.p_chmask = -1, /* dynamic channels */
},
{ /* BAIF; Mono or Stereo cfg, Mono alt possible */
.subclass = UAC3_FUNCTION_SUBCLASS_MICROPHONE,
.name = "MICROPHONE",
.c_chmask = -1, /* dynamic channels */
},
{ /* * BAIOF topology * IN: Mono only * OUT: Mono or Stereo cfg, Mono alt possible
*/
.subclass = UAC3_FUNCTION_SUBCLASS_HEADSET,
.name = "HEADSET",
.c_chmask = 1,
.p_chmask = -1, /* dynamic channels */
.st_chmask = 1,
},
{ /* BAIOF; IN: Mono only; OUT: Stereo only, Mono alt possible */
.subclass = UAC3_FUNCTION_SUBCLASS_HEADSET_ADAPTER,
.name = "HEADSET ADAPTER",
.c_chmask = 1,
.p_chmask = 3,
.st_chmask = 1,
},
{ /* BAIF + BAOF; IN: Mono only; OUT: Mono only */
.subclass = UAC3_FUNCTION_SUBCLASS_SPEAKERPHONE,
.name = "SPEAKERPHONE",
.c_chmask = 1,
.p_chmask = 1,
},
{ 0 } /* terminator */
};
staticbool uac3_badd_func_has_valid_channels(struct usb_mixer_interface *mixer, conststruct uac3_badd_profile *f, int c_chmask, int p_chmask)
{ /* * If both playback/capture channels are dynamic, make sure * at least one channel is present
*/ if (f->c_chmask < 0 && f->p_chmask < 0) { if (!c_chmask && !p_chmask) {
usb_audio_warn(mixer->chip, "BAAD %s: no channels?",
f->name); returnfalse;
} returntrue;
}
/* * create mixer controls for UAC3 BADD profiles * * UAC3 BADD device doesn't contain CS descriptors thus we will guess everything * * BADD device may contain Mixer Unit, which doesn't have any controls, skip it
*/ staticint snd_usb_mixer_controls_badd(struct usb_mixer_interface *mixer, int ctrlif)
{ struct usb_device *dev = mixer->chip->dev; struct usb_interface_assoc_descriptor *assoc; int badd_profile = mixer->chip->badd_profile; conststruct uac3_badd_profile *f; conststruct usbmix_ctl_map *map; int p_chmask = 0, c_chmask = 0, st_chmask = 0; int i;
assoc = usb_ifnum_to_if(dev, ctrlif)->intf_assoc; if (!assoc) return -EINVAL;
/* Detect BADD capture/playback channels from AS EP descriptors */ for (i = 0; i < assoc->bInterfaceCount; i++) { int intf = assoc->bFirstInterface + i;
iface = usb_ifnum_to_if(dev, intf); if (!iface) continue;
num = iface->num_altsetting;
if (num < 2) return -EINVAL;
/* * The number of Channels in an AudioStreaming interface * and the audio sample bit resolution (16 bits or 24 * bits) can be derived from the wMaxPacketSize field in * the Standard AS Audio Data Endpoint descriptor in * Alternate Setting 1
*/
alts = &iface->altsetting[1];
altsd = get_iface_desc(alts);
switch (maxpacksize) { default:
usb_audio_err(mixer->chip, "incorrect wMaxPacketSize 0x%x for BADD profile\n",
maxpacksize); return -EINVAL; case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_16: case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_16: case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_24: case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_24:
chmask = 1; break; case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_16: case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_16: case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_24: case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_24:
chmask = 3; break;
}
if (dir_in)
c_chmask = chmask; else
p_chmask = chmask;
}
/* 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 = desc->iTerminal;
err = parse_audio_unit(&state, desc->bSourceID); if (err < 0 && err != -EINVAL) return err;
/* * For UAC2, use the same approach to also add the * clock selectors
*/
err = parse_audio_unit(&state, desc->bCSourceID); if (err < 0 && err != -EINVAL) return err;
/* 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;
if (!list->is_std_info) continue;
info = mixer_elem_list_to_info(list); /* invalidate cache, so the value is read from the device */
info->cached = 0;
snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
&list->kctl->id);
}
}
if (!list->kctl) continue; if (!list->is_std_info) continue;
info = mixer_elem_list_to_info(list); if (count > 1 && info->control != control) continue;
switch (attribute) { case UAC2_CS_CUR: /* invalidate cache, so the value is read from the device */ if (channel)
info->cached &= ~BIT(channel); else/* master channel */
info->cached = 0;
staticvoid snd_usb_mixer_interrupt(struct urb *urb)
{ struct usb_mixer_interface *mixer = urb->context; int len = urb->actual_length; int ustatus = urb->status;
if (ustatus != 0) goto requeue;
if (mixer->protocol == UAC_VERSION_1) { struct uac1_status_word *status;
for (status = urb->transfer_buffer;
len >= sizeof(*status);
len -= sizeof(*status), status++) {
dev_dbg(&urb->dev->dev, "status interrupt: %02x %02x\n",
status->bStatusType,
status->bOriginator);
/* ignore any notifications not from the control interface */ if ((status->bStatusType & UAC1_STATUS_TYPE_ORIG_MASK) !=
UAC1_STATUS_TYPE_ORIG_AUDIO_CONTROL_IF) continue;
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;
/* create the handler for the optional status interrupt endpoint */ staticint snd_usb_mixer_status_create(struct usb_mixer_interface *mixer)
{ struct usb_endpoint_descriptor *ep; void *transfer_buffer; int buffer_length; unsignedint epnum;
/* we need one interrupt input endpoint */ if (get_iface_desc(mixer->hostif)->bNumEndpoints < 1) return 0;
ep = get_endpoint(mixer->hostif, 0); if (!usb_endpoint_dir_in(ep) || !usb_endpoint_xfer_int(ep)) return 0;
void snd_usb_mixer_disconnect(struct usb_mixer_interface *mixer)
{ if (mixer->disconnected) return; if (mixer->urb)
usb_kill_urb(mixer->urb); if (mixer->rc_urb)
usb_kill_urb(mixer->rc_urb); if (mixer->private_free)
mixer->private_free(mixer);
mixer->disconnected = true;
}
/* stop any bus activity of a mixer */ staticvoid snd_usb_mixer_inactivate(struct usb_mixer_interface *mixer)
{
usb_kill_urb(mixer->urb);
usb_kill_urb(mixer->rc_urb);
}
staticint snd_usb_mixer_activate(struct usb_mixer_interface *mixer)
{ int err;
if (mixer->urb) {
err = usb_submit_urb(mixer->urb, GFP_NOIO); if (err < 0) return err;
}
return 0;
}
int snd_usb_mixer_suspend(struct usb_mixer_interface *mixer)
{
snd_usb_mixer_inactivate(mixer); if (mixer->private_suspend)
mixer->private_suspend(mixer); return 0;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.