* Copyright (C) 2011
  *    Yadwinder Singh (yadi.brar01@gmail.com)
  *    Jaswinder Singh (jaswinder.singh@linaro.org)
+ *
+ * Copyright (C) 2020
+ *    Ruslan Bilovol (ruslan.bilovol@gmail.com)
  */
 
 #include <linux/usb/audio.h>
 
 /*
  * The driver implements a simple UAC_2 topology.
- * USB-OUT -> IT_1 -> OT_3 -> ALSA_Capture
- * ALSA_Playback -> IT_2 -> OT_4 -> USB-IN
+ * USB-OUT -> IT_1 -> FU -> OT_3 -> ALSA_Capture
+ * ALSA_Playback -> IT_2 -> FU -> OT_4 -> USB-IN
  * Capture and Playback sampling rates are independently
  *  controlled by two clock sources :
  *    CLK_5 := c_srate, and CLK_6 := p_srate
  */
 #define USB_OUT_CLK_ID (out_clk_src_desc.bClockID)
 #define USB_IN_CLK_ID  (in_clk_src_desc.bClockID)
+#define USB_OUT_FU_ID  (out_feature_unit_desc->bUnitID)
+#define USB_IN_FU_ID   (in_feature_unit_desc->bUnitID)
 
 #define CONTROL_ABSENT 0
 #define CONTROL_RDONLY 1
 
 #define CLK_FREQ_CTRL  0
 #define CLK_VLD_CTRL   2
+#define FU_MUTE_CTRL   0
+#define FU_VOL_CTRL    2
 
 #define COPY_CTRL      0
 #define CONN_CTRL      2
 
 #define EPIN_EN(_opts) ((_opts)->p_chmask != 0)
 #define EPOUT_EN(_opts) ((_opts)->c_chmask != 0)
+#define FUIN_EN(_opts) (EPIN_EN(_opts) \
+                               && ((_opts)->p_mute_present \
+                               || (_opts)->p_volume_present))
+#define FUOUT_EN(_opts) (EPOUT_EN(_opts) \
+                               && ((_opts)->c_mute_present \
+                               || (_opts)->c_volume_present))
 #define EPOUT_FBACK_IN_EN(_opts) ((_opts)->c_sync == USB_ENDPOINT_SYNC_ASYNC)
 
 struct f_uac2 {
        struct g_audio g_audio;
        u8 ac_intf, as_in_intf, as_out_intf;
        u8 ac_alt, as_in_alt, as_out_alt;       /* needed for get_alt() */
+
+       struct usb_ctrlrequest setup_cr;        /* will be used in data stage */
+
+       /* Interrupt IN endpoint of AC interface */
+       struct usb_ep   *int_ep;
+       atomic_t        int_count;
 };
 
 static inline struct f_uac2 *func_to_uac2(struct usb_function *f)
        return container_of(agdev->func.fi, struct f_uac2_opts, func_inst);
 }
 
+static int afunc_notify(struct g_audio *agdev, int unit_id, int cs);
+
 /* --------- USB Function Interface ------------- */
 
 enum {
        STR_IO_IT,
        STR_USB_OT,
        STR_IO_OT,
+       STR_FU_IN,
+       STR_FU_OUT,
        STR_AS_OUT_ALT0,
        STR_AS_OUT_ALT1,
        STR_AS_IN_ALT0,
        [STR_IO_IT].s = "USBD Out",
        [STR_USB_OT].s = "USBH In",
        [STR_IO_OT].s = "USBD In",
+       [STR_FU_IN].s = "Capture Volume",
+       [STR_FU_OUT].s = "Playback Volume",
        [STR_AS_OUT_ALT0].s = "Playback Inactive",
        [STR_AS_OUT_ALT1].s = "Playback Active",
        [STR_AS_IN_ALT0].s = "Capture Inactive",
        .bDescriptorType = USB_DT_INTERFACE,
 
        .bAlternateSetting = 0,
-       .bNumEndpoints = 0,
+       /* .bNumEndpoints = DYNAMIC */
        .bInterfaceClass = USB_CLASS_AUDIO,
        .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
        .bInterfaceProtocol = UAC_VERSION_2,
        .bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL),
 };
 
+static struct uac2_feature_unit_descriptor *in_feature_unit_desc;
+static struct uac2_feature_unit_descriptor *out_feature_unit_desc;
+
 static struct uac2_ac_header_descriptor ac_hdr_desc = {
        .bLength = sizeof ac_hdr_desc,
        .bDescriptorType = USB_DT_CS_INTERFACE,
        .bmControls = 0,
 };
 
