Merge v5.14-rc3 into usb-next
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 26 Jul 2021 09:16:46 +0000 (11:16 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 26 Jul 2021 09:16:46 +0000 (11:16 +0200)
We need the fixes in here, and this resolves a merge issue with
drivers/usb/dwc3/gadget.c

Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
39 files changed:
Documentation/ABI/testing/configfs-usb-gadget-uac1
Documentation/ABI/testing/configfs-usb-gadget-uac2
Documentation/devicetree/bindings/phy/qcom,qmp-usb3-dp-phy.yaml
Documentation/devicetree/bindings/usb/generic-ehci.yaml
Documentation/devicetree/bindings/usb/generic-ohci.yaml
Documentation/devicetree/bindings/usb/snps,dwc3.yaml
Documentation/usb/gadget-testing.rst
arch/arm64/boot/dts/qcom/sc7280-idp.dts
arch/arm64/boot/dts/qcom/sc7280.dtsi
drivers/of/base.c
drivers/usb/dwc2/core.h
drivers/usb/dwc2/gadget.c
drivers/usb/dwc3/core.c
drivers/usb/dwc3/core.h
drivers/usb/dwc3/dwc3-qcom.c
drivers/usb/dwc3/ep0.c
drivers/usb/dwc3/gadget.c
drivers/usb/gadget/composite.c
drivers/usb/gadget/configfs.c
drivers/usb/gadget/function/f_ncm.c
drivers/usb/gadget/function/f_uac1.c
drivers/usb/gadget/function/f_uac2.c
drivers/usb/gadget/function/u_audio.c
drivers/usb/gadget/function/u_audio.h
drivers/usb/gadget/function/u_ether.c
drivers/usb/gadget/function/u_uac1.h
drivers/usb/gadget/function/u_uac2.h
drivers/usb/gadget/udc/core.c
drivers/usb/host/ehci-mv.c
drivers/usb/host/fotg210-hcd.c
drivers/usb/host/fotg210.h
drivers/usb/host/ohci-spear.c
drivers/usb/host/xhci-pci-renesas.c
drivers/usb/host/xhci-pci.c
drivers/usb/host/xhci-pci.h
drivers/usb/phy/phy-isp1301-omap.c
include/linux/of.h
include/linux/usb/audio-v2.h
include/linux/usb/gadget.h

index dc23fd7..dd647d4 100644 (file)
@@ -8,9 +8,19 @@ Description:
                c_chmask        capture channel mask
                c_srate         capture sampling rate
                c_ssize         capture sample size (bytes)
+               c_mute_present  capture mute control enable
+               c_volume_present        capture volume control enable
+               c_volume_min    capture volume control min value (in 1/256 dB)
+               c_volume_max    capture volume control max value (in 1/256 dB)
+               c_volume_res    capture volume control resolution (in 1/256 dB)
                p_chmask        playback channel mask
                p_srate         playback sampling rate
                p_ssize         playback sample size (bytes)
+               p_mute_present  playback mute control enable
+               p_volume_present        playback volume control enable
+               p_volume_min    playback volume control min value (in 1/256 dB)
+               p_volume_max    playback volume control max value (in 1/256 dB)
+               p_volume_res    playback volume control resolution (in 1/256 dB)
                req_number      the number of pre-allocated request
                                for both capture and playback
                ==========      ===================================
index 26fb8e9..cfd160f 100644 (file)
@@ -9,8 +9,18 @@ Description:
                c_srate    capture sampling rate
                c_ssize    capture sample size (bytes)
                c_sync     capture synchronization type (async/adaptive)
+               c_mute_present  capture mute control enable
+               c_volume_present        capture volume control enable
+               c_volume_min    capture volume control min value (in 1/256 dB)
+               c_volume_max    capture volume control max value (in 1/256 dB)
+               c_volume_res    capture volume control resolution (in 1/256 dB)
                fb_max     maximum extra bandwidth in async mode
                p_chmask   playback channel mask
                p_srate    playback sampling rate
                p_ssize    playback sample size (bytes)
+               p_mute_present  playback mute control enable
+               p_volume_present        playback volume control enable
+               p_volume_min    playback volume control min value (in 1/256 dB)
+               p_volume_max    playback volume control max value (in 1/256 dB)
+               p_volume_res    playback volume control resolution (in 1/256 dB)
                =========  ============================
index 217aa6c..2019983 100644 (file)
@@ -14,6 +14,7 @@ properties:
   compatible:
     enum:
       - qcom,sc7180-qmp-usb3-dp-phy
+      - qcom,sc7280-qmp-usb3-dp-phy
       - qcom,sdm845-qmp-usb3-dp-phy
       - qcom,sm8250-qmp-usb3-dp-phy
   reg:
index 8089dc9..f6e5e4a 100644 (file)
@@ -142,6 +142,11 @@ properties:
   iommus:
     maxItems: 1
 
+  dr_mode:
+    enum:
+      - host
+      - otg
+
 required:
   - compatible
   - reg
index 0f5f6ea..569777a 100644 (file)
@@ -109,6 +109,11 @@ properties:
   iommus:
     maxItems: 1
 
+  dr_mode:
+    enum:
+      - host
+      - otg
+
 required:
   - compatible
   - reg
index 41416fb..078fb78 100644 (file)
@@ -289,10 +289,21 @@ properties:
     maximum: 16
 
   tx-fifo-resize:
-    description: Determines if the FIFO *has* to be reallocated
-    deprecated: true
+    description: Determines if the TX fifos can be dynamically resized depending
+      on the number of IN endpoints used and if bursting is supported.  This
+      may help improve bandwidth on platforms with higher system latencies, as
+      increased fifo space allows for the controller to prefetch data into its
+      internal memory.
     type: boolean
 
+  tx-fifo-max-num:
+    description: Specifies the max number of packets the txfifo resizing logic
+      can account for when higher endpoint bursting is used. (bMaxBurst > 6) The
+      higher the number, the more fifo space the txfifo resizing logic will
+      allocate for that endpoint.
+    $ref: /schemas/types.yaml#/definitions/uint8
+    minimum: 3
+
   snps,incr-burst-type-adjustment:
     description:
       Value for INCR burst type of GSBUSCFG0 register, undefined length INCR
index 9d6276f..d6253f1 100644 (file)
@@ -729,10 +729,20 @@ The uac2 function provides these attributes in its function directory:
        c_srate         capture sampling rate
        c_ssize         capture sample size (bytes)
        c_sync          capture synchronization type (async/adaptive)
-       fb_max          maximum extra bandwidth in async mode
+       c_mute_present  capture mute control enable
+       c_volume_present        capture volume control enable
+       c_volume_min    capture volume control min value (in 1/256 dB)
+       c_volume_max    capture volume control max value (in 1/256 dB)
+       c_volume_res    capture volume control resolution (in 1/256 dB)
+       fb_max    maximum extra bandwidth in async mode
        p_chmask        playback channel mask
        p_srate         playback sampling rate
        p_ssize         playback sample size (bytes)
+       p_mute_present  playback mute control enable
+       p_volume_present        playback volume control enable
+       p_volume_min    playback volume control min value (in 1/256 dB)
+       p_volume_max    playback volume control max value (in 1/256 dB)
+       p_volume_res    playback volume control resolution (in 1/256 dB)
        req_number      the number of pre-allocated request for both capture
                        and playback
        =============== ====================================================
@@ -905,14 +915,24 @@ The function name to use when creating the function directory is "uac1".
 The uac1 function provides these attributes in its function directory:
 
        ========== ====================================================
-       c_chmask   capture channel mask
-       c_srate    capture sampling rate
-       c_ssize    capture sample size (bytes)
-       p_chmask   playback channel mask
-       p_srate    playback sampling rate
-       p_ssize    playback sample size (bytes)
-       req_number the number of pre-allocated request for both capture
-                  and playback
+       c_chmask        capture channel mask
+       c_srate         capture sampling rate
+       c_ssize         capture sample size (bytes)
+       c_mute_present  capture mute control enable
+       c_volume_present        capture volume control enable
+       c_volume_min    capture volume control min value (in 1/256 dB)
+       c_volume_max    capture volume control max value (in 1/256 dB)
+       c_volume_res    capture volume control resolution (in 1/256 dB)
+       p_chmask        playback channel mask
+       p_srate         playback sampling rate
+       p_ssize         playback sample size (bytes)
+       p_mute_present  playback mute control enable
+       p_volume_present        playback volume control enable
+       p_volume_min    playback volume control min value (in 1/256 dB)
+       p_volume_max    playback volume control max value (in 1/256 dB)
+       p_volume_res    playback volume control resolution (in 1/256 dB)
+       req_number      the number of pre-allocated request for both capture
+                       and playback
        ========== ====================================================
 
 The attributes have sane default values.
index 3900cfc..44326d8 100644 (file)
        status = "okay";
 };
 
+&usb_1 {
+       status = "okay";
+};
+
+&usb_1_dwc3 {
+       dr_mode = "host";
+};
+
+&usb_1_hsphy {
+       status = "okay";
+
+       vdda-pll-supply = <&vreg_l10c_0p8>;
+       vdda33-supply = <&vreg_l2b_3p0>;
+       vdda18-supply = <&vreg_l1c_1p8>;
+};
+
+&usb_1_qmpphy {
+       status = "okay";
+
+       vdda-phy-supply = <&vreg_l6b_1p2>;
+       vdda-pll-supply = <&vreg_l1b_0p8>;
+};
+
+&usb_2 {
+       status = "okay";
+};
+
+&usb_2_dwc3 {
+       dr_mode = "peripheral";
+};
+
+&usb_2_hsphy {
+       status = "okay";
+
+       vdda-pll-supply = <&vreg_l10c_0p8>;
+       vdda33-supply = <&vreg_l2b_3p0>;
+       vdda18-supply = <&vreg_l1c_1p8>;
+};
+
 /* PINCTRL - additions to nodes defined in sc7280.dtsi */
 
 &qup_uart5_default {
index a8c274a..cd6908f 100644 (file)
                        };
                };
 
+               usb_1_hsphy: phy@88e3000 {
+                       compatible = "qcom,sc7280-usb-hs-phy",
+                                    "qcom,usb-snps-hs-7nm-phy";
+                       reg = <0 0x088e3000 0 0x400>;
+                       status = "disabled";
+                       #phy-cells = <0>;
+
+                       clocks = <&rpmhcc RPMH_CXO_CLK>;
+                       clock-names = "ref";
+
+                       resets = <&gcc GCC_QUSB2PHY_PRIM_BCR>;
+               };
+
+               usb_2_hsphy: phy@88e4000 {
+                       compatible = "qcom,sc7280-usb-hs-phy",
+                                    "qcom,usb-snps-hs-7nm-phy";
+                       reg = <0 0x088e4000 0 0x400>;
+                       status = "disabled";
+                       #phy-cells = <0>;
+
+                       clocks = <&rpmhcc RPMH_CXO_CLK>;
+                       clock-names = "ref";
+
+                       resets = <&gcc GCC_QUSB2PHY_SEC_BCR>;
+               };
+
+               usb_1_qmpphy: phy-wrapper@88e9000 {
+                       compatible = "qcom,sc7280-qmp-usb3-dp-phy",
+                                    "qcom,sm8250-qmp-usb3-dp-phy";
+                       reg = <0 0x088e9000 0 0x200>,
+                             <0 0x088e8000 0 0x40>,
+                             <0 0x088ea000 0 0x200>;
+                       status = "disabled";
+                       #address-cells = <2>;
+                       #size-cells = <2>;
+                       ranges;
+
+                       clocks = <&gcc GCC_USB3_PRIM_PHY_AUX_CLK>,
+                                <&rpmhcc RPMH_CXO_CLK>,
+                                <&gcc GCC_USB3_PRIM_PHY_COM_AUX_CLK>;
+                       clock-names = "aux", "ref_clk_src", "com_aux";
+
+                       resets = <&gcc GCC_USB3_DP_PHY_PRIM_BCR>,
+                                <&gcc GCC_USB3_PHY_PRIM_BCR>;
+                       reset-names = "phy", "common";
+
+                       usb_1_ssphy: usb3-phy@88e9200 {
+                               reg = <0 0x088e9200 0 0x200>,
+                                     <0 0x088e9400 0 0x200>,
+                                     <0 0x088e9c00 0 0x400>,
+                                     <0 0x088e9600 0 0x200>,
+                                     <0 0x088e9800 0 0x200>,
+                                     <0 0x088e9a00 0 0x100>;
+                               #clock-cells = <0>;
+                               #phy-cells = <0>;
+                               clocks = <&gcc GCC_USB3_PRIM_PHY_PIPE_CLK>;
+                               clock-names = "pipe0";
+                               clock-output-names = "usb3_phy_pipe_clk_src";
+                       };
+
+                       dp_phy: dp-phy@88ea200 {
+                               reg = <0 0x088ea200 0 0x200>,
+                                     <0 0x088ea400 0 0x200>,
+                                     <0 0x088eac00 0 0x400>,
+                                     <0 0x088ea600 0 0x200>,
+                                     <0 0x088ea800 0 0x200>,
+                                     <0 0x088eaa00 0 0x100>;
+                               #phy-cells = <0>;
+                               #clock-cells = <1>;
+                               clocks = <&gcc GCC_USB3_PRIM_PHY_PIPE_CLK>;
+                               clock-names = "pipe0";
+                               clock-output-names = "usb3_phy_pipe_clk_src";
+                       };
+               };
+
+               usb_2: usb@8cf8800 {
+                       compatible = "qcom,sc7280-dwc3", "qcom,dwc3";
+                       reg = <0 0x08cf8800 0 0x400>;
+                       status = "disabled";
+                       #address-cells = <2>;
+                       #size-cells = <2>;
+                       ranges;
+                       dma-ranges;
+
+                       clocks = <&gcc GCC_CFG_NOC_USB3_SEC_AXI_CLK>,
+                                <&gcc GCC_USB30_SEC_MASTER_CLK>,
+                                <&gcc GCC_AGGRE_USB3_SEC_AXI_CLK>,
+                                <&gcc GCC_USB30_SEC_MOCK_UTMI_CLK>,
+                                <&gcc GCC_USB30_SEC_SLEEP_CLK>;
+                       clock-names = "cfg_noc", "core", "iface","mock_utmi",
+                                     "sleep";
+
+                       assigned-clocks = <&gcc GCC_USB30_SEC_MOCK_UTMI_CLK>,
+                                         <&gcc GCC_USB30_SEC_MASTER_CLK>;
+                       assigned-clock-rates = <19200000>, <200000000>;
+
+                       interrupts-extended = <&intc GIC_SPI 240 IRQ_TYPE_LEVEL_HIGH>,
+                                    <&pdc 13 IRQ_TYPE_EDGE_RISING>,
+                                    <&pdc 12 IRQ_TYPE_EDGE_RISING>;
+                       interrupt-names = "hs_phy_irq",
+                                         "dm_hs_phy_irq", "dp_hs_phy_irq";
+
+                       power-domains = <&gcc GCC_USB30_SEC_GDSC>;
+
+                       resets = <&gcc GCC_USB30_SEC_BCR>;
+
+                       usb_2_dwc3: usb@8c00000 {
+                               compatible = "snps,dwc3";
+                               reg = <0 0x08c00000 0 0xe000>;
+                               interrupts = <GIC_SPI 242 IRQ_TYPE_LEVEL_HIGH>;
+                               iommus = <&apps_smmu 0xa0 0x0>;
+                               snps,dis_u2_susphy_quirk;
+                               snps,dis_enblslpm_quirk;
+                               phys = <&usb_2_hsphy>;
+                               phy-names = "usb2-phy";
+                               maximum-speed = "high-speed";
+                       };
+               };
+
                dc_noc: interconnect@90e0000 {
                        reg = <0 0x090e0000 0 0x5080>;
                        compatible = "qcom,sc7280-dc-noc";
                        qcom,bcm-voters = <&apps_bcm_voter>;
                };
 
