diff mbox

[v2,7/7] ALSA: usb: add UAC3 BADD profiles support

Message ID 1525397044-15080-8-git-send-email-ruslan.bilovol@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Ruslan Bilovol May 4, 2018, 1:24 a.m. UTC
Recently released USB Audio Class 3.0 specification
contains BADD (Basic Audio Device Definition) document
which describes pre-defined UAC3 configurations.

BADD support is mandatory for UAC3 devices, it should be
implemented as a separate USB device configuration.
As per BADD document, class-specific descriptors
shall not be included in the Device’s Configuration
descriptor ("inferred"), but host can guess them
from BADD profile number, number of endpoints and
their max packed sizes.

This patch adds support of all BADD profiles from the spec

Signed-off-by: Ruslan Bilovol <ruslan.bilovol@gmail.com>
---
 sound/usb/card.c       |  14 +++
 sound/usb/clock.c      |   9 +-
 sound/usb/mixer.c      | 327 ++++++++++++++++++++++++++++++++++++++++++++-----
 sound/usb/mixer_maps.c |  65 ++++++++++
 sound/usb/stream.c     |  83 +++++++++++--
 sound/usb/usbaudio.h   |   2 +
 6 files changed, 459 insertions(+), 41 deletions(-)

Comments

Jorge Sanjuan May 11, 2018, 3:36 p.m. UTC | #1
On 04/05/18 02:24, Ruslan Bilovol wrote:
> Recently released USB Audio Class 3.0 specification
> contains BADD (Basic Audio Device Definition) document
> which describes pre-defined UAC3 configurations.
> 
> BADD support is mandatory for UAC3 devices, it should be
> implemented as a separate USB device configuration.
> As per BADD document, class-specific descriptors
> shall not be included in the Device’s Configuration
> descriptor ("inferred"), but host can guess them
> from BADD profile number, number of endpoints and
> their max packed sizes.
> 
> This patch adds support of all BADD profiles from the spec
> 
> Signed-off-by: Ruslan Bilovol <ruslan.bilovol@gmail.com>

Tested-by: Jorge Sanjuan <jorge.sanjuan@codethink.co.uk>