+/* AC IN Interrupt Endpoint */
+static struct usb_endpoint_descriptor fs_ep_int_desc = {
+       .bLength = USB_DT_ENDPOINT_SIZE,
+       .bDescriptorType = USB_DT_ENDPOINT,
+
+       .bEndpointAddress = USB_DIR_IN,
+       .bmAttributes = USB_ENDPOINT_XFER_INT,
+       .wMaxPacketSize = cpu_to_le16(6),
+       .bInterval = 1,
+};
+
+static struct usb_endpoint_descriptor hs_ep_int_desc = {
+       .bLength = USB_DT_ENDPOINT_SIZE,
+       .bDescriptorType = USB_DT_ENDPOINT,
+
+       .bmAttributes = USB_ENDPOINT_XFER_INT,
+       .wMaxPacketSize = cpu_to_le16(6),
+       .bInterval = 4,
+};
+
+static struct usb_endpoint_descriptor ss_ep_int_desc = {
+       .bLength = USB_DT_ENDPOINT_SIZE,
+       .bDescriptorType = USB_DT_ENDPOINT,
+
+       .bEndpointAddress = USB_DIR_IN,
+       .bmAttributes = USB_ENDPOINT_XFER_INT,
+       .wMaxPacketSize = cpu_to_le16(6),
+       .bInterval = 4,
+};
+
 /* Audio Streaming OUT Interface - Alt0 */
 static struct usb_interface_descriptor std_as_out_if0_desc = {
        .bLength = sizeof std_as_out_if0_desc,
        (struct usb_descriptor_header *)&in_clk_src_desc,
        (struct usb_descriptor_header *)&out_clk_src_desc,
        (struct usb_descriptor_header *)&usb_out_it_desc,
+       (struct usb_descriptor_header *)&out_feature_unit_desc,
        (struct usb_descriptor_header *)&io_in_it_desc,
        (struct usb_descriptor_header *)&usb_in_ot_desc,
+       (struct usb_descriptor_header *)&in_feature_unit_desc,
        (struct usb_descriptor_header *)&io_out_ot_desc,
 
+       (struct usb_descriptor_header *)&fs_ep_int_desc,
+
        (struct usb_descriptor_header *)&std_as_out_if0_desc,
        (struct usb_descriptor_header *)&std_as_out_if1_desc,
 
        (struct usb_descriptor_header *)&in_clk_src_desc,
        (struct usb_descriptor_header *)&out_clk_src_desc,
        (struct usb_descriptor_header *)&usb_out_it_desc,
+       (struct usb_descriptor_header *)&out_feature_unit_desc,
        (struct usb_descriptor_header *)&io_in_it_desc,
        (struct usb_descriptor_header *)&usb_in_ot_desc,
+       (struct usb_descriptor_header *)&in_feature_unit_desc,
        (struct usb_descriptor_header *)&io_out_ot_desc,
 
+       (struct usb_descriptor_header *)&hs_ep_int_desc,
+
        (struct usb_descriptor_header *)&std_as_out_if0_desc,
        (struct usb_descriptor_header *)&std_as_out_if1_desc,
 
        (struct usb_descriptor_header *)&in_clk_src_desc,
        (struct usb_descriptor_header *)&out_clk_src_desc,
        (struct usb_descriptor_header *)&usb_out_it_desc,
+  (struct usb_descriptor_header *)&out_feature_unit_desc,
        (struct usb_descriptor_header *)&io_in_it_desc,
        (struct usb_descriptor_header *)&usb_in_ot_desc,
+       (struct usb_descriptor_header *)&in_feature_unit_desc,
        (struct usb_descriptor_header *)&io_out_ot_desc,
 
+  (struct usb_descriptor_header *)&ss_ep_int_desc,
+
        (struct usb_descriptor_header *)&std_as_out_if0_desc,
        (struct usb_descriptor_header *)&std_as_out_if1_desc,
 
        NULL,
 };
 
+struct cntrl_cur_lay2 {
+       __le16  wCUR;
+};
+
+struct cntrl_range_lay2 {
+       __le16  wNumSubRanges;
+       __le16  wMIN;
+       __le16  wMAX;
+       __le16  wRES;
+} __packed;
+
 struct cntrl_cur_lay3 {
        __le32  dCUR;
 };
        return 0;
 }
 
+static struct uac2_feature_unit_descriptor *build_fu_desc(int chmask)
+{
+       struct uac2_feature_unit_descriptor *fu_desc;
+       int channels = num_channels(chmask);
+       int fu_desc_size = UAC2_DT_FEATURE_UNIT_SIZE(channels);
+
+       fu_desc = kzalloc(fu_desc_size, GFP_KERNEL);
+       if (!fu_desc)
+               return NULL;
+
+       fu_desc->bLength = fu_desc_size;
+       fu_desc->bDescriptorType = USB_DT_CS_INTERFACE;
+
+       fu_desc->bDescriptorSubtype = UAC_FEATURE_UNIT;
+
+       /* bUnitID, bSourceID and bmaControls will be defined later */
+
+       return fu_desc;
+}
+
 /* Use macro to overcome line length limitation */
 #define USBDHDR(p) (struct usb_descriptor_header *)(p)
 
        struct usb_endpoint_descriptor *epout_desc;
        struct usb_endpoint_descriptor *epin_desc;
        struct usb_endpoint_descriptor *epin_fback_desc;
+       struct usb_endpoint_descriptor *ep_int_desc;
        int i;
 
        switch (speed) {
                epout_desc = &fs_epout_desc;
                epin_desc = &fs_epin_desc;
                epin_fback_desc = &fs_epin_fback_desc;
+               ep_int_desc = &fs_ep_int_desc;
                break;
        case USB_SPEED_HIGH:
                epout_desc = &hs_epout_desc;
                epin_desc = &hs_epin_desc;
                epin_fback_desc = &hs_epin_fback_desc;
+               ep_int_desc = &hs_ep_int_desc;
                break;
        default:
                epout_desc = &ss_epout_desc;
                epout_desc_comp = &ss_epout_desc_comp;
                epin_desc_comp = &ss_epin_desc_comp;
                epin_fback_desc = &ss_epin_fback_desc;
+               ep_int_desc = &ss_ep_int_desc;
        }
 
        i = 0;
        if (EPOUT_EN(opts)) {
                headers[i++] = USBDHDR(&out_clk_src_desc);
                headers[i++] = USBDHDR(&usb_out_it_desc);
-       }
+
+    if (FUOUT_EN(opts))
+      headers[i++] = USBDHDR(out_feature_unit_desc);
+  }
+
        if (EPIN_EN(opts)) {
                headers[i++] = USBDHDR(&io_in_it_desc);
+
+    if (FUIN_EN(opts))
+      headers[i++] = USBDHDR(in_feature_unit_desc);
+
                headers[i++] = USBDHDR(&usb_in_ot_desc);
        }