+               usb_1: usb@a6f8800 {
+                       compatible = "qcom,sc7280-dwc3", "qcom,dwc3";
+                       reg = <0 0x0a6f8800 0 0x400>;
+                       status = "disabled";
+                       #address-cells = <2>;
+                       #size-cells = <2>;
+                       ranges;
+                       dma-ranges;
+
+                       clocks = <&gcc GCC_CFG_NOC_USB3_PRIM_AXI_CLK>,
+                                <&gcc GCC_USB30_PRIM_MASTER_CLK>,
+                                <&gcc GCC_AGGRE_USB3_PRIM_AXI_CLK>,
+                                <&gcc GCC_USB30_PRIM_MOCK_UTMI_CLK>,
+                                <&gcc GCC_USB30_PRIM_SLEEP_CLK>;
+                       clock-names = "cfg_noc", "core", "iface", "mock_utmi",
+                                     "sleep";
+
+                       assigned-clocks = <&gcc GCC_USB30_PRIM_MOCK_UTMI_CLK>,
+                                         <&gcc GCC_USB30_PRIM_MASTER_CLK>;
+                       assigned-clock-rates = <19200000>, <200000000>;
+
+                       interrupts-extended = <&intc GIC_SPI 131 IRQ_TYPE_LEVEL_HIGH>,
+                                             <&pdc 14 IRQ_TYPE_EDGE_BOTH>,
+                                             <&pdc 15 IRQ_TYPE_EDGE_BOTH>,
+                                             <&pdc 17 IRQ_TYPE_LEVEL_HIGH>;
+                       interrupt-names = "hs_phy_irq", "dp_hs_phy_irq",
+                                         "dm_hs_phy_irq", "ss_phy_irq";
+
+                       power-domains = <&gcc GCC_USB30_PRIM_GDSC>;
+
+                       resets = <&gcc GCC_USB30_PRIM_BCR>;
+
+                       usb_1_dwc3: usb@a600000 {
+                               compatible = "snps,dwc3";
+                               reg = <0 0x0a600000 0 0xe000>;
+                               interrupts = <GIC_SPI 133 IRQ_TYPE_LEVEL_HIGH>;
+                               iommus = <&apps_smmu 0xe0 0x0>;
+                               snps,dis_u2_susphy_quirk;
+                               snps,dis_enblslpm_quirk;
+                               phys = <&usb_1_hsphy>, <&usb_1_ssphy>;
+                               phy-names = "usb2-phy", "usb3-phy";
+                               maximum-speed = "super-speed";
+                       };
+               };
+
                videocc: clock-controller@aaf0000 {
                        compatible = "qcom,sc7280-videocc";
                        reg = <0 0xaaf0000 0 0x10000>;
index 48e941f..5883d63 100644 (file)
@@ -1821,6 +1821,7 @@ int of_add_property(struct device_node *np, struct property *prop)
 
        return rc;
 }