> ---
>   sound/usb/card.c       |  14 +++
>   sound/usb/clock.c      |   9 +-
>   sound/usb/mixer.c      | 327 ++++++++++++++++++++++++++++++++++++++++++++-----
>   sound/usb/mixer_maps.c |  65 ++++++++++
>   sound/usb/stream.c     |  83 +++++++++++--
>   sound/usb/usbaudio.h   |   2 +
>   6 files changed, 459 insertions(+), 41 deletions(-)
> 
> diff --git a/sound/usb/card.c b/sound/usb/card.c
> index 0d7a5d7..f6c3c1c 100644
> --- a/sound/usb/card.c
> +++ b/sound/usb/card.c
> @@ -307,6 +307,20 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif)
>   			return -EINVAL;
>   		}
>   
> +		if (protocol == UAC_VERSION_3) {
> +			int badd = assoc->bFunctionSubClass;
> +
> +			if (badd != UAC3_FUNCTION_SUBCLASS_FULL_ADC_3_0 &&
> +			    (badd < UAC3_FUNCTION_SUBCLASS_GENERIC_IO ||
> +			     badd > UAC3_FUNCTION_SUBCLASS_SPEAKERPHONE)) {
> +				dev_err(&dev->dev,
> +					"Unsupported UAC3 BADD profile\n");
> +				return -EINVAL;
> +			}
> +
> +			chip->badd_profile = badd;
> +		}
> +
>   		for (i = 0; i < assoc->bInterfaceCount; i++) {
>   			int intf = assoc->bFirstInterface + i;
>   
> diff --git a/sound/usb/clock.c b/sound/usb/clock.c
> index 0b030d8..17673f3 100644
> --- a/sound/usb/clock.c
> +++ b/sound/usb/clock.c
> @@ -587,8 +587,15 @@ int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface,
>   	default:
>   		return set_sample_rate_v1(chip, iface, alts, fmt, rate);
>   
> -	case UAC_VERSION_2:
>   	case UAC_VERSION_3:
> +		if (chip->badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) {
> +			if (rate != UAC3_BADD_SAMPLING_RATE)
> +				return -ENXIO;
> +			else
> +				return 0;
> +		}
> +	/* fall through */
> +	case UAC_VERSION_2:
>   		return set_sample_rate_v2v3(chip, iface, alts, fmt, rate);
>   	}
>   }
> diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
> index e280354..d98bc3f 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
>    *
> @@ -2883,9 +3143,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;
> diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c
> index eaa03ac..71069e1 100644
> --- a/sound/usb/mixer_maps.c
> +++ b/sound/usb/mixer_maps.c
> @@ -485,3 +485,68 @@ struct usbmix_ctl_map {
>   	{ 0 } /* terminator */
>   };
>   
> +/*
> + * Control map entries for UAC3 BADD profiles
> + */
> +
> +static struct usbmix_name_map uac3_badd_generic_io_map[] = {
> +	{ UAC3_BADD_FU_ID2, "Generic Out Playback" },
> +	{ UAC3_BADD_FU_ID5, "Generic In Capture" },
> +	{ 0 }					/* terminator */
> +};
> +static struct usbmix_name_map uac3_badd_headphone_map[] = {
> +	{ UAC3_BADD_FU_ID2, "Headphone Playback" },
> +	{ 0 }					/* terminator */
> +};
> +static struct usbmix_name_map uac3_badd_speaker_map[] = {
> +	{ UAC3_BADD_FU_ID2, "Speaker Playback" },
> +	{ 0 }					/* terminator */
> +};
> +static struct usbmix_name_map uac3_badd_microphone_map[] = {
> +	{ UAC3_BADD_FU_ID5, "Mic Capture" },
> +	{ 0 }					/* terminator */
> +};
> +/* Covers also 'headset adapter' profile */
> +static struct usbmix_name_map uac3_badd_headset_map[] = {
> +	{ UAC3_BADD_FU_ID2, "Headset Playback" },
> +	{ UAC3_BADD_FU_ID5, "Headset Capture" },
> +	{ UAC3_BADD_FU_ID7, "Sidetone Mixing" },
> +	{ 0 }					/* terminator */
> +};
> +static struct usbmix_name_map uac3_badd_speakerphone_map[] = {
> +	{ UAC3_BADD_FU_ID2, "Speaker Playback" },
> +	{ UAC3_BADD_FU_ID5, "Mic Capture" },
> +	{ 0 }					/* terminator */
> +};
> +
> +static struct usbmix_ctl_map uac3_badd_usbmix_ctl_maps[] = {
> +	{
> +		.id = UAC3_FUNCTION_SUBCLASS_GENERIC_IO,
> +		.map = uac3_badd_generic_io_map,
> +	},
> +	{
> +		.id = UAC3_FUNCTION_SUBCLASS_HEADPHONE,
> +		.map = uac3_badd_headphone_map,
> +	},
> +	{
> +		.id = UAC3_FUNCTION_SUBCLASS_SPEAKER,
> +		.map = uac3_badd_speaker_map,
> +	},
> +	{
> +		.id = UAC3_FUNCTION_SUBCLASS_MICROPHONE,
> +		.map = uac3_badd_microphone_map,
> +	},
> +	{
> +		.id = UAC3_FUNCTION_SUBCLASS_HEADSET,
> +		.map = uac3_badd_headset_map,
> +	},
> +	{
> +		.id = UAC3_FUNCTION_SUBCLASS_HEADSET_ADAPTER,
> +		.map = uac3_badd_headset_map,
> +	},
> +	{
> +		.id = UAC3_FUNCTION_SUBCLASS_SPEAKERPHONE,
> +		.map = uac3_badd_speakerphone_map,
> +	},
> +	{ 0 } /* terminator */
> +};
> diff --git a/sound/usb/stream.c b/sound/usb/stream.c
> index 764be07..de8bbb3 100644
> --- a/sound/usb/stream.c
> +++ b/sound/usb/stream.c
> @@ -817,15 +817,67 @@ static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip,
>   	struct uac3_input_terminal_descriptor *input_term;
>   	struct uac3_output_terminal_descriptor *output_term;
>   	struct uac3_cluster_header_descriptor *cluster;
> -	struct uac3_as_header_descriptor *as;
> +	struct uac3_as_header_descriptor *as = NULL;
>   	struct uac3_hc_descriptor_header hc_header;
>   	struct snd_pcm_chmap_elem *chmap;
> +	unsigned char badd_profile;
> +	u64 badd_formats = 0;
>   	unsigned int num_channels;
>   	struct audioformat *fp;
>   	u16 cluster_id, wLength;
>   	int clock = 0;
>   	int err;
>   
> +	badd_profile = chip->badd_profile;
> +
> +	if (badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) {
> +		unsigned int maxpacksize =
> +			le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
> +
> +		switch (maxpacksize) {
> +		default:
> +			dev_err(&dev->dev,
> +				"%u:%d : incorrect wMaxPacketSize for BADD profile\n",
> +				iface_no, altno);
> +			return NULL;
> +		case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_16:
> +		case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_16:
> +			badd_formats = SNDRV_PCM_FMTBIT_S16_LE;
> +			num_channels = 1;
> +			break;
> +		case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_24:
> +		case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_24:
> +			badd_formats = SNDRV_PCM_FMTBIT_S24_3LE;
> +			num_channels = 1;
> +			break;
> +		case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_16:
> +		case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_16:
> +			badd_formats = SNDRV_PCM_FMTBIT_S16_LE;
> +			num_channels = 2;
> +			break;
> +		case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_24:
> +		case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_24:
> +			badd_formats = SNDRV_PCM_FMTBIT_S24_3LE;
> +			num_channels = 2;
> +			break;
> +		}
> +
> +		chmap = kzalloc(sizeof(*chmap), GFP_KERNEL);
> +		if (!chmap)
> +			return ERR_PTR(-ENOMEM);
> +
> +		if (num_channels == 1) {
> +			chmap->map[0] = SNDRV_CHMAP_MONO;
> +		} else {
> +			chmap->map[0] = SNDRV_CHMAP_FL;
> +			chmap->map[1] = SNDRV_CHMAP_FR;
> +		}
> +
> +		chmap->channels = num_channels;
> +		clock = UAC3_BADD_CS_ID9;
> +		goto found_clock;
> +	}
> +
>   	as = snd_usb_find_csint_desc(alts->extra, alts->extralen,
>   				     NULL, UAC_AS_GENERAL);
>   	if (!as) {
> @@ -931,16 +983,29 @@ static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip,
>   	if (!fp)
>   		return ERR_PTR(-ENOMEM);
>   
> -	fp->attributes = parse_uac_endpoint_attributes(chip, alts,
> -						       UAC_VERSION_3,
> -						       iface_no);
>   	fp->chmap = chmap;
>   
> -	/* ok, let's parse further... */
> -	if (snd_usb_parse_audio_format_v3(chip, fp, as, stream) < 0) {
> -		kfree(fp->rate_table);
> -		kfree(fp);
> -		return NULL;
> +	if (badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) {
> +		fp->attributes = 0; /* No attributes */
> +
> +		fp->fmt_type = UAC_FORMAT_TYPE_I;
> +		fp->formats = badd_formats;
> +
> +		fp->nr_rates = 0;	/* SNDRV_PCM_RATE_CONTINUOUS */
> +		fp->rate_min = UAC3_BADD_SAMPLING_RATE;
> +		fp->rate_max = UAC3_BADD_SAMPLING_RATE;
> +		fp->rates = SNDRV_PCM_RATE_CONTINUOUS;
> +
> +	} else {
> +		fp->attributes = parse_uac_endpoint_attributes(chip, alts,
> +							       UAC_VERSION_3,
> +							       iface_no);
> +		/* ok, let's parse further... */
> +		if (snd_usb_parse_audio_format_v3(chip, fp, as, stream) < 0) {
> +			kfree(fp->rate_table);
> +			kfree(fp);
> +			return NULL;
> +		}
>   	}
>   
>   	return fp;
> diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h
> index 1cb6b3e..7b28cbd 100644
> --- a/sound/usb/usbaudio.h
> +++ b/sound/usb/usbaudio.h
> @@ -49,6 +49,8 @@ struct snd_usb_audio {
>   	int num_suspended_intf;
>   	int sample_rate_read_error;
>   
> +	int badd_profile;		/* UAC3 BADD profile */
> +
>   	struct list_head pcm_list;	/* list of pcm streams */
>   	struct list_head ep_list;	/* list of audio-related endpoints */
>   	int pcm_devs;
>
Takashi Iwai May 13, 2018, 7:06 a.m. UTC | #2
On Fri, 11 May 2018 17:36:36 +0200,
Jorge wrote:
> 
> 
> 
> On 04/05/18 02:24, Ruslan Bilovol wrote:
> > Recently released USB Audio Class 3.0 specification
> > contains BADD (Basic Audio Device Definition) document
> > which describes pre-defined UAC3 configurations.
> >
> > BADD support is mandatory for UAC3 devices, it should be
> > implemented as a separate USB device configuration.
> > As per BADD document, class-specific descriptors
> > shall not be included in the Device’s Configuration
> > descriptor ("inferred"), but host can guess them
> > from BADD profile number, number of endpoints and
> > their max packed sizes.
> >
> > This patch adds support of all BADD profiles from the spec
> >
> > Signed-off-by: Ruslan Bilovol <ruslan.bilovol@gmail.com>
> 
> Tested-by: Jorge Sanjuan <jorge.sanjuan@codethink.co.uk>

OK, I'll queue this one to for-next branch.
Thanks!


Takashi
diff mbox

Patch

diff --git a/sound/usb/card.c b/sound/usb/card.c
index 0d7a5d7..f6c3c1c 100644
--- a/sound/usb/card.c
+++ b/sound/usb/card.c
@@ -307,6 +307,20 @@  static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif)
 			return -EINVAL;
 		}
 
+		if (protocol == UAC_VERSION_3) {
+			int badd = assoc->bFunctionSubClass;
+
+			if (badd != UAC3_FUNCTION_SUBCLASS_FULL_ADC_3_0 &&
+			    (badd < UAC3_FUNCTION_SUBCLASS_GENERIC_IO ||
+			     badd > UAC3_FUNCTION_SUBCLASS_SPEAKERPHONE)) {
+				dev_err(&dev->dev,
+					"Unsupported UAC3 BADD profile\n");
+				return -EINVAL;
+			}
+
+			chip->badd_profile = badd;
+		}
+
 		for (i = 0; i < assoc->bInterfaceCount; i++) {
 			int intf = assoc->bFirstInterface + i;
 
diff --git a/sound/usb/clock.c b/sound/usb/clock.c
index 0b030d8..17673f3 100644
--- a/sound/usb/clock.c
+++ b/sound/usb/clock.c
@@ -587,8 +587,15 @@  int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface,
 	default:
 		return set_sample_rate_v1(chip, iface, alts, fmt, rate);
 
-	case UAC_VERSION_2:
 	case UAC_VERSION_3:
+		if (chip->badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) {
+			if (rate != UAC3_BADD_SAMPLING_RATE)
+				return -ENXIO;
+			else
+				return 0;
+		}
+	/* fall through */
+	case UAC_VERSION_2:
 		return set_sample_rate_v2v3(chip, iface, alts, fmt, rate);
 	}
 }
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
index e280354..d98bc3f 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
  *