-       if (EPOUT_EN(opts)) {
+
+       if (EPOUT_EN(opts))
                headers[i++] = USBDHDR(&io_out_ot_desc);
+
+  if (FUOUT_EN(opts) || FUIN_EN(opts))
+      headers[i++] = USBDHDR(ep_int_desc);
+
+  if (EPOUT_EN(opts)) {
                headers[i++] = USBDHDR(&std_as_out_if0_desc);
                headers[i++] = USBDHDR(&std_as_out_if1_desc);
                headers[i++] = USBDHDR(&as_out_hdr_desc);
                if (EPOUT_FBACK_IN_EN(opts))
                        headers[i++] = USBDHDR(epin_fback_desc);
        }
+
        if (EPIN_EN(opts)) {
                headers[i++] = USBDHDR(&std_as_in_if0_desc);
                headers[i++] = USBDHDR(&std_as_in_if1_desc);
                io_out_ot_desc.bTerminalID = i++;
        if (EPIN_EN(opts))
                usb_in_ot_desc.bTerminalID = i++;
+       if (FUOUT_EN(opts))
+               out_feature_unit_desc->bUnitID = i++;
+       if (FUIN_EN(opts))
+               in_feature_unit_desc->bUnitID = i++;
        if (EPOUT_EN(opts))
                out_clk_src_desc.bClockID = i++;
        if (EPIN_EN(opts))
                in_clk_src_desc.bClockID = i++;
 
        usb_out_it_desc.bCSourceID = out_clk_src_desc.bClockID;
-       usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID;
+
+       if (FUIN_EN(opts)) {
+               usb_in_ot_desc.bSourceID = in_feature_unit_desc->bUnitID;
+               in_feature_unit_desc->bSourceID = io_in_it_desc.bTerminalID;
+       } else {
+               usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID;
+       }
+
        usb_in_ot_desc.bCSourceID = in_clk_src_desc.bClockID;
        io_in_it_desc.bCSourceID = in_clk_src_desc.bClockID;
        io_out_ot_desc.bCSourceID = out_clk_src_desc.bClockID;
-       io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID;
+
+       if (FUOUT_EN(opts)) {
+               io_out_ot_desc.bSourceID = out_feature_unit_desc->bUnitID;
+               out_feature_unit_desc->bSourceID = usb_out_it_desc.bTerminalID;
+       } else {
+               io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID;
+       }
+
        as_out_hdr_desc.bTerminalLink = usb_out_it_desc.bTerminalID;
        as_in_hdr_desc.bTerminalLink = usb_in_ot_desc.bTerminalID;
 
 
                len += sizeof(in_clk_src_desc);
                len += sizeof(usb_in_ot_desc);
+
+               if (FUIN_EN(opts))
+                       len += in_feature_unit_desc->bLength;
+
                len += sizeof(io_in_it_desc);
                ac_hdr_desc.wTotalLength = cpu_to_le16(len);
                iad_desc.bInterfaceCount++;
 
                len += sizeof(out_clk_src_desc);
                len += sizeof(usb_out_it_desc);
+
+               if (FUOUT_EN(opts))
+                       len += out_feature_unit_desc->bLength;
+
                len += sizeof(io_out_ot_desc);
                ac_hdr_desc.wTotalLength = cpu_to_le16(len);
                iad_desc.bInterfaceCount++;
                return -EINVAL;
        }
 
+       if (opts->p_volume_max <= opts->p_volume_min) {
+               dev_err(dev, "Error: incorrect playback volume max/min\n");
+                       return -EINVAL;
+       } else if (opts->c_volume_max <= opts->c_volume_min) {
+               dev_err(dev, "Error: incorrect capture volume max/min\n");
+                       return -EINVAL;
+       } else if (opts->p_volume_res <= 0) {
+               dev_err(dev, "Error: negative/zero playback volume resolution\n");
+                       return -EINVAL;
+       } else if (opts->c_volume_res <= 0) {
+               dev_err(dev, "Error: negative/zero capture volume resolution\n");
+                       return -EINVAL;
+       }
+
+       if ((opts->p_volume_max - opts->p_volume_min) % opts->p_volume_res) {
+               dev_err(dev, "Error: incorrect playback volume resolution\n");
+                       return -EINVAL;
+       } else if ((opts->c_volume_max - opts->c_volume_min) % opts->c_volume_res) {
+               dev_err(dev, "Error: incorrect capture volume resolution\n");
+                       return -EINVAL;
+       }
+
        return 0;
 }
 
        us = usb_gstrings_attach(cdev, fn_strings, ARRAY_SIZE(strings_fn));
        if (IS_ERR(us))
                return PTR_ERR(us);
+
+       if (FUOUT_EN(uac2_opts)) {
+               out_feature_unit_desc = build_fu_desc(uac2_opts->c_chmask);
+               if (!out_feature_unit_desc)
+                       return -ENOMEM;
+       }
+       if (FUIN_EN(uac2_opts)) {
+               in_feature_unit_desc = build_fu_desc(uac2_opts->p_chmask);
+               if (!in_feature_unit_desc) {
+                       ret = -ENOMEM;
+                       goto err_free_fu;
+               }
+       }
+
        iad_desc.iFunction = us[STR_ASSOC].id;
        std_ac_if_desc.iInterface = us[STR_IF_CTRL].id;
        in_clk_src_desc.iClockSource = us[STR_CLKSRC_IN].id;
        std_as_in_if0_desc.iInterface = us[STR_AS_IN_ALT0].id;
        std_as_in_if1_desc.iInterface = us[STR_AS_IN_ALT1].id;
 