+EXPORT_SYMBOL_GPL(of_add_property);
 
 int __of_remove_property(struct device_node *np, struct property *prop)
 {
index 483de2b..cb9059a 100644 (file)
@@ -122,6 +122,7 @@ struct dwc2_hsotg_req;
  * @periodic: Set if this is a periodic ep, such as Interrupt
  * @isochronous: Set if this is a isochronous ep
  * @send_zlp: Set if we need to send a zero-length packet.
+ * @wedged: Set if ep is wedged.
  * @desc_list_dma: The DMA address of descriptor chain currently in use.
  * @desc_list: Pointer to descriptor DMA chain head currently in use.
  * @desc_count: Count of entries within the DMA descriptor chain of EP.
@@ -172,6 +173,7 @@ struct dwc2_hsotg_ep {
        unsigned int            periodic:1;
        unsigned int            isochronous:1;
        unsigned int            send_zlp:1;
+       unsigned int            wedged:1;
        unsigned int            target_frame;
 #define TARGET_FRAME_INITIAL   0xFFFFFFFF
        bool                    frame_overrun;
index 3146df6..985b272 100644 (file)
@@ -1806,7 +1806,8 @@ static int dwc2_hsotg_process_req_feature(struct dwc2_hsotg *hsotg,
                case USB_ENDPOINT_HALT:
                        halted = ep->halted;
 
-                       dwc2_hsotg_ep_sethalt(&ep->ep, set, true);
+                       if (!ep->wedged)
+                               dwc2_hsotg_ep_sethalt(&ep->ep, set, true);
 
                        ret = dwc2_hsotg_send_reply(hsotg, ep0, NULL, 0);
                        if (ret) {
@@ -4066,6 +4067,7 @@ static int dwc2_hsotg_ep_enable(struct usb_ep *ep,
        hs_ep->isochronous = 0;
        hs_ep->periodic = 0;
        hs_ep->halted = 0;
+       hs_ep->wedged = 0;
        hs_ep->interval = desc->bInterval;
 
        switch (ep_type) {
@@ -4306,6 +4308,27 @@ static int dwc2_hsotg_ep_dequeue(struct usb_ep *ep, struct usb_request *req)
        return 0;
 }
 
+/**
+ * dwc2_gadget_ep_set_wedge - set wedge on a given endpoint
+ * @ep: The endpoint to be wedged.
+ *
+ */
+static int dwc2_gadget_ep_set_wedge(struct usb_ep *ep)
+{
+       struct dwc2_hsotg_ep *hs_ep = our_ep(ep);
+       struct dwc2_hsotg *hs = hs_ep->parent;
+
+       unsigned long   flags;
+       int             ret;
+
+       spin_lock_irqsave(&hs->lock, flags);
+       hs_ep->wedged = 1;
+       ret = dwc2_hsotg_ep_sethalt(ep, 1, false);
+       spin_unlock_irqrestore(&hs->lock, flags);
+
+       return ret;
+}
+
 /**
  * dwc2_hsotg_ep_sethalt - set halt on a given endpoint
  * @ep: The endpoint to set halt.
@@ -4357,6 +4380,7 @@ static int dwc2_hsotg_ep_sethalt(struct usb_ep *ep, int value, bool now)
                                epctl |= DXEPCTL_EPDIS;
                } else {
                        epctl &= ~DXEPCTL_STALL;
+                       hs_ep->wedged = 0;
                        xfertype = epctl & DXEPCTL_EPTYPE_MASK;
                        if (xfertype == DXEPCTL_EPTYPE_BULK ||
                            xfertype == DXEPCTL_EPTYPE_INTERRUPT)
@@ -4376,6 +4400,7 @@ static int dwc2_hsotg_ep_sethalt(struct usb_ep *ep, int value, bool now)
                        // STALL bit will be set in GOUTNAKEFF interrupt handler
                } else {
                        epctl &= ~DXEPCTL_STALL;
+                       hs_ep->wedged = 0;
                        xfertype = epctl & DXEPCTL_EPTYPE_MASK;
                        if (xfertype == DXEPCTL_EPTYPE_BULK ||
                            xfertype == DXEPCTL_EPTYPE_INTERRUPT)
@@ -4415,6 +4440,7 @@ static const struct usb_ep_ops dwc2_hsotg_ep_ops = {
        .queue          = dwc2_hsotg_ep_queue_lock,
        .dequeue        = dwc2_hsotg_ep_dequeue,
        .set_halt       = dwc2_hsotg_ep_sethalt_lock,
+       .set_wedge      = dwc2_gadget_ep_set_wedge,
        /* note, don't believe we have any call for the fifo routines */
 };
 
index ba74ad7..b194aec 100644 (file)
@@ -1267,6 +1267,7 @@ static void dwc3_get_properties(struct dwc3 *dwc)
        u8                      rx_max_burst_prd;
        u8                      tx_thr_num_pkt_prd;
        u8                      tx_max_burst_prd;
+       u8                      tx_fifo_resize_max_num;
        const char              *usb_psy_name;
        int                     ret;
 
@@ -1282,6 +1283,13 @@ static void dwc3_get_properties(struct dwc3 *dwc)
         */
        hird_threshold = 12;
 
+       /*
+        * default to a TXFIFO size large enough to fit 6 max packets.  This
+        * allows for systems with larger bus latencies to have some headroom
+        * for endpoints that have a large bMaxBurst value.
+        */
+       tx_fifo_resize_max_num = 6;
+
        dwc->maximum_speed = usb_get_maximum_speed(dev);
        dwc->max_ssp_rate = usb_get_maximum_ssp_rate(dev);
        dwc->dr_mode = usb_get_dr_mode(dev);
@@ -1325,6 +1333,11 @@ static void dwc3_get_properties(struct dwc3 *dwc)
                                &tx_thr_num_pkt_prd);
        device_property_read_u8(dev, "snps,tx-max-burst-prd",
                                &tx_max_burst_prd);
+       dwc->do_fifo_resize = device_property_read_bool(dev,
+                                                       "tx-fifo-resize");
+       if (dwc->do_fifo_resize)
+               device_property_read_u8(dev, "tx-fifo-max-num",
+                                       &tx_fifo_resize_max_num);
 
        dwc->disable_scramble_quirk = device_property_read_bool(dev,
                                "snps,disable_scramble_quirk");
@@ -1390,6 +1403,8 @@ static void dwc3_get_properties(struct dwc3 *dwc)
        dwc->tx_max_burst_prd = tx_max_burst_prd;
 
        dwc->imod_interval = 0;
+
+       dwc->tx_fifo_resize_max_num = tx_fifo_resize_max_num;
 }
 
 /* check whether the core supports IMOD */
index 5991766..bcfeadc 100644 (file)
@@ -1023,6 +1023,7 @@ struct dwc3_scratchpad_array {
  * @rx_max_burst_prd: max periodic ESS receive burst size
  * @tx_thr_num_pkt_prd: periodic ESS transmit packet count
  * @tx_max_burst_prd: max periodic ESS transmit burst size
+ * @tx_fifo_resize_max_num: max number of fifos allocated during txfifo resize
  * @hsphy_interface: "utmi" or "ulpi"
  * @connected: true when we're connected to a host, false otherwise
  * @delayed_status: true when gadget driver asks for delayed status
@@ -1037,6 +1038,7 @@ struct dwc3_scratchpad_array {
  *     1       - utmi_l1_suspend_n
  * @is_fpga: true when we are using the FPGA board
  * @pending_events: true when we have pending IRQs to be handled
+ * @do_fifo_resize: true when txfifo resizing is enabled for dwc3 endpoints
  * @pullups_connected: true when Run/Stop bit is set
  * @setup_packet_pending: true when there's a Setup Packet in FIFO. Workaround
  * @three_stage_setup: set if we perform a three phase setup
@@ -1079,6 +1081,11 @@ struct dwc3_scratchpad_array {
  * @dis_split_quirk: set to disable split boundary.
  * @imod_interval: set the interrupt moderation interval in 250ns
  *                     increments or 0 to disable.
+ * @max_cfg_eps: current max number of IN eps used across all USB configs.
+ * @last_fifo_depth: last fifo depth used to determine next fifo ram start
+ *                  address.
+ * @num_ep_resized: carries the current number endpoints which have had its tx
+ *                 fifo resized.
  */
 struct dwc3 {
        struct work_struct      drd_work;
@@ -1233,6 +1240,7 @@ struct dwc3 {
        u8                      rx_max_burst_prd;
        u8                      tx_thr_num_pkt_prd;
        u8                      tx_max_burst_prd;
+       u8                      tx_fifo_resize_max_num;
 
        const char              *hsphy_interface;
 
@@ -1246,6 +1254,7 @@ struct dwc3 {
        unsigned                is_utmi_l1_suspend:1;
        unsigned                is_fpga:1;
        unsigned                pending_events:1;
+       unsigned                do_fifo_resize:1;
        unsigned                pullups_connected:1;
        unsigned                setup_packet_pending:1;
        unsigned                three_stage_setup:1;
@@ -1282,6 +1291,10 @@ struct dwc3 {
        unsigned                async_callbacks:1;
 
        u16                     imod_interval;
+
+       int                     max_cfg_eps;
+       int                     last_fifo_depth;
+       int                     num_ep_resized;
 };
 
 #define INCRX_BURST_MODE 0
@@ -1513,6 +1526,7 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned int cmd,
                struct dwc3_gadget_ep_cmd_params *params);
 int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned int cmd,
                u32 param);
+void dwc3_gadget_clear_tx_fifos(struct dwc3 *dwc);
 #else
 static inline int dwc3_gadget_init(struct dwc3 *dwc)
 { return 0; }
@@ -1532,6 +1546,8 @@ static inline int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned int cmd,
 static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc,
                int cmd, u32 param)
 { return 0; }
+static inline void dwc3_gadget_clear_tx_fifos(struct dwc3 *dwc)
+{ }
 #endif
 
 #if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
index 49e6ca9..25cd123 100644 (file)
@@ -115,7 +115,7 @@ static inline void dwc3_qcom_clrbits(void __iomem *base, u32 offset, u32 val)
        readl(base + offset);
 }
 
-static void dwc3_qcom_vbus_overrride_enable(struct dwc3_qcom *qcom, bool enable)
+static void dwc3_qcom_vbus_override_enable(struct dwc3_qcom *qcom, bool enable)
 {
        if (enable) {
                dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_SS_PHY_CTRL,
@@ -136,7 +136,7 @@ static int dwc3_qcom_vbus_notifier(struct notifier_block *nb,
        struct dwc3_qcom *qcom = container_of(nb, struct dwc3_qcom, vbus_nb);
 
        /* enable vbus override for device mode */
-       dwc3_qcom_vbus_overrride_enable(qcom, event);
+       dwc3_qcom_vbus_override_enable(qcom, event);
        qcom->mode = event ? USB_DR_MODE_PERIPHERAL : USB_DR_MODE_HOST;
 
        return NOTIFY_DONE;
@@ -148,7 +148,7 @@ static int dwc3_qcom_host_notifier(struct notifier_block *nb,
        struct dwc3_qcom *qcom = container_of(nb, struct dwc3_qcom, host_nb);
 
        /* disable vbus override in host mode */
-       dwc3_qcom_vbus_overrride_enable(qcom, !event);
+       dwc3_qcom_vbus_override_enable(qcom, !event);
        qcom->mode = event ? USB_DR_MODE_HOST : USB_DR_MODE_PERIPHERAL;
 
        return NOTIFY_DONE;
@@ -645,6 +645,7 @@ static int dwc3_qcom_of_register_core(struct platform_device *pdev)
        struct dwc3_qcom        *qcom = platform_get_drvdata(pdev);
        struct device_node      *np = pdev->dev.of_node, *dwc3_np;
        struct device           *dev = &pdev->dev;
+       struct property         *prop;
        int                     ret;
 
        dwc3_np = of_get_compatible_child(np, "snps,dwc3");
@@ -653,6 +654,20 @@ static int dwc3_qcom_of_register_core(struct platform_device *pdev)
                return -ENODEV;
        }
 
+       prop = devm_kzalloc(dev, sizeof(*prop), GFP_KERNEL);
+       if (!prop) {
+               ret = -ENOMEM;
+               dev_err(dev, "unable to allocate memory for property\n");
+               goto node_put;
+       }
+
+       prop->name = "tx-fifo-resize";
+       ret = of_add_property(dwc3_np, prop);
+       if (ret) {
+               dev_err(dev, "unable to add property\n");
+               goto node_put;
+       }
+
        ret = of_platform_populate(np, NULL, NULL, dev);
        if (ret) {
                dev_err(dev, "failed to register dwc3 core - %d\n", ret);
@@ -811,7 +826,7 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
 
        /* enable vbus override for device mode */
        if (qcom->mode == USB_DR_MODE_PERIPHERAL)
-               dwc3_qcom_vbus_overrride_enable(qcom, true);
+               dwc3_qcom_vbus_override_enable(qcom, true);
 
        /* register extcon to override sw_vbus on Vbus change later */
        ret = dwc3_qcom_register_extcon(qcom);
index 2f9e45e..6587394 100644 (file)
@@ -621,6 +621,8 @@ static int dwc3_ep0_set_config(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl)
                return -EINVAL;
 
        case USB_STATE_ADDRESS:
+               dwc3_gadget_clear_tx_fifos(dwc);
+
                ret = dwc3_ep0_delegate_req(dwc, ctrl);
                /* if the cfg matches and the cfg is non zero */
                if (cfg && (!ret || (ret == USB_GADGET_DELAYED_STATUS))) {
index 45f2bc0..fb5a09f 100644 (file)
@@ -631,6 +631,187 @@ static int dwc3_gadget_set_ep_config(struct dwc3_ep *dep, unsigned int action)
 static void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force,
                bool interrupt);
 
+/**
+ * dwc3_gadget_calc_tx_fifo_size - calculates the txfifo size value
+ * @dwc: pointer to the DWC3 context
+ * @nfifos: number of fifos to calculate for
+ *
+ * Calculates the size value based on the equation below:
+ *
+ * DWC3 revision 280A and prior:
+ * fifo_size = mult * (max_packet / mdwidth) + 1;
+ *
+ * DWC3 revision 290A and onwards:
+ * fifo_size = mult * ((max_packet + mdwidth)/mdwidth + 1) + 1
+ *
+ * The max packet size is set to 1024, as the txfifo requirements mainly apply
+ * to super speed USB use cases.  However, it is safe to overestimate the fifo
+ * allocations for other scenarios, i.e. high speed USB.
+ */
+static int dwc3_gadget_calc_tx_fifo_size(struct dwc3 *dwc, int mult)
+{
+       int max_packet = 1024;
+       int fifo_size;
+       int mdwidth;
+
+       mdwidth = dwc3_mdwidth(dwc);
+
+       /* MDWIDTH is represented in bits, we need it in bytes */
+       mdwidth >>= 3;
+
+       if (DWC3_VER_IS_PRIOR(DWC3, 290A))
+               fifo_size = mult * (max_packet / mdwidth) + 1;
+       else
+               fifo_size = mult * ((max_packet + mdwidth) / mdwidth) + 1;
+       return fifo_size;
+}
+
+/**
+ * dwc3_gadget_clear_tx_fifo_size - Clears txfifo allocation
+ * @dwc: pointer to the DWC3 context
+ *
+ * Iterates through all the endpoint registers and clears the previous txfifo
+ * allocations.
+ */
+void dwc3_gadget_clear_tx_fifos(struct dwc3 *dwc)
+{
+       struct dwc3_ep *dep;
+       int fifo_depth;
+       int size;
+       int num;
+
+       if (!dwc->do_fifo_resize)
+               return;
+
+       /* Read ep0IN related TXFIFO size */
+       dep = dwc->eps[1];
+       size = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(0));
+       if (DWC3_IP_IS(DWC3))
+               fifo_depth = DWC3_GTXFIFOSIZ_TXFDEP(size);
+       else
+               fifo_depth = DWC31_GTXFIFOSIZ_TXFDEP(size);
+
+       dwc->last_fifo_depth = fifo_depth;
+       /* Clear existing TXFIFO for all IN eps except ep0 */
+       for (num = 3; num < min_t(int, dwc->num_eps, DWC3_ENDPOINTS_NUM);
+            num += 2) {
+               dep = dwc->eps[num];
+               /* Don't change TXFRAMNUM on usb31 version */
+               size = DWC3_IP_IS(DWC3) ? 0 :
+                       dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(num >> 1)) &
+                                  DWC31_GTXFIFOSIZ_TXFRAMNUM;
+
+               dwc3_writel(dwc->regs, DWC3_GTXFIFOSIZ(num >> 1), size);
+       }
+       dwc->num_ep_resized = 0;
+}
+
+/*
+ * dwc3_gadget_resize_tx_fifos - reallocate fifo spaces for current use-case
+ * @dwc: pointer to our context structure
+ *
+ * This function will a best effort FIFO allocation in order
+ * to improve FIFO usage and throughput, while still allowing
+ * us to enable as many endpoints as possible.
+ *
+ * Keep in mind that this operation will be highly dependent
+ * on the configured size for RAM1 - which contains TxFifo -,
+ * the amount of endpoints enabled on coreConsultant tool, and
+ * the width of the Master Bus.
+ *
+ * In general, FIFO depths are represented with the following equation:
+ *
+ * fifo_size = mult * ((max_packet + mdwidth)/mdwidth + 1) + 1
+ *
+ * In conjunction with dwc3_gadget_check_config(), this resizing logic will
+ * ensure that all endpoints will have enough internal memory for one max
+ * packet per endpoint.
+ */
+static int dwc3_gadget_resize_tx_fifos(struct dwc3_ep *dep)
+{
+       struct dwc3 *dwc = dep->dwc;
+       int fifo_0_start;
+       int ram1_depth;
+       int fifo_size;
+       int min_depth;
+       int num_in_ep;
+       int remaining;
+       int num_fifos = 1;
+       int fifo;
+       int tmp;
+
+       if (!dwc->do_fifo_resize)
+               return 0;
+
+       /* resize IN endpoints except ep0 */
+       if (!usb_endpoint_dir_in(dep->endpoint.desc) || dep->number <= 1)
+               return 0;
+
+       ram1_depth = DWC3_RAM1_DEPTH(dwc->hwparams.hwparams7);
+
+       if ((dep->endpoint.maxburst > 1 &&
+            usb_endpoint_xfer_bulk(dep->endpoint.desc)) ||
+           usb_endpoint_xfer_isoc(dep->endpoint.desc))
+               num_fifos = 3;
+
+       if (dep->endpoint.maxburst > 6 &&
+           usb_endpoint_xfer_bulk(dep->endpoint.desc) && DWC3_IP_IS(DWC31))
+               num_fifos = dwc->tx_fifo_resize_max_num;
+
+       /* FIFO size for a single buffer */
+       fifo = dwc3_gadget_calc_tx_fifo_size(dwc, 1);
+
+       /* Calculate the number of remaining EPs w/o any FIFO */
+       num_in_ep = dwc->max_cfg_eps;
+       num_in_ep -= dwc->num_ep_resized;
+
+       /* Reserve at least one FIFO for the number of IN EPs */
+       min_depth = num_in_ep * (fifo + 1);
+       remaining = ram1_depth - min_depth - dwc->last_fifo_depth;
+       remaining = max_t(int, 0, remaining);
+       /*
+        * We've already reserved 1 FIFO per EP, so check what we can fit in
+        * addition to it.  If there is not enough remaining space, allocate
+        * all the remaining space to the EP.
+        */
+       fifo_size = (num_fifos - 1) * fifo;
+       if (remaining < fifo_size)
+               fifo_size = remaining;
+
+       fifo_size += fifo;
+       /* Last increment according to the TX FIFO size equation */
+       fifo_size++;
+
+       /* Check if TXFIFOs start at non-zero addr */
+       tmp = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(0));
+       fifo_0_start = DWC3_GTXFIFOSIZ_TXFSTADDR(tmp);
+
+       fifo_size |= (fifo_0_start + (dwc->last_fifo_depth << 16));
+       if (DWC3_IP_IS(DWC3))
+               dwc->last_fifo_depth += DWC3_GTXFIFOSIZ_TXFDEP(fifo_size);
+       else
+               dwc->last_fifo_depth += DWC31_GTXFIFOSIZ_TXFDEP(fifo_size);
+
+       /* Check fifo size allocation doesn't exceed available RAM size. */
+       if (dwc->last_fifo_depth >= ram1_depth) {
+               dev_err(dwc->dev, "Fifosize(%d) > RAM size(%d) %s depth:%d\n",
+                       dwc->last_fifo_depth, ram1_depth,
+                       dep->endpoint.name, fifo_size);
+               if (DWC3_IP_IS(DWC3))
+                       fifo_size = DWC3_GTXFIFOSIZ_TXFDEP(fifo_size);
+               else
+                       fifo_size = DWC31_GTXFIFOSIZ_TXFDEP(fifo_size);
+
+               dwc->last_fifo_depth -= fifo_size;
+               return -ENOMEM;
+       }
+
+       dwc3_writel(dwc->regs, DWC3_GTXFIFOSIZ(dep->number >> 1), fifo_size);
+       dwc->num_ep_resized++;
+
+       return 0;
+}
+
 /**
  * __dwc3_gadget_ep_enable - initializes a hw endpoint
  * @dep: endpoint to be initialized
@@ -648,6 +829,10 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, unsigned int action)
        int                     ret;
 
        if (!(dep->flags & DWC3_EP_ENABLED)) {
+               ret = dwc3_gadget_resize_tx_fifos(dep);
+               if (ret)
+                       return ret;
+
                ret = dwc3_gadget_start_config(dep);
                if (ret)
                        return ret;
@@ -2498,6 +2683,7 @@ static int dwc3_gadget_stop(struct usb_gadget *g)
 
        spin_lock_irqsave(&dwc->lock, flags);
        dwc->gadget_driver      = NULL;
+       dwc->max_cfg_eps = 0;
        spin_unlock_irqrestore(&dwc->lock, flags);
 
        free_irq(dwc->irq_gadget, dwc->ev_buf);
@@ -2585,6 +2771,51 @@ static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned int mA)
        return ret;
 }
 
+/**
+ * dwc3_gadget_check_config - ensure dwc3 can support the USB configuration
+ * @g: pointer to the USB gadget
+ *
+ * Used to record the maximum number of endpoints being used in a USB composite
+ * device. (across all configurations)  This is to be used in the calculation
+ * of the TXFIFO sizes when resizing internal memory for individual endpoints.
+ * It will help ensured that the resizing logic reserves enough space for at
+ * least one max packet.
+ */
+static int dwc3_gadget_check_config(struct usb_gadget *g)
+{
+       struct dwc3 *dwc = gadget_to_dwc(g);
+       struct usb_ep *ep;
+       int fifo_size = 0;
+       int ram1_depth;
+       int ep_num = 0;
+
+       if (!dwc->do_fifo_resize)
+               return 0;
+
+       list_for_each_entry(ep, &g->ep_list, ep_list) {
+               /* Only interested in the IN endpoints */
+               if (ep->claimed && (ep->address & USB_DIR_IN))
+                       ep_num++;
+       }
+
+       if (ep_num <= dwc->max_cfg_eps)
+               return 0;
+
+       /* Update the max number of eps in the composition */
+       dwc->max_cfg_eps = ep_num;
+
+       fifo_size = dwc3_gadget_calc_tx_fifo_size(dwc, dwc->max_cfg_eps);
+       /* Based on the equation, increment by one for every ep */
+       fifo_size += dwc->max_cfg_eps;
+
+       /* Check if we can fit a single fifo per endpoint */
+       ram1_depth = DWC3_RAM1_DEPTH(dwc->hwparams.hwparams7);
+       if (fifo_size > ram1_depth)
+               return -ENOMEM;
+
+       return 0;
+}
+
 static void dwc3_gadget_async_callbacks(struct usb_gadget *g, bool enable)
 {
        struct dwc3             *dwc = gadget_to_dwc(g);
@@ -2606,6 +2837,7 @@ static const struct usb_gadget_ops dwc3_gadget_ops = {
        .udc_set_ssp_rate       = dwc3_gadget_set_ssp_rate,
        .get_config_params      = dwc3_gadget_config_params,
        .vbus_draw              = dwc3_gadget_vbus_draw,
+       .check_config           = dwc3_gadget_check_config,
        .udc_async_callbacks    = dwc3_gadget_async_callbacks,
 };
 
index 72a9797..504c1cb 100644 (file)
@@ -482,7 +482,7 @@ static u8 encode_bMaxPower(enum usb_device_speed speed,
 {
        unsigned val;
 
-       if (c->MaxPower)
+       if (c->MaxPower || (c->bmAttributes & USB_CONFIG_ATT_SELFPOWER))
                val = c->MaxPower;
        else
                val = CONFIG_USB_GADGET_VBUS_DRAW;
@@ -936,7 +936,11 @@ static int set_config(struct usb_composite_dev *cdev,
        }
 
        /* when we return, be sure our power usage is valid */
-       power = c->MaxPower ? c->MaxPower : CONFIG_USB_GADGET_VBUS_DRAW;
+       if (c->MaxPower || (c->bmAttributes & USB_CONFIG_ATT_SELFPOWER))
+               power = c->MaxPower;
+       else
+               power = CONFIG_USB_GADGET_VBUS_DRAW;
+
        if (gadget->speed < USB_SPEED_SUPER)
                power = min(power, 500U);
        else
index 15a607c..f4c7c82 100644 (file)
@@ -1404,6 +1404,10 @@ static int configfs_composite_bind(struct usb_gadget *gadget,
                                goto err_purge_funcs;
                        }
                }
+               ret = usb_gadget_check_config(cdev->gadget);
+               if (ret)
+                       goto err_purge_funcs;
+
                usb_ep_autoconfig_reset(cdev->gadget);
        }
        if (cdev->use_os_string) {
index 8551272..dc8f078 100644 (file)
@@ -72,9 +72,7 @@ struct f_ncm {
        struct sk_buff                  *skb_tx_data;
        struct sk_buff                  *skb_tx_ndp;
        u16                             ndp_dgram_count;
-       bool                            timer_force_tx;
        struct hrtimer                  task_timer;
-       bool                            timer_stopping;
 };
 
 static inline struct f_ncm *func_to_ncm(struct usb_function *f)
@@ -890,7 +888,6 @@ static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
 
                if (ncm->port.in_ep->enabled) {
                        DBG(cdev, "reset ncm\n");
-                       ncm->timer_stopping = true;
                        ncm->netdev = NULL;
                        gether_disconnect(&ncm->port);
                        ncm_reset_values(ncm);
@@ -928,7 +925,6 @@ static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
                        if (IS_ERR(net))
                                return PTR_ERR(net);
                        ncm->netdev = net;
-                       ncm->timer_stopping = false;
                }
 
                spin_lock(&ncm->lock);
@@ -1017,22 +1013,20 @@ static struct sk_buff *ncm_wrap_ntb(struct gether *port,
 {
        struct f_ncm    *ncm = func_to_ncm(&port->func);
        struct sk_buff  *skb2 = NULL;
-       int             ncb_len = 0;
-       __le16          *ntb_data;
-       __le16          *ntb_ndp;
-       int             dgram_pad;
-
-       unsigned        max_size = ncm->port.fixed_in_len;
-       const struct ndp_parser_opts *opts = ncm->parser_opts;
-       const int ndp_align = le16_to_cpu(ntb_parameters.wNdpInAlignment);
-       const int div = le16_to_cpu(ntb_parameters.wNdpInDivisor);
-       const int rem = le16_to_cpu(ntb_parameters.wNdpInPayloadRemainder);
-       const int dgram_idx_len = 2 * 2 * opts->dgram_item_len;
-
-       if (!skb && !ncm->skb_tx_data)
-               return NULL;
 
        if (skb) {
+               int             ncb_len = 0;
+               __le16          *ntb_data;
+               __le16          *ntb_ndp;
+               int             dgram_pad;
+
+               unsigned        max_size = ncm->port.fixed_in_len;
+               const struct ndp_parser_opts *opts = ncm->parser_opts;
+               const int ndp_align = le16_to_cpu(ntb_parameters.wNdpInAlignment);
+               const int div = le16_to_cpu(ntb_parameters.wNdpInDivisor);
+               const int rem = le16_to_cpu(ntb_parameters.wNdpInPayloadRemainder);
+               const int dgram_idx_len = 2 * 2 * opts->dgram_item_len;
+
                /* Add the CRC if required up front */
                if (ncm->is_crc) {
                        uint32_t        crc;
@@ -1126,8 +1120,11 @@ static struct sk_buff *ncm_wrap_ntb(struct gether *port,
                dev_consume_skb_any(skb);
                skb = NULL;
 
-       } else if (ncm->skb_tx_data && ncm->timer_force_tx) {
-               /* If the tx was requested because of a timeout then send */
+       } else if (ncm->skb_tx_data) {
+               /* If we get here ncm_wrap_ntb() was called with NULL skb,
+                * because eth_start_xmit() was called with NULL skb by
+                * ncm_tx_timeout() - hence, this is our signal to flush/send.
+                */
                skb2 = package_for_tx(ncm);
                if (!skb2)
                        goto err;
@@ -1155,20 +1152,18 @@ err:
 static enum hrtimer_restart ncm_tx_timeout(struct hrtimer *data)
 {
        struct f_ncm *ncm = container_of(data, struct f_ncm, task_timer);
+       struct net_device *netdev = READ_ONCE(ncm->netdev);
 
-       /* Only send if data is available. */
-       if (!ncm->timer_stopping && ncm->skb_tx_data) {
-               ncm->timer_force_tx = true;
-
+       if (netdev) {
                /* XXX This allowance of a NULL skb argument to ndo_start_xmit
                 * XXX is not sane.  The gadget layer should be redesigned so
                 * XXX that the dev->wrap() invocations to build SKBs is transparent
                 * XXX and performed in some way outside of the ndo_start_xmit
                 * XXX interface.
+                *
+                * This will call directly into u_ether's eth_start_xmit()
                 */
-               ncm->netdev->netdev_ops->ndo_start_xmit(NULL, ncm->netdev);
-
-               ncm->timer_force_tx = false;
+               netdev->netdev_ops->ndo_start_xmit(NULL, netdev);
        }
        return HRTIMER_NORESTART;
 }
@@ -1357,7 +1352,6 @@ static void ncm_disable(struct usb_function *f)
        DBG(cdev, "ncm deactivated\n");
 
        if (ncm->port.in_ep->enabled) {
-               ncm->timer_stopping = true;
                ncm->netdev = NULL;
                gether_disconnect(&ncm->port);
        }
index d047075..3b3db1a 100644 (file)
 /* UAC1 spec: 3.7.2.3 Audio Channel Cluster Format */
 #define UAC1_CHANNEL_MASK 0x0FFF
 
+#define USB_OUT_FU_ID  (out_feature_unit_desc->bUnitID)
+#define USB_IN_FU_ID   (in_feature_unit_desc->bUnitID)
+
 #define EPIN_EN(_opts) ((_opts)->p_chmask != 0)
 #define EPOUT_EN(_opts) ((_opts)->c_chmask != 0)
+#define FUIN_EN(_opts) ((_opts)->p_mute_present \
+                       || (_opts)->p_volume_present)
+#define FUOUT_EN(_opts) ((_opts)->c_mute_present \
+                       || (_opts)->c_volume_present)
 
 struct f_uac1 {
        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_uac1 *func_to_uac1(struct usb_function *f)
@@ -58,7 +71,7 @@ static inline struct f_uac1_opts *g_audio_to_uac1_opts(struct g_audio *audio)
 static struct usb_interface_descriptor ac_interface_desc = {
        .bLength =              USB_DT_INTERFACE_SIZE,
        .bDescriptorType =      USB_DT_INTERFACE,
-       .bNumEndpoints =        0,
+       /* .bNumEndpoints =     DYNAMIC */
        .bInterfaceClass =      USB_CLASS_AUDIO,
        .bInterfaceSubClass =   USB_SUBCLASS_AUDIOCONTROL,
 };
@@ -106,6 +119,19 @@ static struct uac1_output_terminal_descriptor usb_in_ot_desc = {
        /* .bSourceID =         DYNAMIC */
 };
 
+static struct uac_feature_unit_descriptor *in_feature_unit_desc;
+static struct uac_feature_unit_descriptor *out_feature_unit_desc;
+
+/* AC IN Interrupt Endpoint */
+static struct usb_endpoint_descriptor ac_int_ep_desc = {
+       .bLength = USB_DT_ENDPOINT_SIZE,
+       .bDescriptorType = USB_DT_ENDPOINT,
+       .bEndpointAddress = USB_DIR_IN,
+       .bmAttributes = USB_ENDPOINT_XFER_INT,
+       .wMaxPacketSize = cpu_to_le16(2),
+       .bInterval = 4,
+};
+
 /* B.4.1  Standard AS Interface Descriptor */
 static struct usb_interface_descriptor as_out_interface_alt_0_desc = {
        .bLength =              USB_DT_INTERFACE_SIZE,
@@ -232,8 +258,13 @@ static struct usb_descriptor_header *f_audio_desc[] = {
 
        (struct usb_descriptor_header *)&usb_out_it_desc,
        (struct usb_descriptor_header *)&io_out_ot_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 *)&ac_int_ep_desc,
 
        (struct usb_descriptor_header *)&as_out_interface_alt_0_desc,
        (struct usb_descriptor_header *)&as_out_interface_alt_1_desc,
@@ -263,6 +294,8 @@ enum {
        STR_IO_IN_IT,
        STR_IO_IN_IT_CH_NAMES,
        STR_USB_IN_OT,
+       STR_FU_IN,
+       STR_FU_OUT,
        STR_AS_OUT_IF_ALT0,
        STR_AS_OUT_IF_ALT1,
        STR_AS_IN_IF_ALT0,
@@ -277,6 +310,8 @@ static struct usb_string strings_uac1[] = {
        [STR_IO_IN_IT].s = "Capture Input terminal",
        [STR_IO_IN_IT_CH_NAMES].s = "Capture Channels",
        [STR_USB_IN_OT].s = "Capture Output terminal",
+       [STR_FU_IN].s = "Capture Volume",
+       [STR_FU_OUT].s = "Playback Volume",
        [STR_AS_OUT_IF_ALT0].s = "Playback Inactive",
        [STR_AS_OUT_IF_ALT1].s = "Playback Active",
        [STR_AS_IN_IF_ALT0].s = "Capture Inactive",
@@ -298,6 +333,376 @@ static struct usb_gadget_strings *uac1_strings[] = {
  * This function is an ALSA sound card following USB Audio Class Spec 1.0.
  */
 
+static void audio_notify_complete(struct usb_ep *_ep, struct usb_request *req)
+{
+       struct g_audio *audio = req->context;
+       struct f_uac1 *uac1 = func_to_uac1(&audio->func);
+
+       atomic_dec(&uac1->int_count);
+       kfree(req->buf);
+       usb_ep_free_request(_ep, req);
+}
+
+static int audio_notify(struct g_audio *audio, int unit_id, int cs)
+{
+       struct f_uac1 *uac1 = func_to_uac1(&audio->func);
+       struct usb_request *req;
+       struct uac1_status_word *msg;
+       int ret;
+
+       if (!uac1->int_ep->enabled)
+               return 0;
+
+       if (atomic_inc_return(&uac1->int_count) > UAC1_DEF_INT_REQ_NUM) {
+               atomic_dec(&uac1->int_count);
+               return 0;
+       }
+
+       req = usb_ep_alloc_request(uac1->int_ep, GFP_ATOMIC);
+       if (req == NULL) {
+               ret = -ENOMEM;
+               goto err_dec_int_count;
+       }
+
+       msg = kmalloc(sizeof(*msg), GFP_ATOMIC);
+       if (msg == NULL) {
+               ret = -ENOMEM;
+               goto err_free_request;
+       }
+
+       msg->bStatusType = UAC1_STATUS_TYPE_IRQ_PENDING
+                               | UAC1_STATUS_TYPE_ORIG_AUDIO_CONTROL_IF;
+       msg->bOriginator = unit_id;
+
+       req->length = sizeof(*msg);
+       req->buf = msg;
+       req->context = audio;
+       req->complete = audio_notify_complete;
+
+       ret = usb_ep_queue(uac1->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(uac1->int_ep, req);
+err_dec_int_count:
+       atomic_dec(&uac1->int_count);
+
+       return ret;
+}
+
+static int
+in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+       struct usb_request *req = fn->config->cdev->req;
+       struct g_audio *audio = func_to_g_audio(fn);
+       struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio);
+       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;
+       int value = -EOPNOTSUPP;
+
+       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) {
+                       unsigned int mute;
+
+                       u_audio_get_mute(audio, is_playback, &mute);
+
+                       *(u8 *)req->buf = mute;
+                       value = min_t(unsigned int, w_length, 1);
+               } else if (control_selector == UAC_FU_VOLUME) {
+                       __le16 c;
+                       s16 volume;
+
+                       u_audio_get_volume(audio, is_playback, &volume);
+
+                       c = cpu_to_le16(volume);
+
+                       value = min_t(unsigned int, w_length, sizeof(c));
+                       memcpy(req->buf, &c, value);
+               } else {
+                       dev_err(&audio->gadget->dev,
+                               "%s:%d control_selector=%d TODO!\n",
+                               __func__, __LINE__, control_selector);
+               }
+       } else {
+               dev_err(&audio->gadget->dev,
+                       "%s:%d entity_id=%d control_selector=%d TODO!\n",
+                       __func__, __LINE__, entity_id, control_selector);
+       }
+
+       return value;
+}
+
+static int
+in_rq_min(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+       struct usb_request *req = fn->config->cdev->req;
+       struct g_audio *audio = func_to_g_audio(fn);
+       struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio);
+       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;
+       int value = -EOPNOTSUPP;
+
+       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) {
+                       __le16 r;
+                       s16 min_db;
+
+                       if (is_playback)
+                               min_db = opts->p_volume_min;
+                       else
+                               min_db = opts->c_volume_min;
+
+                       r = cpu_to_le16(min_db);
+
+                       value = min_t(unsigned int, w_length, sizeof(r));
+                       memcpy(req->buf, &r, value);
+               } else {
+                       dev_err(&audio->gadget->dev,
+                               "%s:%d control_selector=%d TODO!\n",
+                               __func__, __LINE__, control_selector);
+               }
+       } else {
+               dev_err(&audio->gadget->dev,
+                       "%s:%d entity_id=%d control_selector=%d TODO!\n",
+                       __func__, __LINE__, entity_id, control_selector);
+       }
+
+       return value;
+}
+
+static int
+in_rq_max(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+       struct usb_request *req = fn->config->cdev->req;
+       struct g_audio *audio = func_to_g_audio(fn);
+       struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio);
+       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;
+       int value = -EOPNOTSUPP;
+
+       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) {
+                       __le16 r;
+                       s16 max_db;
+
+                       if (is_playback)
+                               max_db = opts->p_volume_max;
+                       else
+                               max_db = opts->c_volume_max;
+
+                       r = cpu_to_le16(max_db);
+
+                       value = min_t(unsigned int, w_length, sizeof(r));
+                       memcpy(req->buf, &r, value);
+               } else {
+                       dev_err(&audio->gadget->dev,
+                               "%s:%d control_selector=%d TODO!\n",
+                               __func__, __LINE__, control_selector);
+               }
+       } else {
+               dev_err(&audio->gadget->dev,
+                       "%s:%d entity_id=%d control_selector=%d TODO!\n",
+                       __func__, __LINE__, entity_id, control_selector);
+       }
+
+       return value;
+}
+
+static int
+in_rq_res(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+       struct usb_request *req = fn->config->cdev->req;
+       struct g_audio *audio = func_to_g_audio(fn);
+       struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio);
+       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;
+       int value = -EOPNOTSUPP;
+
+       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) {
+                       __le16 r;
+                       s16 res_db;
+
+                       if (is_playback)
+                               res_db = opts->p_volume_res;
+                       else
+                               res_db = opts->c_volume_res;
+
+                       r = cpu_to_le16(res_db);
+
+                       value = min_t(unsigned int, w_length, sizeof(r));
+                       memcpy(req->buf, &r, value);
+               } else {
+                       dev_err(&audio->gadget->dev,
+                               "%s:%d control_selector=%d TODO!\n",
+                               __func__, __LINE__, control_selector);
+               }
+       } else {
+               dev_err(&audio->gadget->dev,
+                       "%s:%d entity_id=%d control_selector=%d TODO!\n",
+                       __func__, __LINE__, entity_id, control_selector);
+       }
+
+       return value;
+}
+
+static void
+out_rq_cur_complete(struct usb_ep *ep, struct usb_request *req)
+{
+       struct g_audio *audio = req->context;
+       struct usb_composite_dev *cdev = audio->func.config->cdev;
+       struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio);
+       struct f_uac1 *uac1 = func_to_uac1(&audio->func);
+       struct usb_ctrlrequest *cr = &uac1->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(audio, is_playback, mute);
+
+                       return;
+               } else if (control_selector == UAC_FU_VOLUME) {
+                       __le16 *c = req->buf;
+                       s16 volume;
+
+                       volume = le16_to_cpu(*c);
+                       u_audio_set_volume(audio, is_playback, volume);
+
+                       return;
+               } else {
+                       dev_err(&audio->gadget->dev,
+                               "%s:%d control_selector=%d TODO!\n",
+                               __func__, __LINE__, control_selector);
+                       usb_ep_set_halt(ep);
+               }
+       } else {
+               dev_err(&audio->gadget->dev,
+                       "%s:%d entity_id=%d control_selector=%d TODO!\n",
+                       __func__, __LINE__, entity_id, 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 *audio = func_to_g_audio(fn);
+       struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio);
+       struct f_uac1 *uac1 = func_to_uac1(&audio->func);
+       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 ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) ||
+                       (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) {
+               memcpy(&uac1->setup_cr, cr, sizeof(*cr));
+               req->context = audio;
+               req->complete = out_rq_cur_complete;
+
+               return w_length;
+       } else {
+               dev_err(&audio->gadget->dev,
+                       "%s:%d entity_id=%d control_selector=%d TODO!\n",
+                       __func__, __LINE__, entity_id, control_selector);
+       }
+       return -EOPNOTSUPP;
+}
+
+static int ac_rq_in(struct usb_function *f,
+               const struct usb_ctrlrequest *ctrl)
+{
+       struct usb_composite_dev *cdev = f->config->cdev;
+       int value = -EOPNOTSUPP;
+       u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF);
+       u16 len = le16_to_cpu(ctrl->wLength);
+       u16 w_value = le16_to_cpu(ctrl->wValue);
+
+       DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
+                       ctrl->bRequest, w_value, len, ep);
+
+       switch (ctrl->bRequest) {
+       case UAC_GET_CUR:
+               return in_rq_cur(f, ctrl);
+       case UAC_GET_MIN:
+               return in_rq_min(f, ctrl);
+       case UAC_GET_MAX:
+               return in_rq_max(f, ctrl);
+       case UAC_GET_RES:
+               return in_rq_res(f, ctrl);
+       case UAC_GET_MEM:
+               break;
+       case UAC_GET_STAT:
+               value = len;
+               break;
+       default:
+               break;
+       }
+
+       return value;
+}
+
 static int audio_set_endpoint_req(struct usb_function *f,
                const struct usb_ctrlrequest *ctrl)
 {
@@ -383,7 +788,13 @@ f_audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
        case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT:
                value = audio_get_endpoint_req(f, ctrl);
                break;
-
+       case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE:
+               if (ctrl->bRequest == UAC_SET_CUR)
+                       value = out_rq_cur(f, ctrl);
+               break;
+       case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE:
+               value = ac_rq_in(f, ctrl);
+               break;
        default:
                ERROR(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n",
                        ctrl->bRequestType, ctrl->bRequest,
@@ -411,6 +822,7 @@ static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
        struct usb_composite_dev *cdev = f->config->cdev;
        struct usb_gadget *gadget = cdev->gadget;
        struct device *dev = &gadget->dev;
+       struct g_audio *audio = func_to_g_audio(f);
        struct f_uac1 *uac1 = func_to_uac1(f);
        int ret = 0;
 
@@ -426,6 +838,14 @@ static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
                        dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
                        return -EINVAL;
                }
+
+               /* restart interrupt endpoint */
+               if (uac1->int_ep) {
+                       usb_ep_disable(uac1->int_ep);
+                       config_ep_by_speed(gadget, &audio->func, uac1->int_ep);
+                       usb_ep_enable(uac1->int_ep);
+               }
+
                return 0;
        }
 
