diff options
Diffstat (limited to 'sound/usb/mixer.c')
-rw-r--r-- | sound/usb/mixer.c | 327 |
1 files changed, 296 insertions, 31 deletions
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 76417943ff85..4987982250d5 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -112,14 +112,12 @@ enum { #include "mixer_maps.c" static const struct usbmix_name_map * -find_map(struct mixer_build *state, int unitid, int control) +find_map(const struct usbmix_name_map *p, int unitid, int control) { - const struct usbmix_name_map *p = state->map; - if (!p) return NULL; - for (p = state->map; p->id; p++) { + for (; p->id; p++) { if (p->id == unitid && (!control || !p->control || control == p->control)) return p; @@ -1333,16 +1331,16 @@ static struct usb_feature_control_info *get_feature_control_info(int control) 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, - int readonly_mask) +static void __build_feature_ctl(struct usb_mixer_interface *mixer, + const struct usbmix_name_map *imap, + unsigned int ctl_mask, int control, + struct usb_audio_term *iterm, + struct usb_audio_term *oterm, + int unitid, int nameid, int readonly_mask) { - struct uac_feature_unit_descriptor *desc = raw_desc; struct usb_feature_control_info *ctl_info; unsigned int len = 0; int mapped_name = 0; - int nameid = uac_feature_unit_iFeature(desc); struct snd_kcontrol *kctl; struct usb_mixer_elem_info *cval; const struct usbmix_name_map *map; @@ -1353,14 +1351,14 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, return; } - map = find_map(state, unitid, control); + map = find_map(imap, unitid, control); 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); + snd_usb_mixer_elem_init_std(&cval->head, mixer, unitid); cval->control = control; cval->cmask = ctl_mask; @@ -1369,7 +1367,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, kfree(cval); return; } - if (state->mixer->protocol == UAC_VERSION_1) + if (mixer->protocol == UAC_VERSION_1) cval->val_type = ctl_info->type; else /* UAC_VERSION_2 */ cval->val_type = ctl_info->type_uac2 >= 0 ? @@ -1398,7 +1396,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval); if (!kctl) { - usb_audio_err(state->chip, "cannot malloc kcontrol\n"); + usb_audio_err(mixer->chip, "cannot malloc kcontrol\n"); kfree(cval); return; } @@ -1407,7 +1405,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, 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(state->chip, nameid, + len = snd_usb_copy_string_desc(mixer->chip, nameid, kctl->id.name, sizeof(kctl->id.name)); switch (control) { @@ -1422,10 +1420,12 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, * - otherwise, anonymous name. */ if (!len) { - len = get_term_name(state->chip, iterm, kctl->id.name, - sizeof(kctl->id.name), 1); - if (!len) - len = get_term_name(state->chip, &state->oterm, + 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) @@ -1434,15 +1434,15 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, } if (!mapped_name) - check_no_speaker_on_headset(kctl, state->mixer->chip->card); + 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 && !(state->oterm.type >> 16)) { - if ((state->oterm.type & 0xff00) == 0x0100) + if (!mapped_name && oterm && !(oterm->type >> 16)) { + if ((oterm->type & 0xff00) == 0x0100) append_ctl_name(kctl, " Capture"); else append_ctl_name(kctl, " Playback"); @@ -1470,7 +1470,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, } } - snd_usb_mixer_fu_apply_quirk(state->mixer, cval, unitid, kctl); + snd_usb_mixer_fu_apply_quirk(mixer, cval, unitid, kctl); range = (cval->max - cval->min) / cval->res; /* @@ -1479,21 +1479,41 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, * devices. It will definitively catch all buggy Logitech devices. */ if (range > 384) { - usb_audio_warn(state->chip, + usb_audio_warn(mixer->chip, "Warning! Unlikely big volume range (=%u), cval->res is probably wrong.", range); - usb_audio_warn(state->chip, + 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(state->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n", + 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); } +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, + int readonly_mask) +{ + struct uac_feature_unit_descriptor *desc = raw_desc; + int nameid = uac_feature_unit_iFeature(desc); + + __build_feature_ctl(state->mixer, state->map, ctl_mask, control, + iterm, &state->oterm, unitid, nameid, readonly_mask); +} + +static void build_feature_ctl_badd(struct usb_mixer_interface *mixer, + unsigned int ctl_mask, int control, int unitid, + const struct usbmix_name_map *badd_map) +{ + __build_feature_ctl(mixer, badd_map, ctl_mask, control, + NULL, NULL, unitid, 0, 0); +} + static void get_connector_control_name(struct mixer_build *state, struct usb_audio_term *term, bool is_input, char *name, int name_size) @@ -1807,7 +1827,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state, struct snd_kcontrol *kctl; const struct usbmix_name_map *map; - map = find_map(state, unitid, 0); + map = find_map(state->map, unitid, 0); if (check_ignored_ctl(map)) return; @@ -2106,7 +2126,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, if (!(controls[valinfo->control / 8] & (1 << ((valinfo->control % 8) - 1)))) continue; - map = find_map(state, unitid, valinfo->control); + map = find_map(state->map, unitid, valinfo->control); if (check_ignored_ctl(map)) continue; cval = kzalloc(sizeof(*cval), GFP_KERNEL); @@ -2310,7 +2330,7 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, if (desc->bNrInPins == 1) /* only one ? nonsense! */ return 0; - map = find_map(state, unitid, 0); + map = find_map(state->map, unitid, 0); if (check_ignored_ctl(map)) return 0; @@ -2497,6 +2517,246 @@ static int snd_usb_mixer_dev_free(struct snd_device *device) return 0; } +/* UAC3 predefined channels configuration */ +struct uac3_badd_profile { + int subclass; + const char *name; + int c_chmask; /* capture channels mask */ + int p_chmask; /* playback channels mask */ + int st_chmask; /* side tone mixing channel mask */ +}; + +static struct 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 */ +}; + +static bool uac3_badd_func_has_valid_channels(struct usb_mixer_interface *mixer, + struct 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); + return false; + } + return true; + } + + if ((f->c_chmask < 0 && !c_chmask) || + (f->c_chmask >= 0 && f->c_chmask != c_chmask)) { + usb_audio_warn(mixer->chip, "BAAD %s c_chmask mismatch", + f->name); + return false; + } + if ((f->p_chmask < 0 && !p_chmask) || + (f->p_chmask >= 0 && f->p_chmask != p_chmask)) { + usb_audio_warn(mixer->chip, "BAAD %s p_chmask mismatch", + f->name); + return false; + } + return true; +} + +/* + * 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 + */ +static int 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; + struct uac3_badd_profile *f; + const struct 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; + + /* Detect BADD capture/playback channels from AS EP descriptors */ + for (i = 0; i < assoc->bInterfaceCount; i++) { + int intf = assoc->bFirstInterface + i; + + struct usb_interface *iface; + struct usb_host_interface *alts; + struct usb_interface_descriptor *altsd; + unsigned int maxpacksize; + char dir_in; + int chmask, num; + + if (intf == ctrlif) + continue; + + iface = usb_ifnum_to_if(dev, intf); + 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); + + if (altsd->bNumEndpoints < 1) + return -EINVAL; + + /* check direction */ + dir_in = (get_endpoint(alts, 0)->bEndpointAddress & USB_DIR_IN); + maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); + + 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; + } + + usb_audio_dbg(mixer->chip, + "UAC3 BADD profile 0x%x: detected c_chmask=%d p_chmask=%d\n", + badd_profile, c_chmask, p_chmask); + + /* check the mapping table */ + for (map = uac3_badd_usbmix_ctl_maps; map->id; map++) { + if (map->id == badd_profile) + break; + } + + if (!map->id) + return -EINVAL; + + for (f = uac3_badd_profiles; f->name; f++) { + if (badd_profile == f->subclass) + break; + } + if (!f->name) + return -EINVAL; + if (!uac3_badd_func_has_valid_channels(mixer, f, c_chmask, p_chmask)) + return -EINVAL; + st_chmask = f->st_chmask; + + /* Playback */ + if (p_chmask) { + /* Master channel, always writable */ + build_feature_ctl_badd(mixer, 0, UAC_FU_MUTE, + UAC3_BADD_FU_ID2, map->map); + /* Mono/Stereo volume channels, always writable */ + build_feature_ctl_badd(mixer, p_chmask, UAC_FU_VOLUME, + UAC3_BADD_FU_ID2, map->map); + } + + /* Capture */ + if (c_chmask) { + /* Master channel, always writable */ + build_feature_ctl_badd(mixer, 0, UAC_FU_MUTE, + UAC3_BADD_FU_ID5, map->map); + /* Mono/Stereo volume channels, always writable */ + build_feature_ctl_badd(mixer, c_chmask, UAC_FU_VOLUME, + UAC3_BADD_FU_ID5, map->map); + } + + /* Side tone-mixing */ + if (st_chmask) { + /* Master channel, always writable */ + build_feature_ctl_badd(mixer, 0, UAC_FU_MUTE, + UAC3_BADD_FU_ID7, map->map); + /* Mono volume channel, always writable */ + build_feature_ctl_badd(mixer, 1, UAC_FU_VOLUME, + UAC3_BADD_FU_ID7, map->map); + } + + return 0; +} + /* * create mixer controls * @@ -2882,9 +3142,14 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, break; } - if ((err = snd_usb_mixer_controls(mixer)) < 0 || - (err = snd_usb_mixer_status_create(mixer)) < 0) + if (mixer->protocol == UAC_VERSION_3 && + chip->badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) { + if ((err = snd_usb_mixer_controls_badd(mixer, ctrlif)) < 0) + goto _error; + } else if ((err = snd_usb_mixer_controls(mixer)) < 0 || + (err = snd_usb_mixer_status_create(mixer)) < 0) { goto _error; + } err = create_keep_iface_ctl(mixer); if (err < 0) goto _error; |