+       if (FUOUT_EN(uac2_opts)) {
+               u8 *i_feature = (u8 *)out_feature_unit_desc;
+
+               i_feature = (u8 *)out_feature_unit_desc +
+                                       out_feature_unit_desc->bLength - 1;
+               *i_feature = us[STR_FU_OUT].id;
+       }
+       if (FUIN_EN(uac2_opts)) {
+               u8 *i_feature = (u8 *)in_feature_unit_desc;
+
+               i_feature = (u8 *)in_feature_unit_desc +
+                                       in_feature_unit_desc->bLength - 1;
+               *i_feature = us[STR_FU_IN].id;
+       }
+
 
        /* Initialize the configurable parameters */
        usb_out_it_desc.bNrChannels = num_channels(uac2_opts->c_chmask);
        as_out_fmt1_desc.bBitResolution = uac2_opts->c_ssize * 8;
        as_in_fmt1_desc.bSubslotSize = uac2_opts->p_ssize;
        as_in_fmt1_desc.bBitResolution = uac2_opts->p_ssize * 8;
+       if (FUOUT_EN(uac2_opts)) {
+               __le32 *bma = (__le32 *)&out_feature_unit_desc->bmaControls[0];
+               u32 control = 0;
+
+               if (uac2_opts->c_mute_present)
+                       control |= CONTROL_RDWR << FU_MUTE_CTRL;
+               if (uac2_opts->c_volume_present)
+                       control |= CONTROL_RDWR << FU_VOL_CTRL;
+               *bma = cpu_to_le32(control);
+       }
+       if (FUIN_EN(uac2_opts)) {
+               __le32 *bma = (__le32 *)&in_feature_unit_desc->bmaControls[0];
+               u32 control = 0;
+
+               if (uac2_opts->p_mute_present)
+                       control |= CONTROL_RDWR << FU_MUTE_CTRL;
+               if (uac2_opts->p_volume_present)
+                       control |= CONTROL_RDWR << FU_VOL_CTRL;
+               *bma = cpu_to_le32(control);
+       }
 
        snprintf(clksrc_in, sizeof(clksrc_in), "%uHz", uac2_opts->p_srate);
        snprintf(clksrc_out, sizeof(clksrc_out), "%uHz", uac2_opts->c_srate);
        ret = usb_interface_id(cfg, fn);
        if (ret < 0) {
                dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
-               return ret;
+               goto err_free_fu;
        }
        iad_desc.bFirstInterface = ret;
 
                ret = usb_interface_id(cfg, fn);
                if (ret < 0) {
                        dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
-                       return ret;
+                       goto err_free_fu;
                }
                std_as_out_if0_desc.bInterfaceNumber = ret;
                std_as_out_if1_desc.bInterfaceNumber = ret;
                ret = usb_interface_id(cfg, fn);
                if (ret < 0) {
                        dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
-                       return ret;
+                       goto err_free_fu;
                }
                std_as_in_if0_desc.bInterfaceNumber = ret;
                std_as_in_if1_desc.bInterfaceNumber = ret;
                uac2->as_in_alt = 0;
        }
 
+       if (FUOUT_EN(uac2_opts) || FUIN_EN(uac2_opts)) {
+               uac2->int_ep = usb_ep_autoconfig(gadget, &fs_ep_int_desc);
+               if (!uac2->int_ep) {
+                       dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+                       ret = -ENODEV;
+                       goto err_free_fu;
+               }
+
+               std_ac_if_desc.bNumEndpoints = 1;
+       }
+
        /* Calculate wMaxPacketSize according to audio bandwidth */
        ret = set_ep_max_packet_size(uac2_opts, &fs_epin_desc, USB_SPEED_FULL,
                                     true);
                agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc);
                if (!agdev->out_ep) {
                        dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
-                       return -ENODEV;
+                       ret = -ENODEV;
+                       goto err_free_fu;
                }
                if (EPOUT_FBACK_IN_EN(uac2_opts)) {
                        agdev->in_ep_fback = usb_ep_autoconfig(gadget,
                        if (!agdev->in_ep_fback) {
                                dev_err(dev, "%s:%d Error!\n",
                                        __func__, __LINE__);
-                               return -ENODEV;
+                               ret = -ENODEV;
+                               goto err_free_fu;
                        }
                }
        }
                agdev->in_ep = usb_ep_autoconfig(gadget, &fs_epin_desc);
                if (!agdev->in_ep) {
                        dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
-                       return -ENODEV;
+                       ret = -ENODEV;
+                       goto err_free_fu;
                }
        }
 
        agdev->out_ep_maxpsize = max_t(u16, agdev->out_ep_maxpsize,
                                le16_to_cpu(ss_epout_desc.wMaxPacketSize));
 
+       // HS and SS endpoint addresses are copied from autoconfigured FS descriptors
+       hs_ep_int_desc.bEndpointAddress = fs_ep_int_desc.bEndpointAddress;
        hs_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress;
        hs_epin_fback_desc.bEndpointAddress = fs_epin_fback_desc.bEndpointAddress;
        hs_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress;
        ss_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress;
        ss_epin_fback_desc.bEndpointAddress = fs_epin_fback_desc.bEndpointAddress;
        ss_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress;