@@ -481,10 +901,33 @@ static void f_audio_disable(struct usb_function *f)
 
        u_audio_stop_playback(&uac1->g_audio);
        u_audio_stop_capture(&uac1->g_audio);
+       if (uac1->int_ep)
+               usb_ep_disable(uac1->int_ep);
 }
 
 /*-------------------------------------------------------------------------*/
+static struct uac_feature_unit_descriptor *build_fu_desc(int chmask)
+{
+       struct uac_feature_unit_descriptor *fu_desc;
+       int channels = num_channels(chmask);
+       int fu_desc_size = UAC_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;
+       fu_desc->bControlSize  = 2;
 
+       /* bUnitID, bSourceID and bmaControls will be defined later */
+
+       return fu_desc;
+}
+
+/* B.3.2  Class-Specific AC Interface Descriptor */
 static struct
 uac1_ac_header_descriptor *build_ac_header_desc(struct f_uac1_opts *opts)
 {
@@ -530,9 +973,23 @@ static void setup_descriptor(struct f_uac1_opts *opts)
                io_out_ot_desc.bTerminalID = i++;
        if (EPIN_EN(opts))
                usb_in_ot_desc.bTerminalID = i++;
-
-       usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID;
-       io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID;
+       if (FUOUT_EN(opts))
+               out_feature_unit_desc->bUnitID = i++;
+       if (FUIN_EN(opts))
+               in_feature_unit_desc->bUnitID = i++;
+
+       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;
+       }
+       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_header_desc.bTerminalLink = usb_out_it_desc.bTerminalID;
        as_in_header_desc.bTerminalLink = usb_in_ot_desc.bTerminalID;