@@ -2883,9 +3143,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;
diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c
index eaa03ac..71069e1 100644
--- a/sound/usb/mixer_maps.c
+++ b/sound/usb/mixer_maps.c
@@ -485,3 +485,68 @@  struct usbmix_ctl_map {
 	{ 0 } /* terminator */
 };
 
+/*
+ * Control map entries for UAC3 BADD profiles
+ */
+
+static struct usbmix_name_map uac3_badd_generic_io_map[] = {
+	{ UAC3_BADD_FU_ID2, "Generic Out Playback" },
+	{ UAC3_BADD_FU_ID5, "Generic In Capture" },
+	{ 0 }					/* terminator */
+};
+static struct usbmix_name_map uac3_badd_headphone_map[] = {
+	{ UAC3_BADD_FU_ID2, "Headphone Playback" },
+	{ 0 }					/* terminator */
+};
+static struct usbmix_name_map uac3_badd_speaker_map[] = {
+	{ UAC3_BADD_FU_ID2, "Speaker Playback" },
+	{ 0 }					/* terminator */
+};
+static struct usbmix_name_map uac3_badd_microphone_map[] = {
+	{ UAC3_BADD_FU_ID5, "Mic Capture" },
+	{ 0 }					/* terminator */
+};
+/* Covers also 'headset adapter' profile */
+static struct usbmix_name_map uac3_badd_headset_map[] = {
+	{ UAC3_BADD_FU_ID2, "Headset Playback" },
+	{ UAC3_BADD_FU_ID5, "Headset Capture" },
+	{ UAC3_BADD_FU_ID7, "Sidetone Mixing" },
+	{ 0 }					/* terminator */
+};
+static struct usbmix_name_map uac3_badd_speakerphone_map[] = {
+	{ UAC3_BADD_FU_ID2, "Speaker Playback" },
+	{ UAC3_BADD_FU_ID5, "Mic Capture" },
+	{ 0 }					/* terminator */
+};
+
+static struct usbmix_ctl_map uac3_badd_usbmix_ctl_maps[] = {
+	{
+		.id = UAC3_FUNCTION_SUBCLASS_GENERIC_IO,
+		.map = uac3_badd_generic_io_map,
+	},
+	{
+		.id = UAC3_FUNCTION_SUBCLASS_HEADPHONE,
+		.map = uac3_badd_headphone_map,
+	},
+	{
+		.id = UAC3_FUNCTION_SUBCLASS_SPEAKER,
+		.map = uac3_badd_speaker_map,
+	},
+	{
+		.id = UAC3_FUNCTION_SUBCLASS_MICROPHONE,
+		.map = uac3_badd_microphone_map,
+	},
+	{
+		.id = UAC3_FUNCTION_SUBCLASS_HEADSET,
+		.map = uac3_badd_headset_map,
+	},
+	{
+		.id = UAC3_FUNCTION_SUBCLASS_HEADSET_ADAPTER,
+		.map = uac3_badd_headset_map,
+	},
+	{
+		.id = UAC3_FUNCTION_SUBCLASS_SPEAKERPHONE,
+		.map = uac3_badd_speakerphone_map,
+	},
+	{ 0 } /* terminator */
+};
diff --git a/sound/usb/stream.c b/sound/usb/stream.c
index 764be07..de8bbb3 100644
--- a/sound/usb/stream.c
+++ b/sound/usb/stream.c
@@ -817,15 +817,67 @@  static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip,
 	struct uac3_input_terminal_descriptor *input_term;
 	struct uac3_output_terminal_descriptor *output_term;
 	struct uac3_cluster_header_descriptor *cluster;