+       ss_ep_int_desc.bEndpointAddress = fs_ep_int_desc.bEndpointAddress;
 
        setup_descriptor(uac2_opts);
 
        ret = usb_assign_descriptors(fn, fs_audio_desc, hs_audio_desc, ss_audio_desc,
                                     ss_audio_desc);
        if (ret)
-               return ret;
+               goto err_free_fu;
 
        agdev->gadget = gadget;
 
        agdev->params.p_chmask = uac2_opts->p_chmask;
        agdev->params.p_srate = uac2_opts->p_srate;
        agdev->params.p_ssize = uac2_opts->p_ssize;
+       if (FUIN_EN(uac2_opts)) {
+               agdev->params.p_fu.id = USB_IN_FU_ID;
+               agdev->params.p_fu.mute_present = uac2_opts->p_mute_present;
+               agdev->params.p_fu.volume_present = uac2_opts->p_volume_present;
+               agdev->params.p_fu.volume_min = uac2_opts->p_volume_min;
+               agdev->params.p_fu.volume_max = uac2_opts->p_volume_max;
+               agdev->params.p_fu.volume_res = uac2_opts->p_volume_res;
+       }
        agdev->params.c_chmask = uac2_opts->c_chmask;
        agdev->params.c_srate = uac2_opts->c_srate;
        agdev->params.c_ssize = uac2_opts->c_ssize;
+       if (FUOUT_EN(uac2_opts)) {
+               agdev->params.c_fu.id = USB_OUT_FU_ID;
+               agdev->params.c_fu.mute_present = uac2_opts->c_mute_present;
+               agdev->params.c_fu.volume_present = uac2_opts->c_volume_present;
+               agdev->params.c_fu.volume_min = uac2_opts->c_volume_min;
+               agdev->params.c_fu.volume_max = uac2_opts->c_volume_max;
+               agdev->params.c_fu.volume_res = uac2_opts->c_volume_res;
+       }
        agdev->params.req_number = uac2_opts->req_number;
        agdev->params.fb_max = uac2_opts->fb_max;
+
+       if (FUOUT_EN(uac2_opts) || FUIN_EN(uac2_opts))
+    agdev->notify = afunc_notify;
+
        ret = g_audio_setup(agdev, "UAC2 PCM", "UAC2_Gadget");
        if (ret)
                goto err_free_descs;
+
        return 0;
 
 err_free_descs:
        usb_free_all_descriptors(fn);
        agdev->gadget = NULL;
+err_free_fu:
+       kfree(out_feature_unit_desc);
+       out_feature_unit_desc = NULL;
+       kfree(in_feature_unit_desc);
+       in_feature_unit_desc = NULL;
+       return ret;
+}
+
+static void
+afunc_notify_complete(struct usb_ep *_ep, struct usb_request *req)
+{
+       struct g_audio *agdev = req->context;
+       struct f_uac2 *uac2 = func_to_uac2(&agdev->func);
+
+       atomic_dec(&uac2->int_count);
+       kfree(req->buf);
+       usb_ep_free_request(_ep, req);
+}
+
+static int
+afunc_notify(struct g_audio *agdev, int unit_id, int cs)
+{
+       struct f_uac2 *uac2 = func_to_uac2(&agdev->func);
+       struct usb_request *req;
+       struct uac2_interrupt_data_msg *msg;
+       u16 w_index, w_value;
+       int ret;
+
+       if (!uac2->int_ep->enabled)
+               return 0;
+
+       if (atomic_inc_return(&uac2->int_count) > UAC2_DEF_INT_REQ_NUM) {
+               atomic_dec(&uac2->int_count);
+               return 0;
+       }
+
+       req = usb_ep_alloc_request(uac2->int_ep, GFP_ATOMIC);
+       if (req == NULL) {
+               ret = -ENOMEM;
+               goto err_dec_int_count;
+       }
+
+       msg = kzalloc(sizeof(*msg), GFP_ATOMIC);
+       if (msg == NULL) {
+               ret = -ENOMEM;
+               goto err_free_request;
+       }
+
+       w_index = unit_id << 8 | uac2->ac_intf;
+       w_value = cs << 8;
+
+       msg->bInfo = 0; /* Non-vendor, interface interrupt */
+       msg->bAttribute = UAC2_CS_CUR;
+       msg->wIndex = cpu_to_le16(w_index);
+       msg->wValue = cpu_to_le16(w_value);
+
+       req->length = sizeof(*msg);
+       req->buf = msg;
+       req->context = agdev;
+       req->complete = afunc_notify_complete;
+
+       ret = usb_ep_queue(uac2->int_ep, req, GFP_ATOMIC);
+
+       if (ret)
+               goto err_free_msg;
+
+       return 0;
+
+err_free_msg:
+       kfree(msg);
+err_free_request:
+       usb_ep_free_request(uac2->int_ep, req);
+err_dec_int_count:
+       atomic_dec(&uac2->int_count);
+
        return ret;
 }
 
 {
        struct usb_composite_dev *cdev = fn->config->cdev;
        struct f_uac2 *uac2 = func_to_uac2(fn);
+       struct g_audio *agdev = func_to_g_audio(fn);
        struct usb_gadget *gadget = cdev->gadget;
        struct device *dev = &gadget->dev;
        int ret = 0;
                        dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
                        return -EINVAL;
                }
+
+               /* restart interrupt endpoint */
+               if (uac2->int_ep) {
+                       usb_ep_disable(uac2->int_ep);
+                       config_ep_by_speed(gadget, &agdev->func, uac2->int_ep);
+                       usb_ep_enable(uac2->int_ep);
+               }
+
                return 0;
        }
 
        uac2->as_out_alt = 0;
        u_audio_stop_capture(&uac2->g_audio);
        u_audio_stop_playback(&uac2->g_audio);