@@ -544,6 +1001,8 @@ static void setup_descriptor(struct f_uac1_opts *opts)
 
                len += sizeof(usb_in_ot_desc);
                len += sizeof(io_in_it_desc);
+               if (FUIN_EN(opts))
+                       len += in_feature_unit_desc->bLength;
                ac_header_desc->wTotalLength = cpu_to_le16(len);
        }
        if (EPOUT_EN(opts)) {
@@ -551,6 +1010,8 @@ static void setup_descriptor(struct f_uac1_opts *opts)
 
                len += sizeof(usb_out_it_desc);
                len += sizeof(io_out_ot_desc);
+               if (FUOUT_EN(opts))
+                       len += out_feature_unit_desc->bLength;
                ac_header_desc->wTotalLength = cpu_to_le16(len);
        }
 
@@ -561,13 +1022,20 @@ static void setup_descriptor(struct f_uac1_opts *opts)
        if (EPOUT_EN(opts)) {
                f_audio_desc[i++] = USBDHDR(&usb_out_it_desc);
                f_audio_desc[i++] = USBDHDR(&io_out_ot_desc);
+               if (FUOUT_EN(opts))
+                       f_audio_desc[i++] = USBDHDR(out_feature_unit_desc);
        }
 
        if (EPIN_EN(opts)) {
                f_audio_desc[i++] = USBDHDR(&io_in_it_desc);
                f_audio_desc[i++] = USBDHDR(&usb_in_ot_desc);
+               if (FUIN_EN(opts))
+                       f_audio_desc[i++] = USBDHDR(in_feature_unit_desc);
        }
 
+       if (FUOUT_EN(opts) || FUIN_EN(opts))
+               f_audio_desc[i++] = USBDHDR(&ac_int_ep_desc);
+
        if (EPOUT_EN(opts)) {
                f_audio_desc[i++] = USBDHDR(&as_out_interface_alt_0_desc);
                f_audio_desc[i++] = USBDHDR(&as_out_interface_alt_1_desc);
@@ -614,6 +1082,28 @@ static int f_audio_validate_opts(struct g_audio *audio, struct device *dev)
                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;
 }
 
@@ -647,6 +1137,21 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
        if (!ac_header_desc)
                return -ENOMEM;
 
+       if (FUOUT_EN(audio_opts)) {
+               out_feature_unit_desc = build_fu_desc(audio_opts->c_chmask);
+               if (!out_feature_unit_desc) {
+                       status = -ENOMEM;
+                       goto fail;
+               }
+       }
+       if (FUIN_EN(audio_opts)) {
+               in_feature_unit_desc = build_fu_desc(audio_opts->p_chmask);
+               if (!in_feature_unit_desc) {
+                       status = -ENOMEM;
+                       goto err_free_fu;
+               }
+       }
+
        ac_interface_desc.iInterface = us[STR_AC_IF].id;
        usb_out_it_desc.iTerminal = us[STR_USB_OUT_IT].id;
        usb_out_it_desc.iChannelNames = us[STR_USB_OUT_IT_CH_NAMES].id;
@@ -659,6 +1164,21 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
        as_in_interface_alt_0_desc.iInterface = us[STR_AS_IN_IF_ALT0].id;
        as_in_interface_alt_1_desc.iInterface = us[STR_AS_IN_IF_ALT1].id;
 
+       if (FUOUT_EN(audio_opts)) {
+               u8 *i_feature;
+
+               i_feature = (u8 *)out_feature_unit_desc +
+                                       out_feature_unit_desc->bLength - 1;
+               *i_feature = us[STR_FU_OUT].id;
+       }
+       if (FUIN_EN(audio_opts)) {
+               u8 *i_feature;
+
+               i_feature = (u8 *)in_feature_unit_desc +
+                                       in_feature_unit_desc->bLength - 1;
+               *i_feature = us[STR_FU_IN].id;
+       }
+
        /* Set channel numbers */
        usb_out_it_desc.bNrChannels = num_channels(audio_opts->c_chmask);
        usb_out_it_desc.wChannelConfig = cpu_to_le16(audio_opts->c_chmask);
@@ -671,6 +1191,27 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
        as_in_type_i_desc.bSubframeSize = audio_opts->p_ssize;
        as_in_type_i_desc.bBitResolution = audio_opts->p_ssize * 8;
 
+       if (FUOUT_EN(audio_opts)) {
+               __le16 *bma = (__le16 *)&out_feature_unit_desc->bmaControls[0];
+               u32 control = 0;
+
+               if (audio_opts->c_mute_present)
+                       control |= UAC_FU_MUTE;
+               if (audio_opts->c_volume_present)
+                       control |= UAC_FU_VOLUME;
+               *bma = cpu_to_le16(control);
+       }
+       if (FUIN_EN(audio_opts)) {
+               __le16 *bma = (__le16 *)&in_feature_unit_desc->bmaControls[0];
+               u32 control = 0;
+
+               if (audio_opts->p_mute_present)
+                       control |= UAC_FU_MUTE;
+               if (audio_opts->p_volume_present)
+                       control |= UAC_FU_VOLUME;
+               *bma = cpu_to_le16(control);
+       }
+
        /* Set sample rates */
        rate = audio_opts->c_srate;
        sam_freq = as_out_type_i_desc.tSamFreq[0];
@@ -682,7 +1223,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
        /* allocate instance-specific interface IDs, and patch descriptors */
        status = usb_interface_id(c, f);
        if (status < 0)
-               goto fail;
+               goto err_free_fu;
        ac_interface_desc.bInterfaceNumber = status;
        uac1->ac_intf = status;
        uac1->ac_alt = 0;
@@ -692,7 +1233,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
        if (EPOUT_EN(audio_opts)) {
                status = usb_interface_id(c, f);
                if (status < 0)
-                       goto fail;
+                       goto err_free_fu;
                as_out_interface_alt_0_desc.bInterfaceNumber = status;
                as_out_interface_alt_1_desc.bInterfaceNumber = status;
                ac_header_desc->baInterfaceNr[ba_iface_id++] = status;
@@ -703,7 +1244,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
        if (EPIN_EN(audio_opts)) {
                status = usb_interface_id(c, f);
                if (status < 0)
-                       goto fail;
+                       goto err_free_fu;
                as_in_interface_alt_0_desc.bInterfaceNumber = status;
                as_in_interface_alt_1_desc.bInterfaceNumber = status;
                ac_header_desc->baInterfaceNr[ba_iface_id++] = status;
@@ -715,11 +1256,24 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
 
        status = -ENODEV;
 
+       ac_interface_desc.bNumEndpoints = 0;
+
+       /* allocate AC interrupt endpoint */
+       if (FUOUT_EN(audio_opts) || FUIN_EN(audio_opts)) {
+               ep = usb_ep_autoconfig(cdev->gadget, &ac_int_ep_desc);
+               if (!ep)
+                       goto err_free_fu;
+               uac1->int_ep = ep;
+               uac1->int_ep->desc = &ac_int_ep_desc;
+
+               ac_interface_desc.bNumEndpoints = 1;
+       }
+
        /* allocate instance-specific endpoints */
        if (EPOUT_EN(audio_opts)) {
                ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc);
                if (!ep)
-                       goto fail;
+                       goto err_free_fu;
                audio->out_ep = ep;
                audio->out_ep->desc = &as_out_ep_desc;
        }
@@ -727,7 +1281,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
        if (EPIN_EN(audio_opts)) {
                ep = usb_ep_autoconfig(cdev->gadget, &as_in_ep_desc);
                if (!ep)
-                       goto fail;
+                       goto err_free_fu;
                audio->in_ep = ep;
                audio->in_ep->desc = &as_in_ep_desc;
        }
@@ -738,17 +1292,37 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
        status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL,
                                        NULL);
        if (status)
-               goto fail;
+               goto err_free_fu;
 
        audio->out_ep_maxpsize = le16_to_cpu(as_out_ep_desc.wMaxPacketSize);
        audio->in_ep_maxpsize = le16_to_cpu(as_in_ep_desc.wMaxPacketSize);
        audio->params.c_chmask = audio_opts->c_chmask;
        audio->params.c_srate = audio_opts->c_srate;
        audio->params.c_ssize = audio_opts->c_ssize;
+       if (FUIN_EN(audio_opts)) {
+               audio->params.p_fu.id = USB_IN_FU_ID;
+               audio->params.p_fu.mute_present = audio_opts->p_mute_present;
+               audio->params.p_fu.volume_present =
+                               audio_opts->p_volume_present;
+               audio->params.p_fu.volume_min = audio_opts->p_volume_min;
+               audio->params.p_fu.volume_max = audio_opts->p_volume_max;
+               audio->params.p_fu.volume_res = audio_opts->p_volume_res;
+       }
        audio->params.p_chmask = audio_opts->p_chmask;
        audio->params.p_srate = audio_opts->p_srate;
        audio->params.p_ssize = audio_opts->p_ssize;
+       if (FUOUT_EN(audio_opts)) {
+               audio->params.c_fu.id = USB_OUT_FU_ID;
+               audio->params.c_fu.mute_present = audio_opts->c_mute_present;
+               audio->params.c_fu.volume_present =
+                               audio_opts->c_volume_present;
+               audio->params.c_fu.volume_min = audio_opts->c_volume_min;
+               audio->params.c_fu.volume_max = audio_opts->c_volume_max;
+               audio->params.c_fu.volume_res = audio_opts->c_volume_res;
+       }
        audio->params.req_number = audio_opts->req_number;
+       if (FUOUT_EN(audio_opts) || FUIN_EN(audio_opts))
+               audio->notify = audio_notify;
 
        status = g_audio_setup(audio, "UAC1_PCM", "UAC1_Gadget");
        if (status)
@@ -758,6 +1332,11 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
 
 err_card_register:
        usb_free_all_descriptors(f);
+err_free_fu:
+       kfree(out_feature_unit_desc);
+       out_feature_unit_desc = NULL;
+       kfree(in_feature_unit_desc);
+       in_feature_unit_desc = NULL;
 fail:
        kfree(ac_header_desc);
        ac_header_desc = NULL;
@@ -783,7 +1362,15 @@ static struct configfs_item_operations f_uac1_item_ops = {
        .release        = f_uac1_attr_release,
 };
 
-#define UAC1_ATTRIBUTE(name)                                           \
+#define uac1_kstrtou32                 kstrtou32
+#define uac1_kstrtos16                 kstrtos16
+#define uac1_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 UAC1_ATTRIBUTE(type, name)                                     \
 static ssize_t f_uac1_opts_##name##_show(                              \
                                          struct config_item *item,     \
                                          char *page)                   \
@@ -792,7 +1379,7 @@ static ssize_t f_uac1_opts_##name##_show(                          \
        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;                                                  \