-	struct uac3_as_header_descriptor *as;
+	struct uac3_as_header_descriptor *as = NULL;
 	struct uac3_hc_descriptor_header hc_header;
 	struct snd_pcm_chmap_elem *chmap;
+	unsigned char badd_profile;
+	u64 badd_formats = 0;
 	unsigned int num_channels;
 	struct audioformat *fp;
 	u16 cluster_id, wLength;
 	int clock = 0;
 	int err;
 
+	badd_profile = chip->badd_profile;
+
+	if (badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) {
+		unsigned int maxpacksize =
+			le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
+
+		switch (maxpacksize) {
+		default:
+			dev_err(&dev->dev,
+				"%u:%d : incorrect wMaxPacketSize for BADD profile\n",
+				iface_no, altno);
+			return NULL;
+		case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_16:
+		case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_16:
+			badd_formats = SNDRV_PCM_FMTBIT_S16_LE;
+			num_channels = 1;
+			break;
+		case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_24:
+		case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_24:
+			badd_formats = SNDRV_PCM_FMTBIT_S24_3LE;
+			num_channels = 1;
+			break;
+		case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_16:
+		case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_16:
+			badd_formats = SNDRV_PCM_FMTBIT_S16_LE;
+			num_channels = 2;
+			break;
+		case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_24:
+		case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_24:
+			badd_formats = SNDRV_PCM_FMTBIT_S24_3LE;
+			num_channels = 2;
+			break;
+		}
+
+		chmap = kzalloc(sizeof(*chmap), GFP_KERNEL);
+		if (!chmap)
+			return ERR_PTR(-ENOMEM);
+
+		if (num_channels == 1) {
+			chmap->map[0] = SNDRV_CHMAP_MONO;
+		} else {
+			chmap->map[0] = SNDRV_CHMAP_FL;
+			chmap->map[1] = SNDRV_CHMAP_FR;
+		}
+
+		chmap->channels = num_channels;
+		clock = UAC3_BADD_CS_ID9;
+		goto found_clock;
+	}
+
 	as = snd_usb_find_csint_desc(alts->extra, alts->extralen,
 				     NULL, UAC_AS_GENERAL);
 	if (!as) {
@@ -931,16 +983,29 @@  static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip,
 	if (!fp)
 		return ERR_PTR(-ENOMEM);
 
-	fp->attributes = parse_uac_endpoint_attributes(chip, alts,
-						       UAC_VERSION_3,
-						       iface_no);
 	fp->chmap = chmap;
 
-	/* ok, let's parse further... */
-	if (snd_usb_parse_audio_format_v3(chip, fp, as, stream) < 0) {
-		kfree(fp->rate_table);
-		kfree(fp);
-		return NULL;
+	if (badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) {
+		fp->attributes = 0; /* No attributes */
+
+		fp->fmt_type = UAC_FORMAT_TYPE_I;
+		fp->formats = badd_formats;
+
+		fp->nr_rates = 0;	/* SNDRV_PCM_RATE_CONTINUOUS */
+		fp->rate_min = UAC3_BADD_SAMPLING_RATE;
+		fp->rate_max = UAC3_BADD_SAMPLING_RATE;
+		fp->rates = SNDRV_PCM_RATE_CONTINUOUS;
+
+	} else {
+		fp->attributes = parse_uac_endpoint_attributes(chip, alts,
+							       UAC_VERSION_3,
+							       iface_no);
+		/* ok, let's parse further... */
+		if (snd_usb_parse_audio_format_v3(chip, fp, as, stream) < 0) {
+			kfree(fp->rate_table);
+			kfree(fp);
+			return NULL;
+		}
 	}
 
 	return fp;
diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h
index 1cb6b3e..7b28cbd 100644
--- a/sound/usb/usbaudio.h
+++ b/sound/usb/usbaudio.h
@@ -49,6 +49,8 @@  struct snd_usb_audio {
 	int num_suspended_intf;
 	int sample_rate_read_error;
 
+	int badd_profile;		/* UAC3 BADD profile */
+
 	struct list_head pcm_list;	/* list of pcm streams */
 	struct list_head ep_list;	/* list of audio-related endpoints */
 	int pcm_devs;