+       if (uac2->int_ep)
+               usb_ep_disable(uac2->int_ep);
 }
 
 static int
 {
        struct usb_request *req = fn->config->cdev->req;
        struct g_audio *agdev = func_to_g_audio(fn);
-       struct f_uac2_opts *opts;
+       struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev);
        u16 w_length = le16_to_cpu(cr->wLength);
        u16 w_index = le16_to_cpu(cr->wIndex);
        u16 w_value = le16_to_cpu(cr->wValue);
        int value = -EOPNOTSUPP;
        int p_srate, c_srate;
 
-       opts = g_audio_to_uac2_opts(agdev);
        p_srate = opts->p_srate;
        c_srate = opts->c_srate;
 
-       if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
-               struct cntrl_cur_lay3 c;
-               memset(&c, 0, sizeof(struct cntrl_cur_lay3));
+       if ((entity_id == USB_IN_CLK_ID) || (entity_id == USB_OUT_CLK_ID)) {
+               if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
+                       struct cntrl_cur_lay3 c;
+
+                       memset(&c, 0, sizeof(struct cntrl_cur_lay3));
+
+                       if (entity_id == USB_IN_CLK_ID)
+                               c.dCUR = cpu_to_le32(p_srate);
+                       else if (entity_id == USB_OUT_CLK_ID)
+                               c.dCUR = cpu_to_le32(c_srate);
+
+                       value = min_t(unsigned int, w_length, sizeof(c));
+                       memcpy(req->buf, &c, value);
+               } else if (control_selector == UAC2_CS_CONTROL_CLOCK_VALID) {
+                       *(u8 *)req->buf = 1;
+                       value = min_t(unsigned int, w_length, 1);
+               } else {
+                       dev_err(&agdev->gadget->dev,
+                               "%s:%d control_selector=%d TODO!\n",
+                               __func__, __LINE__, control_selector);
+               }
+       } else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) ||
+                       (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) {
+               unsigned int is_playback = 0;
 
-               if (entity_id == USB_IN_CLK_ID)
-                       c.dCUR = cpu_to_le32(p_srate);
-               else if (entity_id == USB_OUT_CLK_ID)
-                       c.dCUR = cpu_to_le32(c_srate);
+               if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID))
+                       is_playback = 1;
 
-               value = min_t(unsigned, w_length, sizeof c);
-               memcpy(req->buf, &c, value);
-       } else if (control_selector == UAC2_CS_CONTROL_CLOCK_VALID) {
-               *(u8 *)req->buf = 1;
-               value = min_t(unsigned, w_length, 1);
+               if (control_selector == UAC_FU_MUTE) {
+                       unsigned int mute;
+
+                       u_audio_get_mute(agdev, is_playback, &mute);
+
+                       *(u8 *)req->buf = mute;
+                       value = min_t(unsigned int, w_length, 1);
+               } else if (control_selector == UAC_FU_VOLUME) {
+                       struct cntrl_cur_lay2 c;
+                       s16 volume;
+
+                       memset(&c, 0, sizeof(struct cntrl_cur_lay2));
+
+                       u_audio_get_volume(agdev, is_playback, &volume);
+                       c.wCUR = cpu_to_le16(volume);
+
+                       value = min_t(unsigned int, w_length, sizeof(c));
+                       memcpy(req->buf, &c, value);
+               } else {
+                       dev_err(&agdev->gadget->dev,
+                               "%s:%d control_selector=%d TODO!\n",
+                               __func__, __LINE__, control_selector);
+               }
        } else {
                dev_err(&agdev->gadget->dev,
-                       "%s:%d control_selector=%d TODO!\n",
-                       __func__, __LINE__, control_selector);
+                       "%s:%d entity_id=%d control_selector=%d TODO!\n",
+                       __func__, __LINE__, entity_id, control_selector);
        }
 
        return value;
 {
        struct usb_request *req = fn->config->cdev->req;
        struct g_audio *agdev = func_to_g_audio(fn);
-       struct f_uac2_opts *opts;
+       struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev);
        u16 w_length = le16_to_cpu(cr->wLength);
        u16 w_index = le16_to_cpu(cr->wIndex);
        u16 w_value = le16_to_cpu(cr->wValue);
        u8 entity_id = (w_index >> 8) & 0xff;
        u8 control_selector = w_value >> 8;
-       struct cntrl_range_lay3 r;
        int value = -EOPNOTSUPP;
        int p_srate, c_srate;
 
-       opts = g_audio_to_uac2_opts(agdev);
        p_srate = opts->p_srate;
        c_srate = opts->c_srate;
 