@@ -804,7 +1391,7 @@ static ssize_t f_uac1_opts_##name##_store(                         \
 {                                                                      \
        struct f_uac1_opts *opts = to_f_uac1_opts(item);                \
        int ret;                                                        \
-       u32 num;                                                        \
+       type num;                                                       \
                                                                        \
        mutex_lock(&opts->lock);                                        \
        if (opts->refcnt) {                                             \
@@ -812,7 +1399,7 @@ static ssize_t f_uac1_opts_##name##_store(                         \
                goto end;                                               \
        }                                                               \
                                                                        \
-       ret = kstrtou32(page, 0, &num);                                 \
+       ret = uac1_kstrto##type(page, 0, &num);                         \
        if (ret)                                                        \
                goto end;                                               \
                                                                        \
@@ -826,13 +1413,25 @@ end:                                                                     \
                                                                        \
 CONFIGFS_ATTR(f_uac1_opts_, name)
 
-UAC1_ATTRIBUTE(c_chmask);
-UAC1_ATTRIBUTE(c_srate);
-UAC1_ATTRIBUTE(c_ssize);
-UAC1_ATTRIBUTE(p_chmask);
-UAC1_ATTRIBUTE(p_srate);
-UAC1_ATTRIBUTE(p_ssize);
-UAC1_ATTRIBUTE(req_number);
+UAC1_ATTRIBUTE(u32, c_chmask);
+UAC1_ATTRIBUTE(u32, c_srate);
+UAC1_ATTRIBUTE(u32, c_ssize);
+UAC1_ATTRIBUTE(u32, p_chmask);
+UAC1_ATTRIBUTE(u32, p_srate);
+UAC1_ATTRIBUTE(u32, p_ssize);
+UAC1_ATTRIBUTE(u32, req_number);
+
+UAC1_ATTRIBUTE(bool, p_mute_present);
+UAC1_ATTRIBUTE(bool, p_volume_present);
+UAC1_ATTRIBUTE(s16, p_volume_min);
+UAC1_ATTRIBUTE(s16, p_volume_max);
+UAC1_ATTRIBUTE(s16, p_volume_res);
+
+UAC1_ATTRIBUTE(bool, c_mute_present);
+UAC1_ATTRIBUTE(bool, c_volume_present);
+UAC1_ATTRIBUTE(s16, c_volume_min);
+UAC1_ATTRIBUTE(s16, c_volume_max);
+UAC1_ATTRIBUTE(s16, c_volume_res);
 
 static struct configfs_attribute *f_uac1_attrs[] = {
        &f_uac1_opts_attr_c_chmask,
@@ -842,6 +1441,19 @@ static struct configfs_attribute *f_uac1_attrs[] = {
        &f_uac1_opts_attr_p_srate,
        &f_uac1_opts_attr_p_ssize,
        &f_uac1_opts_attr_req_number,
+
+       &f_uac1_opts_attr_p_mute_present,
+       &f_uac1_opts_attr_p_volume_present,
+       &f_uac1_opts_attr_p_volume_min,
+       &f_uac1_opts_attr_p_volume_max,
+       &f_uac1_opts_attr_p_volume_res,
+
+       &f_uac1_opts_attr_c_mute_present,
+       &f_uac1_opts_attr_c_volume_present,
+       &f_uac1_opts_attr_c_volume_min,
+       &f_uac1_opts_attr_c_volume_max,
+       &f_uac1_opts_attr_c_volume_res,
+
        NULL,
 };
 
@@ -879,6 +1491,19 @@ static struct usb_function_instance *f_audio_alloc_inst(void)
        opts->p_chmask = UAC1_DEF_PCHMASK;
        opts->p_srate = UAC1_DEF_PSRATE;
        opts->p_ssize = UAC1_DEF_PSSIZE;
+
+       opts->p_mute_present = UAC1_DEF_MUTE_PRESENT;
+       opts->p_volume_present = UAC1_DEF_VOLUME_PRESENT;
+       opts->p_volume_min = UAC1_DEF_MIN_DB;
+       opts->p_volume_max = UAC1_DEF_MAX_DB;
+       opts->p_volume_res = UAC1_DEF_RES_DB;
+
+       opts->c_mute_present = UAC1_DEF_MUTE_PRESENT;
+       opts->c_volume_present = UAC1_DEF_VOLUME_PRESENT;
+       opts->c_volume_min = UAC1_DEF_MIN_DB;
+       opts->c_volume_max = UAC1_DEF_MAX_DB;
+       opts->c_volume_res = UAC1_DEF_RES_DB;
+
        opts->req_number = UAC1_DEF_REQ_NUM;
        return &opts->func_inst;
 }
@@ -903,6 +1528,11 @@ static void f_audio_unbind(struct usb_configuration *c, struct usb_function *f)
        g_audio_cleanup(audio);
        usb_free_all_descriptors(f);
 
+       kfree(out_feature_unit_desc);
+       out_feature_unit_desc = NULL;
+       kfree(in_feature_unit_desc);
+       in_feature_unit_desc = NULL;
+
        kfree(ac_header_desc);
        ac_header_desc = NULL;
 
index ae29ff2..b9edc67 100644 (file)
@@ -5,6 +5,9 @@
  * 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
@@ -34,6 +39,8 @@
 
 #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)
@@ -63,6 +82,8 @@ struct f_uac2_opts *g_audio_to_uac2_opts(struct g_audio *agdev)
        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 {
@@ -74,6 +95,8 @@ 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,
@@ -92,6 +115,8 @@ static struct usb_string strings_fn[] = {
        [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",
@@ -126,7 +151,7 @@ static struct usb_interface_descriptor std_ac_if_desc = {
        .bDescriptorType = USB_DT_INTERFACE,
 
        .bAlternateSetting = 0,
-       .bNumEndpoints = 0,
+       /* .bNumEndpoints = DYNAMIC */
        .bInterfaceClass = USB_CLASS_AUDIO,
        .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
        .bInterfaceProtocol = UAC_VERSION_2,
@@ -212,6 +237,9 @@ static struct uac2_output_terminal_descriptor io_out_ot_desc = {
        .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,
@@ -223,6 +251,36 @@ static struct uac2_ac_header_descriptor ac_hdr_desc = {
        .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,
@@ -452,10 +510,14 @@ static struct usb_descriptor_header *fs_audio_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,
 
@@ -483,10 +545,14 @@ static struct usb_descriptor_header *hs_audio_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,
 
@@ -514,10 +580,14 @@ static struct usb_descriptor_header *ss_audio_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,
 
@@ -539,6 +609,17 @@ static struct usb_descriptor_header *ss_audio_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;
 };
@@ -595,6 +676,26 @@ static int set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts,
        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)
 
@@ -607,6 +708,7 @@ static void setup_headers(struct f_uac2_opts *opts,
        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) {
@@ -614,11 +716,13 @@ static void setup_headers(struct f_uac2_opts *opts,
                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;
@@ -626,6 +730,7 @@ static void setup_headers(struct f_uac2_opts *opts,
                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;
@@ -637,13 +742,27 @@ static void setup_headers(struct f_uac2_opts *opts,
        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);
@@ -657,6 +776,7 @@ static void setup_headers(struct f_uac2_opts *opts,
                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);
@@ -684,17 +804,35 @@ static void setup_descriptor(struct f_uac2_opts *opts)
                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;
 
@@ -706,6 +844,10 @@ static void setup_descriptor(struct f_uac2_opts *opts)
 
                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++;
@@ -715,6 +857,10 @@ static void setup_descriptor(struct f_uac2_opts *opts)
 
                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++;
@@ -752,6 +898,28 @@ static int afunc_validate_opts(struct g_audio *agdev, struct device *dev)
                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;
 }
 
@@ -774,6 +942,20 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
        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;
@@ -787,6 +969,21 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
        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);
@@ -801,6 +998,26 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
        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);
@@ -808,7 +1025,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
        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;
 
@@ -820,7 +1037,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
                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;
@@ -849,7 +1066,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
                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;
@@ -857,6 +1074,17 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
                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);
@@ -904,7 +1132,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
                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,
@@ -912,7 +1141,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
                        if (!agdev->in_ep_fback) {
                                dev_err(dev, "%s:%d Error!\n",
                                        __func__, __LINE__);
-                               return -ENODEV;
+                               ret = -ENODEV;
+                               goto err_free_fu;
                        }
                }
        }
@@ -921,7 +1151,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
                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;
                }
        }
 
@@ -937,38 +1168,137 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
        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;
 }
 
@@ -977,6 +1307,7 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
 {
        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;
@@ -993,6 +1324,14 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
                        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;
        }
 
@@ -1047,6 +1386,8 @@ afunc_disable(struct usb_function *fn)
        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
@@ -1054,7 +1395,7 @@ in_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;
+       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);
@@ -1063,28 +1404,64 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
        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;
@@ -1095,38 +1472,77 @@ in_rq_range(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;
+       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;
@@ -1143,16 +1559,82 @@ ac_rq_in(struct usb_function *fn, const struct usb_ctrlrequest *cr)
                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;
 }
 
@@ -1228,7 +1710,15 @@ static struct configfs_item_operations f_uac2_item_ops = {
        .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)                    \
 {                                                                      \
@@ -1236,7 +1726,7 @@ static ssize_t f_uac2_opts_##name##_show(struct config_item *item,        \
        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;                                                  \
@@ -1247,7 +1737,7 @@ static ssize_t f_uac2_opts_##name##_store(struct config_item *item,       \
 {                                                                      \
        struct f_uac2_opts *opts = to_f_uac2_opts(item);                \
        int ret;                                                        \
-       u32 num;                                                        \
+       type num;                                                       \
                                                                        \
        mutex_lock(&opts->lock);                                        \
        if (opts->refcnt) {                                             \
@@ -1255,7 +1745,7 @@ static ssize_t f_uac2_opts_##name##_store(struct config_item *item,       \
                goto end;                                               \
        }                                                               \
                                                                        \
-       ret = kstrtou32(page, 0, &num);                                 \
+       ret = uac2_kstrto##type(page, 0, &num);                         \
        if (ret)                                                        \
                goto end;                                               \
                                                                        \
@@ -1325,15 +1815,27 @@ 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,
@@ -1345,6 +1847,19 @@ static struct configfs_attribute *f_uac2_attrs[] = {
        &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,
 };
 
@@ -1383,6 +1898,19 @@ static struct usb_function_instance *afunc_alloc_inst(void)
        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;
@@ -1409,6 +1937,11 @@ static void afunc_unbind(struct usb_configuration *c, struct usb_function *f)
        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)
@@ -1441,3 +1974,4 @@ DECLARE_USB_FUNCTION_INIT(uac2, afunc_alloc_inst, afunc_alloc);
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Yadwinder Singh");
 MODULE_AUTHOR("Jaswinder Singh");
+MODULE_AUTHOR("Ruslan Bilovol");
index 018dd09..f6b5b95 100644 (file)
  *    Jaswinder Singh (jaswinder.singh@linaro.org)
  */
 
+#include <linux/kernel.h>
 #include <linux/module.h>
 #include <sound/core.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
 #include <sound/control.h>
+#include <sound/tlv.h>
+#include <linux/usb/audio.h>
 
 #include "u_audio.h"
 
 #define PRD_SIZE_MAX   PAGE_SIZE
 #define MIN_PERIODS    4
 
+enum {
+       UAC_FBACK_CTRL,
+       UAC_MUTE_CTRL,
+       UAC_VOLUME_CTRL,
+};
+
 /* Runtime data params for one stream */
 struct uac_rtd_params {
        struct snd_uac_chip *uac; /* parent chip */
@@ -43,6 +52,17 @@ struct uac_rtd_params {
 
        struct usb_request *req_fback; /* Feedback endpoint request */
        bool fb_ep_enabled; /* if the ep is enabled */
+
+  /* Volume/Mute controls and their state */
+  int fu_id; /* Feature Unit ID */
+  struct snd_kcontrol *snd_kctl_volume;
+  struct snd_kcontrol *snd_kctl_mute;
+  s16 volume_min, volume_max, volume_res;
+  s16 volume;
+  int mute;
+
+  spinlock_t lock; /* lock for control transfers */
+
 };
 
 struct snd_uac_chip {
@@ -597,6 +617,103 @@ void u_audio_stop_playback(struct g_audio *audio_dev)
 }
 EXPORT_SYMBOL_GPL(u_audio_stop_playback);
 
+int u_audio_get_volume(struct g_audio *audio_dev, int playback, s16 *val)
+{
+       struct snd_uac_chip *uac = audio_dev->uac;
+       struct uac_rtd_params *prm;
+       unsigned long flags;
+
+       if (playback)
+               prm = &uac->p_prm;
+       else
+               prm = &uac->c_prm;
+
+       spin_lock_irqsave(&prm->lock, flags);
+       *val = prm->volume;
+       spin_unlock_irqrestore(&prm->lock, flags);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(u_audio_get_volume);
+
+int u_audio_set_volume(struct g_audio *audio_dev, int playback, s16 val)
+{
+       struct snd_uac_chip *uac = audio_dev->uac;
+       struct uac_rtd_params *prm;
+       unsigned long flags;
+       int change = 0;
+
+       if (playback)
+               prm = &uac->p_prm;
+       else
+               prm = &uac->c_prm;
+
+       spin_lock_irqsave(&prm->lock, flags);
+       val = clamp(val, prm->volume_min, prm->volume_max);
+       if (prm->volume != val) {
+               prm->volume = val;
+               change = 1;
+       }
+       spin_unlock_irqrestore(&prm->lock, flags);
+
+       if (change)
+               snd_ctl_notify(uac->card, SNDRV_CTL_EVENT_MASK_VALUE,
+                               &prm->snd_kctl_volume->id);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(u_audio_set_volume);
+
+int u_audio_get_mute(struct g_audio *audio_dev, int playback, int *val)
+{
+       struct snd_uac_chip *uac = audio_dev->uac;
+       struct uac_rtd_params *prm;
+       unsigned long flags;
+
+       if (playback)
+               prm = &uac->p_prm;
+       else
+               prm = &uac->c_prm;
+
+       spin_lock_irqsave(&prm->lock, flags);
+       *val = prm->mute;
+       spin_unlock_irqrestore(&prm->lock, flags);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(u_audio_get_mute);
+
+int u_audio_set_mute(struct g_audio *audio_dev, int playback, int val)
+{
+       struct snd_uac_chip *uac = audio_dev->uac;
+       struct uac_rtd_params *prm;
+       unsigned long flags;
+       int change = 0;
+       int mute;
+
+       if (playback)
+               prm = &uac->p_prm;
+       else
+               prm = &uac->c_prm;
+
+       mute = val ? 1 : 0;
+
+       spin_lock_irqsave(&prm->lock, flags);
+       if (prm->mute != mute) {
+               prm->mute = mute;
+               change = 1;
+       }
+       spin_unlock_irqrestore(&prm->lock, flags);
+
+       if (change)
+               snd_ctl_notify(uac->card, SNDRV_CTL_EVENT_MASK_VALUE,
+                              &prm->snd_kctl_mute->id);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(u_audio_set_mute);
+
+
 static int u_audio_pitch_info(struct snd_kcontrol *kcontrol,
                                   struct snd_ctl_elem_info *uinfo)
 {
@@ -656,14 +773,158 @@ static int u_audio_pitch_put(struct snd_kcontrol *kcontrol,
        return change;
 }
 
-static const struct snd_kcontrol_new u_audio_controls[]  = {
+static int u_audio_mute_info(struct snd_kcontrol *kcontrol,
+                                  struct snd_ctl_elem_info *uinfo)
 {
-       .iface =        SNDRV_CTL_ELEM_IFACE_PCM,
-       .name =         "Capture Pitch 1000000",
-       .info =         u_audio_pitch_info,
-       .get =          u_audio_pitch_get,
-       .put =          u_audio_pitch_put,
-},
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+       uinfo->count = 1;
+       uinfo->value.integer.min = 0;
+       uinfo->value.integer.max = 1;
+       uinfo->value.integer.step = 1;
+
+       return 0;
+}
+
+static int u_audio_mute_get(struct snd_kcontrol *kcontrol,
+                                  struct snd_ctl_elem_value *ucontrol)
+{
+       struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol);
+       unsigned long flags;
+
+       spin_lock_irqsave(&prm->lock, flags);
+       ucontrol->value.integer.value[0] = !prm->mute;
+       spin_unlock_irqrestore(&prm->lock, flags);
+
+       return 0;
+}
+
+static int u_audio_mute_put(struct snd_kcontrol *kcontrol,
+                                 struct snd_ctl_elem_value *ucontrol)
+{
+       struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol);
+       struct snd_uac_chip *uac = prm->uac;
+       struct g_audio *audio_dev = uac->audio_dev;
+       unsigned int val;
+       unsigned long flags;
+       int change = 0;
+
+       val = !ucontrol->value.integer.value[0];
+
+       spin_lock_irqsave(&prm->lock, flags);
+       if (val != prm->mute) {
+               prm->mute = val;
+               change = 1;
+       }
+       spin_unlock_irqrestore(&prm->lock, flags);
+
+       if (change && audio_dev->notify)
+               audio_dev->notify(audio_dev, prm->fu_id, UAC_FU_MUTE);
+
+       return change;
+}
+
+/*
+ * TLV callback for mixer volume controls
+ */
+static int u_audio_volume_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+                        unsigned int size, unsigned int __user *_tlv)
+{
+       struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol);
+       DECLARE_TLV_DB_MINMAX(scale, 0, 0);
+
+       if (size < sizeof(scale))
+               return -ENOMEM;
+
+       /* UAC volume resolution is 1/256 dB, TLV is 1/100 dB */
+       scale[2] = (prm->volume_min * 100) / 256;
+       scale[3] = (prm->volume_max * 100) / 256;
+       if (copy_to_user(_tlv, scale, sizeof(scale)))
+               return -EFAULT;
+
+       return 0;
+}
+
+static int u_audio_volume_info(struct snd_kcontrol *kcontrol,
+                                  struct snd_ctl_elem_info *uinfo)
+{
+       struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol);
+
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = 1;
+       uinfo->value.integer.min = 0;
+       uinfo->value.integer.max =
+               (prm->volume_max - prm->volume_min + prm->volume_res - 1)
+               / prm->volume_res;
+       uinfo->value.integer.step = 1;
+
+       return 0;
+}
+
+static int u_audio_volume_get(struct snd_kcontrol *kcontrol,
+                                  struct snd_ctl_elem_value *ucontrol)
+{
+       struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol);
+       unsigned long flags;
+
+       spin_lock_irqsave(&prm->lock, flags);
+       ucontrol->value.integer.value[0] =
+                       (prm->volume - prm->volume_min) / prm->volume_res;
+       spin_unlock_irqrestore(&prm->lock, flags);
+
+       return 0;
+}
+
+static int u_audio_volume_put(struct snd_kcontrol *kcontrol,
+                                 struct snd_ctl_elem_value *ucontrol)
+{
+       struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol);
+       struct snd_uac_chip *uac = prm->uac;
+       struct g_audio *audio_dev = uac->audio_dev;
+       unsigned int val;
+       s16 volume;
+       unsigned long flags;
+       int change = 0;
+
+       val = ucontrol->value.integer.value[0];
+
+       spin_lock_irqsave(&prm->lock, flags);
+       volume = (val * prm->volume_res) + prm->volume_min;
+       volume = clamp(volume, prm->volume_min, prm->volume_max);
+       if (volume != prm->volume) {
+               prm->volume = volume;
+               change = 1;
+       }
+       spin_unlock_irqrestore(&prm->lock, flags);
+
+       if (change && audio_dev->notify)
+               audio_dev->notify(audio_dev, prm->fu_id, UAC_FU_VOLUME);
+
+       return change;
+}
+
+
+static struct snd_kcontrol_new u_audio_controls[]  = {
+  [UAC_FBACK_CTRL] {
+    .iface =        SNDRV_CTL_ELEM_IFACE_PCM,
+    .name =         "Capture Pitch 1000000",
+    .info =         u_audio_pitch_info,
+    .get =          u_audio_pitch_get,
+    .put =          u_audio_pitch_put,
+  },
+  [UAC_MUTE_CTRL] {
+               .iface =        SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name =         "", /* will be filled later */
+               .info =         u_audio_mute_info,
+               .get =          u_audio_mute_get,
+               .put =          u_audio_mute_put,
+       },
+       [UAC_VOLUME_CTRL] {
+               .iface =        SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name =         "", /* will be filled later */
+               .info =         u_audio_volume_info,
+               .get =          u_audio_volume_get,
+               .put =          u_audio_volume_put,
+       },
 };
 
 int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
