#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;
        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;
                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;
 
                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 ?
                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;
        }
        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) {
                 * - 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)
                }
 
                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");
                }
        }
 
-       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;
        /*
         * 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)
        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;
 
 
                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);
        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;
 
        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
  *
                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;