-       if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
-               if (entity_id == USB_IN_CLK_ID)
-                       r.dMIN = cpu_to_le32(p_srate);
-               else if (entity_id == USB_OUT_CLK_ID)
-                       r.dMIN = cpu_to_le32(c_srate);
-               else
-                       return -EOPNOTSUPP;
+       if ((entity_id == USB_IN_CLK_ID) || (entity_id == USB_OUT_CLK_ID)) {
+               if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
+                       struct cntrl_range_lay3 r;
+
+                       if (entity_id == USB_IN_CLK_ID)
+                               r.dMIN = cpu_to_le32(p_srate);
+                       else if (entity_id == USB_OUT_CLK_ID)
+                               r.dMIN = cpu_to_le32(c_srate);
+                       else
+                               return -EOPNOTSUPP;
 
-               r.dMAX = r.dMIN;
-               r.dRES = 0;
-               r.wNumSubRanges = cpu_to_le16(1);
+                       r.dMAX = r.dMIN;
+                       r.dRES = 0;
+                       r.wNumSubRanges = cpu_to_le16(1);
 
-               value = min_t(unsigned, w_length, sizeof r);
-               memcpy(req->buf, &r, value);
+                       value = min_t(unsigned int, w_length, sizeof(r));
+                       memcpy(req->buf, &r, value);
+               } else {
+                       dev_err(&agdev->gadget->dev,
+                               "%s:%d control_selector=%d TODO!\n",
+                               __func__, __LINE__, control_selector);
+               }
+       } else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) ||
+                       (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) {
+               unsigned int is_playback = 0;
+
+               if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID))
+                       is_playback = 1;
+
+               if (control_selector == UAC_FU_VOLUME) {
+                       struct cntrl_range_lay2 r;
+                       s16 max_db, min_db, res_db;
+
+                       if (is_playback) {
+                               max_db = opts->p_volume_max;
+                               min_db = opts->p_volume_min;
+                               res_db = opts->p_volume_res;
+                       } else {
+                               max_db = opts->c_volume_max;
+                               min_db = opts->c_volume_min;
+                               res_db = opts->c_volume_res;
+                       }
+
+                       r.wMAX = cpu_to_le16(max_db);
+                       r.wMIN = cpu_to_le16(min_db);
+                       r.wRES = cpu_to_le16(res_db);
+                       r.wNumSubRanges = cpu_to_le16(1);
+
+                       value = min_t(unsigned int, w_length, sizeof(r));
+                       memcpy(req->buf, &r, value);
+               } else {
+                       dev_err(&agdev->gadget->dev,
+                               "%s:%d control_selector=%d TODO!\n",
+                               __func__, __LINE__, control_selector);
+               }
        } else {
                dev_err(&agdev->gadget->dev,
-                       "%s:%d control_selector=%d TODO!\n",
-                       __func__, __LINE__, control_selector);
+                       "%s:%d entity_id=%d control_selector=%d TODO!\n",
+                       __func__, __LINE__, entity_id, control_selector);
        }
 
        return value;
                return -EOPNOTSUPP;
 }
 
+static void
+out_rq_cur_complete(struct usb_ep *ep, struct usb_request *req)
+{
+       struct g_audio *agdev = req->context;
+       struct usb_composite_dev *cdev = agdev->func.config->cdev;
+       struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev);
+       struct f_uac2 *uac2 = func_to_uac2(&agdev->func);
+       struct usb_ctrlrequest *cr = &uac2->setup_cr;
+       u16 w_index = le16_to_cpu(cr->wIndex);
+       u16 w_value = le16_to_cpu(cr->wValue);
+       u8 entity_id = (w_index >> 8) & 0xff;
+       u8 control_selector = w_value >> 8;
+
+       if (req->status != 0) {
+               dev_dbg(&cdev->gadget->dev, "completion err %d\n", req->status);
+               return;
+       }
+
+       if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) ||
+               (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) {
+               unsigned int is_playback = 0;
+
+               if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID))
+                       is_playback = 1;
+
+               if (control_selector == UAC_FU_MUTE) {
+                       u8 mute = *(u8 *)req->buf;
+
+                       u_audio_set_mute(agdev, is_playback, mute);
+
+                       return;
+               } else if (control_selector == UAC_FU_VOLUME) {
+                       struct cntrl_cur_lay2 *c = req->buf;
+                       s16 volume;
+
+                       volume = le16_to_cpu(c->wCUR);
+                       u_audio_set_volume(agdev, is_playback, volume);
+
+                       return;
+               } else {
+                       dev_err(&agdev->gadget->dev,
+                               "%s:%d control_selector=%d TODO!\n",
+                               __func__, __LINE__, control_selector);
+                       usb_ep_set_halt(ep);
+               }
+       }
+}
+
 static int
 out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
 {
+       struct usb_request *req = fn->config->cdev->req;
+       struct g_audio *agdev = func_to_g_audio(fn);
+       struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev);
+       struct f_uac2 *uac2 = func_to_uac2(fn);
        u16 w_length = le16_to_cpu(cr->wLength);
+       u16 w_index = le16_to_cpu(cr->wIndex);
        u16 w_value = le16_to_cpu(cr->wValue);
+       u8 entity_id = (w_index >> 8) & 0xff;
        u8 control_selector = w_value >> 8;
 
-       if (control_selector == UAC2_CS_CONTROL_SAM_FREQ)
-               return w_length;
+       if ((entity_id == USB_IN_CLK_ID) || (entity_id == USB_OUT_CLK_ID)) {
+               if (control_selector == UAC2_CS_CONTROL_SAM_FREQ)
+                       return w_length;
+       } else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) ||
+                       (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) {
+               memcpy(&uac2->setup_cr, cr, sizeof(*cr));
+               req->context = agdev;
+               req->complete = out_rq_cur_complete;
 
+               return w_length;
+       } else {
+               dev_err(&agdev->gadget->dev,
+                       "%s:%d entity_id=%d control_selector=%d TODO!\n",
+                       __func__, __LINE__, entity_id, control_selector);
+       }
        return -EOPNOTSUPP;
 }
 
        .release        = f_uac2_attr_release,
 };
 