@@ -675,7 +936,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
        struct snd_kcontrol *kctl;
        struct uac_params *params;
        int p_chmask, c_chmask;
-       int err;
+       int i, err;
 
        if (!g_audio)
                return -EINVAL;
@@ -693,7 +954,8 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
        if (c_chmask) {
                struct uac_rtd_params *prm = &uac->c_prm;
 
-               uac->c_prm.uac = uac;
+    spin_lock_init(&prm->lock);
+    uac->c_prm.uac = uac;
                prm->max_psize = g_audio->out_ep_maxpsize;
 
                prm->reqs = kcalloc(params->req_number,
@@ -716,6 +978,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
        if (p_chmask) {
                struct uac_rtd_params *prm = &uac->p_prm;
 
+               spin_lock_init(&prm->lock);
                uac->p_prm.uac = uac;
                prm->max_psize = g_audio->in_ep_maxpsize;
 
@@ -760,10 +1023,18 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac_pcm_ops);
        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac_pcm_ops);
 
-       if (c_chmask && g_audio->in_ep_fback) {
+       /*
+        * Create mixer and controls
+        * Create only if it's required on USB side
+        */
+       if ((c_chmask && g_audio->in_ep_fback)
+                       || (p_chmask && params->p_fu.id)
+                       || (c_chmask && params->c_fu.id))
                strscpy(card->mixername, card_name, sizeof(card->driver));
 
-               kctl = snd_ctl_new1(&u_audio_controls[0], &uac->c_prm);
+       if (c_chmask && g_audio->in_ep_fback) {
+               kctl = snd_ctl_new1(&u_audio_controls[UAC_FBACK_CTRL],
+                                   &uac->c_prm);
                if (!kctl) {
                        err = -ENOMEM;
                        goto snd_fail;
@@ -777,6 +1048,82 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
                        goto snd_fail;
        }
 
+       for (i = 0; i <= SNDRV_PCM_STREAM_LAST; i++) {
+               struct uac_rtd_params *prm;
+               struct uac_fu_params *fu;
+               char ctrl_name[24];
+               char *direction;
+
+               if (!pcm->streams[i].substream_count)
+                       continue;
+
+               if (i == SNDRV_PCM_STREAM_PLAYBACK) {
+                       prm = &uac->p_prm;
+                       fu = &params->p_fu;
+                       direction = "Playback";
+               } else {
+                       prm = &uac->c_prm;
+                       fu = &params->c_fu;
+                       direction = "Capture";
+               }
+
+               prm->fu_id = fu->id;
+
+               if (fu->mute_present) {
+                       snprintf(ctrl_name, sizeof(ctrl_name),
+                                       "PCM %s Switch", direction);
+
+                       u_audio_controls[UAC_MUTE_CTRL].name = ctrl_name;
+
+                       kctl = snd_ctl_new1(&u_audio_controls[UAC_MUTE_CTRL],
+                                           prm);
+                       if (!kctl) {
+                               err = -ENOMEM;
+                               goto snd_fail;
+                       }
+
+                       kctl->id.device = pcm->device;
+                       kctl->id.subdevice = i;
+
+                       err = snd_ctl_add(card, kctl);
+                       if (err < 0)
+                               goto snd_fail;
+                       prm->snd_kctl_mute = kctl;
+                       prm->mute = 0;
+               }
+
+               if (fu->volume_present) {
+                       snprintf(ctrl_name, sizeof(ctrl_name),
+                                       "PCM %s Volume", direction);
+
+                       u_audio_controls[UAC_VOLUME_CTRL].name = ctrl_name;
+
+                       kctl = snd_ctl_new1(&u_audio_controls[UAC_VOLUME_CTRL],
+                                           prm);
+                       if (!kctl) {
+                               err = -ENOMEM;
+                               goto snd_fail;
+                       }
+
+                       kctl->id.device = pcm->device;
+                       kctl->id.subdevice = i;
+
+
+                       kctl->tlv.c = u_audio_volume_tlv;
+                       kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+                                       SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK;
+
+                       err = snd_ctl_add(card, kctl);
+                       if (err < 0)
+                               goto snd_fail;
+                       prm->snd_kctl_volume = kctl;
+                       prm->volume = fu->volume_max;
+                       prm->volume_max = fu->volume_max;
+                       prm->volume_min = fu->volume_min;
+                       prm->volume_res = fu->volume_res;
+               }
+       }
+
        strscpy(card->driver, card_name, sizeof(card->driver));
        strscpy(card->shortname, card_name, sizeof(card->shortname));
        sprintf(card->longname, "%s %i", card_name, card->dev->id);
index a218cdf..001a79a 100644 (file)
  */
 #define FBACK_SLOW_MAX 250
 
+/* Feature Unit parameters */
+struct uac_fu_params {
+       int id;                 /* Feature Unit ID */
+
+       bool mute_present;      /* mute control enable */
+
+       bool volume_present;    /* volume control enable */
+       s16 volume_min;         /* min volume in 1/256 dB */
+       s16 volume_max;         /* max volume in 1/256 dB */
+       s16 volume_res;         /* volume resolution in 1/256 dB */
+};
+
 struct uac_params {
        /* playback */
        int p_chmask;   /* channel mask */
        int p_srate;    /* rate in Hz */
        int p_ssize;    /* sample size */
+       struct uac_fu_params p_fu;      /* Feature Unit parameters */
 
        /* capture */
        int c_chmask;   /* channel mask */
        int c_srate;    /* rate in Hz */
        int c_ssize;    /* sample size */
+       struct uac_fu_params c_fu;      /* Feature Unit parameters */
 
        int req_number; /* number of preallocated requests */
        int fb_max;     /* upper frequency drift feedback limit per-mil */
@@ -49,6 +63,9 @@ struct g_audio {
        /* Max packet size for all out_ep possible speeds */
        unsigned int out_ep_maxpsize;
 
+       /* Notify UAC driver about control change */
+       int (*notify)(struct g_audio *g_audio, int unit_id, int cs);
+
        /* The ALSA Sound Card it represents on the USB-Client side */
        struct snd_uac_chip *uac;
 
@@ -94,4 +111,9 @@ void u_audio_stop_capture(struct g_audio *g_audio);
 int u_audio_start_playback(struct g_audio *g_audio);
 void u_audio_stop_playback(struct g_audio *g_audio);
 
+int u_audio_get_volume(struct g_audio *g_audio, int playback, s16 *val);
+int u_audio_set_volume(struct g_audio *g_audio, int playback, s16 val);
+int u_audio_get_mute(struct g_audio *g_audio, int playback, int *val);
+int u_audio_set_mute(struct g_audio *g_audio, int playback, int val);
+
 #endif /* __U_AUDIO_H */
index d1d044d..85a3f6d 100644 (file)
@@ -492,8 +492,9 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb,
        }
        spin_unlock_irqrestore(&dev->lock, flags);
 
-       if (skb && !in) {
-               dev_kfree_skb_any(skb);
+       if (!in) {
+               if (skb)
+                       dev_kfree_skb_any(skb);
                return NETDEV_TX_OK;
        }
 
index 39c0e29..589fae8 100644 (file)
 #define UAC1_DEF_PSRATE                48000
 #define UAC1_DEF_PSSIZE                2
 #define UAC1_DEF_REQ_NUM       2
+#define UAC1_DEF_INT_REQ_NUM   10
+
+#define UAC1_DEF_MUTE_PRESENT  1
+#define UAC1_DEF_VOLUME_PRESENT 1
+#define UAC1_DEF_MIN_DB                (-100*256)      /* -100 dB */
+#define UAC1_DEF_MAX_DB                0               /* 0 dB */
+#define UAC1_DEF_RES_DB                (1*256) /* 1 dB */
 
 
 struct f_uac1_opts {
@@ -28,6 +35,19 @@ struct f_uac1_opts {
        int                             p_chmask;
        int                             p_srate;
        int                             p_ssize;
+
+       bool                    p_mute_present;
+       bool                    p_volume_present;
+       s16                             p_volume_min;
+       s16                             p_volume_max;
+       s16                             p_volume_res;
+
+       bool                    c_mute_present;
+       bool                    c_volume_present;
+       s16                             c_volume_min;
+       s16                             c_volume_max;
+       s16                             c_volume_res;
+
        int                             req_number;
        unsigned                        bound:1;
 
index 179d3ef..a73b357 100644 (file)
 #define UAC2_DEF_CSRATE 64000
 #define UAC2_DEF_CSSIZE 2
 #define UAC2_DEF_CSYNC         USB_ENDPOINT_SYNC_ASYNC
+
+#define UAC2_DEF_MUTE_PRESENT  1
+#define UAC2_DEF_VOLUME_PRESENT 1
+#define UAC2_DEF_MIN_DB                (-100*256)      /* -100 dB */
+#define UAC2_DEF_MAX_DB                0               /* 0 dB */
+#define UAC2_DEF_RES_DB                (1*256)         /* 1 dB */
+
 #define UAC2_DEF_REQ_NUM 2
 #define UAC2_DEF_FB_MAX 5
+#define UAC2_DEF_INT_REQ_NUM   10
 
 struct f_uac2_opts {
        struct usb_function_instance    func_inst;
@@ -34,9 +42,22 @@ struct f_uac2_opts {
        int                             c_srate;
        int                             c_ssize;
        int                             c_sync;
+
+       bool                    p_mute_present;
+       bool                    p_volume_present;
+       s16                             p_volume_min;
+       s16                             p_volume_max;
+       s16                             p_volume_res;
+
+       bool                    c_mute_present;
+       bool                    c_volume_present;
+       s16                             c_volume_min;
+       s16                             c_volume_max;
+       s16                             c_volume_res;
+
        int                             req_number;
        int                             fb_max;
-       bool                            bound;
+       bool                    bound;
 
        struct mutex                    lock;
        int                             refcnt;
index b7f0b1e..14fdf91 100644 (file)
@@ -1003,6 +1003,25 @@ int usb_gadget_ep_match_desc(struct usb_gadget *gadget,
 }
 EXPORT_SYMBOL_GPL(usb_gadget_ep_match_desc);
 
+/**
+ * usb_gadget_check_config - checks if the UDC can support the binded
+ *     configuration
+ * @gadget: controller to check the USB configuration
+ *
+ * Ensure that a UDC is able to support the requested resources by a
+ * configuration, and that there are no resource limitations, such as
+ * internal memory allocated to all requested endpoints.
+ *
+ * Returns zero on success, else a negative errno.
+ */
+int usb_gadget_check_config(struct usb_gadget *gadget)
+{
+       if (gadget->ops->check_config)
+               return gadget->ops->check_config(gadget);
+       return 0;
+}
+EXPORT_SYMBOL_GPL(usb_gadget_check_config);
+
 /* ------------------------------------------------------------------------- */
 
 static void usb_gadget_state_work(struct work_struct *work)
index cffdc8d..8fd2724 100644 (file)
@@ -42,26 +42,25 @@ struct ehci_hcd_mv {
        int (*set_vbus)(unsigned int vbus);
 };
 
-static void ehci_clock_enable(struct ehci_hcd_mv *ehci_mv)
+static int mv_ehci_enable(struct ehci_hcd_mv *ehci_mv)
 {
-       clk_prepare_enable(ehci_mv->clk);
-}
+       int retval;
 
-static void ehci_clock_disable(struct ehci_hcd_mv *ehci_mv)
-{
-       clk_disable_unprepare(ehci_mv->clk);
-}
+       retval = clk_prepare_enable(ehci_mv->clk);
+       if (retval)
+               return retval;
 
-static int mv_ehci_enable(struct ehci_hcd_mv *ehci_mv)
-{
-       ehci_clock_enable(ehci_mv);
-       return phy_init(ehci_mv->phy);
+       retval = phy_init(ehci_mv->phy);
+       if (retval)
+               clk_disable_unprepare(ehci_mv->clk);
+
+       return retval;
 }
 
 static void mv_ehci_disable(struct ehci_hcd_mv *ehci_mv)
 {
        phy_exit(ehci_mv->phy);
-       ehci_clock_disable(ehci_mv);
+       clk_disable_unprepare(ehci_mv->clk);
 }
 
 static int mv_ehci_reset(struct usb_hcd *hcd)
index 05fb8d9..4b02ace 100644 (file)
@@ -1858,9 +1858,11 @@ static struct fotg210_qh *fotg210_qh_alloc(struct fotg210_hcd *fotg210,
        qh = kzalloc(sizeof(*qh), GFP_ATOMIC);
        if (!qh)
                goto done;
-       qh->hw = dma_pool_zalloc(fotg210->qh_pool, flags, &dma);
+       qh->hw = (struct fotg210_qh_hw *)
+               dma_pool_alloc(fotg210->qh_pool, flags, &dma);
        if (!qh->hw)
                goto fail;
+       memset(qh->hw, 0, sizeof(*qh->hw));
        qh->qh_dma = dma;
        INIT_LIST_HEAD(&qh->qtd_list);
 
@@ -2510,11 +2512,6 @@ retry_xacterr:
        return count;
 }
 
-/* high bandwidth multiplier, as encoded in highspeed endpoint descriptors */
-#define hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03))
-/* ... and packet size, for any kind of endpoint descriptor */
-#define max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff)
-
 /* reverse of qh_urb_transaction:  free a list of TDs.
  * used for cleanup after errors, before HC sees an URB's TDs.
  */
@@ -2600,7 +2597,7 @@ static struct list_head *qh_urb_transaction(struct fotg210_hcd *fotg210,
                token |= (1 /* "in" */ << 8);
        /* else it's already initted to "out" pid (0 << 8) */
 
-       maxpacket = max_packet(usb_maxpacket(urb->dev, urb->pipe, !is_input));
+       maxpacket = usb_maxpacket(urb->dev, urb->pipe, !is_input);
 
        /*
         * buffer gets wrapped in one or more qtds;
@@ -2714,9 +2711,11 @@ static struct fotg210_qh *qh_make(struct fotg210_hcd *fotg210, struct urb *urb,
                gfp_t flags)
 {
        struct fotg210_qh *qh = fotg210_qh_alloc(fotg210, flags);
+       struct usb_host_endpoint *ep;
        u32 info1 = 0, info2 = 0;
        int is_input, type;
        int maxp = 0;
+       int mult;
        struct usb_tt *tt = urb->dev->tt;
        struct fotg210_qh_hw *hw;
 
@@ -2731,14 +2730,15 @@ static struct fotg210_qh *qh_make(struct fotg210_hcd *fotg210, struct urb *urb,
 
        is_input = usb_pipein(urb->pipe);
        type = usb_pipetype(urb->pipe);
-       maxp = usb_maxpacket(urb->dev, urb->pipe, !is_input);
+       ep = usb_pipe_endpoint(urb->dev, urb->pipe);
+       maxp = usb_endpoint_maxp(&ep->desc);
+       mult = usb_endpoint_maxp_mult(&ep->desc);
 
        /* 1024 byte maxpacket is a hardware ceiling.  High bandwidth
         * acts like up to 3KB, but is built from smaller packets.
         */
-       if (max_packet(maxp) > 1024) {
-               fotg210_dbg(fotg210, "bogus qh maxpacket %d\n",
-                               max_packet(maxp));
+       if (maxp > 1024) {
+               fotg210_dbg(fotg210, "bogus qh maxpacket %d\n", maxp);
                goto done;
        }
 
@@ -2752,8 +2752,7 @@ static struct fotg210_qh *qh_make(struct fotg210_hcd *fotg210, struct urb *urb,
         */
        if (type == PIPE_INTERRUPT) {
                qh->usecs = NS_TO_US(usb_calc_bus_time(USB_SPEED_HIGH,
-                               is_input, 0,
-                               hb_mult(maxp) * max_packet(maxp)));
+                               is_input, 0, mult * maxp));
                qh->start = NO_FRAME;
 
                if (urb->dev->speed == USB_SPEED_HIGH) {
@@ -2790,7 +2789,7 @@ static struct fotg210_qh *qh_make(struct fotg210_hcd *fotg210, struct urb *urb,
                        think_time = tt ? tt->think_time : 0;
                        qh->tt_usecs = NS_TO_US(think_time +
                                        usb_calc_bus_time(urb->dev->speed,
-                                       is_input, 0, max_packet(maxp)));
+                                       is_input, 0, maxp));
                        qh->period = urb->interval;
                        if (qh->period > fotg210->periodic_size) {
                                qh->period = fotg210->periodic_size;
@@ -2853,11 +2852,11 @@ static struct fotg210_qh *qh_make(struct fotg210_hcd *fotg210, struct urb *urb,
                         * to help them do so.  So now people expect to use
                         * such nonconformant devices with Linux too; sigh.
                         */
-                       info1 |= max_packet(maxp) << 16;
+                       info1 |= maxp << 16;
                        info2 |= (FOTG210_TUNE_MULT_HS << 30);
                } else {                /* PIPE_INTERRUPT */
-                       info1 |= max_packet(maxp) << 16;
-                       info2 |= hb_mult(maxp) << 30;
+                       info1 |= maxp << 16;
+                       info2 |= mult << 30;
                }
                break;
        default:
@@ -3927,6 +3926,7 @@ static void iso_stream_init(struct fotg210_hcd *fotg210,
        int is_input;
        long bandwidth;
        unsigned multi;
+       struct usb_host_endpoint *ep;
 
        /*
         * this might be a "high bandwidth" highspeed endpoint,
@@ -3934,14 +3934,14 @@ static void iso_stream_init(struct fotg210_hcd *fotg210,
         */
        epnum = usb_pipeendpoint(pipe);
        is_input = usb_pipein(pipe) ? USB_DIR_IN : 0;
-       maxp = usb_maxpacket(dev, pipe, !is_input);
+       ep = usb_pipe_endpoint(dev, pipe);
+       maxp = usb_endpoint_maxp(&ep->desc);
        if (is_input)
                buf1 = (1 << 11);
        else
                buf1 = 0;
 
-       maxp = max_packet(maxp);
-       multi = hb_mult(maxp);
+       multi = usb_endpoint_maxp_mult(&ep->desc);
        buf1 |= maxp;
        maxp *= multi;
 
@@ -4112,7 +4112,7 @@ static int itd_urb_transaction(struct fotg210_iso_stream *stream,
                } else {
 alloc_itd:
                        spin_unlock_irqrestore(&fotg210->lock, flags);
-                       itd = dma_pool_zalloc(fotg210->itd_pool, mem_flags,
+                       itd = dma_pool_alloc(fotg210->itd_pool, mem_flags,
                                        &itd_dma);
                        spin_lock_irqsave(&fotg210->lock, flags);
                        if (!itd) {
@@ -4122,6 +4122,7 @@ alloc_itd:
                        }
                }
 
+               memset(itd, 0, sizeof(*itd));
                itd->itd_dma = itd_dma;
                list_add(&itd->itd_list, &sched->td_list);
        }
@@ -4462,13 +4463,12 @@ static bool itd_complete(struct fotg210_hcd *fotg210, struct fotg210_itd *itd)
 
                        /* HC need not update length with this error */
                        if (!(t & FOTG210_ISOC_BABBLE)) {
-                               desc->actual_length =
-                                       fotg210_itdlen(urb, desc, t);
+                               desc->actual_length = FOTG210_ITD_LENGTH(t);
                                urb->actual_length += desc->actual_length;
                        }
                } else if (likely((t & FOTG210_ISOC_ACTIVE) == 0)) {
                        desc->status = 0;
-                       desc->actual_length = fotg210_itdlen(urb, desc, t);
+                       desc->actual_length = FOTG210_ITD_LENGTH(t);
                        urb->actual_length += desc->actual_length;
                } else {
                        /* URB was too late */
index 0a91061..0781442 100644 (file)
@@ -683,11 +683,6 @@ static inline unsigned fotg210_read_frame_index(struct fotg210_hcd *fotg210)
        return fotg210_readl(fotg210, &fotg210->regs->frame_index);
 }
 
-#define fotg210_itdlen(urb, desc, t) ({                        \
-       usb_pipein((urb)->pipe) ?                               \
-       (desc)->length - FOTG210_ITD_LENGTH(t) :                        \
-       FOTG210_ITD_LENGTH(t);                                  \
-})
 /*-------------------------------------------------------------------------*/
 
 #endif /* __LINUX_FOTG210_H */
index 5cc0544..b4cd9e6 100644 (file)
@@ -84,7 +84,7 @@ static int spear_ohci_hcd_drv_probe(struct platform_device *pdev)
 
        clk_prepare_enable(sohci_p->clk);
 
-       retval = usb_add_hcd(hcd, platform_get_irq(pdev, 0), 0);
+       retval = usb_add_hcd(hcd, irq, 0);
        if (retval == 0) {
                device_wakeup_enable(hcd->self.controller);
                return retval;
index 5923844..aa88e57 100644 (file)
@@ -595,7 +595,7 @@ int renesas_xhci_check_request_fw(struct pci_dev *pdev,
 
        err = renesas_fw_check_running(pdev);
        /* Continue ahead, if the firmware is already running. */
-       if (err == 0)
+       if (!err)
                return 0;
 
        if (err != 1)
@@ -620,9 +620,4 @@ exit:
 }
 EXPORT_SYMBOL_GPL(renesas_xhci_check_request_fw);
 
-void renesas_xhci_pci_exit(struct pci_dev *dev)
-{
-}
-EXPORT_SYMBOL_GPL(renesas_xhci_pci_exit);
-
 MODULE_LICENSE("GPL v2");
index 1c9a795..2c9f25c 100644 (file)
@@ -449,8 +449,6 @@ static void xhci_pci_remove(struct pci_dev *dev)
        struct xhci_hcd *xhci;
 
        xhci = hcd_to_xhci(pci_get_drvdata(dev));
-       if (xhci->quirks & XHCI_RENESAS_FW_QUIRK)
-               renesas_xhci_pci_exit(dev);
 
        xhci->xhc_state |= XHCI_STATE_REMOVING;
 
index acd7cf0..cb9a8f3 100644 (file)
@@ -7,7 +7,6 @@
 #if IS_ENABLED(CONFIG_USB_XHCI_PCI_RENESAS)
 int renesas_xhci_check_request_fw(struct pci_dev *dev,
                                  const struct pci_device_id *id);
-void renesas_xhci_pci_exit(struct pci_dev *dev);
 
 #else
 static int renesas_xhci_check_request_fw(struct pci_dev *dev,
@@ -16,8 +15,6 @@ static int renesas_xhci_check_request_fw(struct pci_dev *dev,
        return 0;
 }
 
-static void renesas_xhci_pci_exit(struct pci_dev *dev) { };
-
 #endif
 
 struct xhci_driver_data {
index f3e9b3b..190699b 100644 (file)
@@ -12,7 +12,7 @@
 #include <linux/slab.h>
 #include <linux/interrupt.h>
 #include <linux/platform_device.h>
-#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
 #include <linux/usb/ch9.h>
 #include <linux/usb/gadget.h>
 #include <linux/usb.h>
index 9c2e71e..0e786b6 100644 (file)
@@ -946,6 +946,11 @@ static inline int of_machine_is_compatible(const char *compat)
        return 0;
 }
 
+static inline int of_add_property(struct device_node *np, struct property *prop)
+{
+       return 0;
+}
+
 static inline int of_remove_property(struct device_node *np, struct property *prop)
 {
        return 0;
index ead8c9a..8fc2abd 100644 (file)
@@ -156,6 +156,20 @@ struct uac2_feature_unit_descriptor {
        __u8 bmaControls[]; /* variable length */
 } __attribute__((packed));
 
+#define UAC2_DT_FEATURE_UNIT_SIZE(ch)          (6 + ((ch) + 1) * 4)
+
+/* As above, but more useful for defining your own descriptors: */
+#define DECLARE_UAC2_FEATURE_UNIT_DESCRIPTOR(ch)               \
+struct uac2_feature_unit_descriptor_##ch {                     \
+       __u8  bLength;                                          \
+       __u8  bDescriptorType;                                  \
+       __u8  bDescriptorSubtype;                               \
+       __u8  bUnitID;                                          \
+       __u8  bSourceID;                                        \
+       __le32 bmaControls[ch + 1];                             \
+       __u8  iFeature;                                         \
+} __packed
+
 /* 4.7.2.10 Effect Unit Descriptor */
 
 struct uac2_effect_unit_descriptor {
index 75c7538..776851e 100644 (file)
@@ -329,6 +329,7 @@ struct usb_gadget_ops {
        struct usb_ep *(*match_ep)(struct usb_gadget *,
                        struct usb_endpoint_descriptor *,
                        struct usb_ss_ep_comp_descriptor *);
+       int     (*check_config)(struct usb_gadget *gadget);
 };
 
 /**
@@ -608,6 +609,7 @@ int usb_gadget_connect(struct usb_gadget *gadget);
 int usb_gadget_disconnect(struct usb_gadget *gadget);
 int usb_gadget_deactivate(struct usb_gadget *gadget);
 int usb_gadget_activate(struct usb_gadget *gadget);
+int usb_gadget_check_config(struct usb_gadget *gadget);
 #else
 static inline int usb_gadget_frame_number(struct usb_gadget *gadget)
 { return 0; }
@@ -631,6 +633,8 @@ static inline int usb_gadget_deactivate(struct usb_gadget *gadget)
 { return 0; }
 static inline int usb_gadget_activate(struct usb_gadget *gadget)
 { return 0; }
+static inline int usb_gadget_check_config(struct usb_gadget *gadget)
+{ return 0; }
 #endif /* CONFIG_USB_GADGET */
 
 /*-------------------------------------------------------------------------*/