-#define UAC2_ATTRIBUTE(name)                                           \
+#define uac2_kstrtou32 kstrtou32
+#define uac2_kstrtos16 kstrtos16
+#define uac2_kstrtobool(s, base, res) kstrtobool((s), (res))
+
+static const char *u32_fmt = "%u\n";
+static const char *s16_fmt = "%hd\n";
+static const char *bool_fmt = "%u\n";
+
+#define UAC2_ATTRIBUTE(type, name)                                     \
 static ssize_t f_uac2_opts_##name##_show(struct config_item *item,     \
                                         char *page)                    \
 {                                                                      \
        int result;                                                     \
                                                                        \
        mutex_lock(&opts->lock);                                        \
-       result = sprintf(page, "%u\n", opts->name);                     \
+       result = sprintf(page, type##_fmt, opts->name);                 \
        mutex_unlock(&opts->lock);                                      \
                                                                        \
        return result;                                                  \
 {                                                                      \
        struct f_uac2_opts *opts = to_f_uac2_opts(item);                \
        int ret;                                                        \
-       u32 num;                                                        \
+       type num;                                                       \
                                                                        \
        mutex_lock(&opts->lock);                                        \
        if (opts->refcnt) {                                             \
                goto end;                                               \
        }                                                               \
                                                                        \
-       ret = kstrtou32(page, 0, &num);                                 \
+       ret = uac2_kstrto##type(page, 0, &num);                         \
        if (ret)                                                        \
                goto end;                                               \
                                                                        \
                                                                        \
 CONFIGFS_ATTR(f_uac2_opts_, name)
 
-UAC2_ATTRIBUTE(p_chmask);
-UAC2_ATTRIBUTE(p_srate);
-UAC2_ATTRIBUTE(p_ssize);
-UAC2_ATTRIBUTE(c_chmask);
-UAC2_ATTRIBUTE(c_srate);
+UAC2_ATTRIBUTE(u32, p_chmask);
+UAC2_ATTRIBUTE(u32, p_srate);
+UAC2_ATTRIBUTE(u32, p_ssize);
+UAC2_ATTRIBUTE(u32, c_chmask);
+UAC2_ATTRIBUTE(u32, c_srate);
 UAC2_ATTRIBUTE_SYNC(c_sync);
-UAC2_ATTRIBUTE(c_ssize);
-UAC2_ATTRIBUTE(req_number);
-UAC2_ATTRIBUTE(fb_max);
+UAC2_ATTRIBUTE(u32, c_ssize);
+UAC2_ATTRIBUTE(u32, req_number);
+
+UAC2_ATTRIBUTE(bool, p_mute_present);
+UAC2_ATTRIBUTE(bool, p_volume_present);
+UAC2_ATTRIBUTE(s16, p_volume_min);
+UAC2_ATTRIBUTE(s16, p_volume_max);
+UAC2_ATTRIBUTE(s16, p_volume_res);
+
+UAC2_ATTRIBUTE(bool, c_mute_present);
+UAC2_ATTRIBUTE(bool, c_volume_present);
+UAC2_ATTRIBUTE(s16, c_volume_min);
+UAC2_ATTRIBUTE(s16, c_volume_max);
+UAC2_ATTRIBUTE(s16, c_volume_res);
+UAC2_ATTRIBUTE(u32, fb_max);
 
 static struct configfs_attribute *f_uac2_attrs[] = {
        &f_uac2_opts_attr_p_chmask,
        &f_uac2_opts_attr_c_sync,
        &f_uac2_opts_attr_req_number,
        &f_uac2_opts_attr_fb_max,
+
+       &f_uac2_opts_attr_p_mute_present,
+       &f_uac2_opts_attr_p_volume_present,
+       &f_uac2_opts_attr_p_volume_min,
+       &f_uac2_opts_attr_p_volume_max,
+       &f_uac2_opts_attr_p_volume_res,
+
+       &f_uac2_opts_attr_c_mute_present,
+       &f_uac2_opts_attr_c_volume_present,
+       &f_uac2_opts_attr_c_volume_min,
+       &f_uac2_opts_attr_c_volume_max,
+       &f_uac2_opts_attr_c_volume_res,
+
        NULL,
 };
 
        opts->c_srate = UAC2_DEF_CSRATE;
        opts->c_ssize = UAC2_DEF_CSSIZE;
        opts->c_sync = UAC2_DEF_CSYNC;
+
+       opts->p_mute_present = UAC2_DEF_MUTE_PRESENT;
+       opts->p_volume_present = UAC2_DEF_VOLUME_PRESENT;
+       opts->p_volume_min = UAC2_DEF_MIN_DB;
+       opts->p_volume_max = UAC2_DEF_MAX_DB;
+       opts->p_volume_res = UAC2_DEF_RES_DB;
+
+       opts->c_mute_present = UAC2_DEF_MUTE_PRESENT;
+       opts->c_volume_present = UAC2_DEF_VOLUME_PRESENT;
+       opts->c_volume_min = UAC2_DEF_MIN_DB;
+       opts->c_volume_max = UAC2_DEF_MAX_DB;
+       opts->c_volume_res = UAC2_DEF_RES_DB;
+
        opts->req_number = UAC2_DEF_REQ_NUM;
        opts->fb_max = UAC2_DEF_FB_MAX;
        return &opts->func_inst;
        usb_free_all_descriptors(f);
 
        agdev->gadget = NULL;
+
+       kfree(out_feature_unit_desc);
+       out_feature_unit_desc = NULL;
+       kfree(in_feature_unit_desc);
+       in_feature_unit_desc = NULL;
 }
 
 static struct usb_function *afunc_alloc(struct usb_function_instance *fi)
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Yadwinder Singh");
 MODULE_AUTHOR("Jaswinder Singh");
+MODULE_AUTHOR("Ruslan Bilovol");