Merge tag 'usb-5.15-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
authorLinus Torvalds <torvalds@linux-foundation.org>
Wed, 1 Sep 2021 16:59:34 +0000 (09:59 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 1 Sep 2021 16:59:34 +0000 (09:59 -0700)
Pull USB / Thunderbolt updates from Greg KH:
 "Here is the big set of USB and Thunderbolt patches for 5.15-rc1.

  Nothing huge in here, just lots of constant forward progress on a
  number of different drivers and hardware support:

   - more USB 4/Thunderbolt support added

   - dwc3 driver updates and additions

   - usb gadget fixes and addtions for new types

   - udc gadget driver updates

   - host controller updates

   - removal of obsolete drivers

   - other minor driver updates

  All of these have been in linux-next for a while with no reported
  issues"

* tag 'usb-5.15-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (148 commits)
  usb: isp1760: otg control register access
  usb: isp1760: use the right irq status bit
  usb: isp1760: write to status and address register
  usb: isp1760: fix qtd fill length
  usb: isp1760: fix memory pool initialization
  usb: typec: tcpm: Fix spelling mistake "atleast" -> "at least"
  usb: dwc2: Fix spelling mistake "was't" -> "wasn't"
  usb: renesas_usbhs: Fix spelling mistake "faile" -> "failed"
  usb: host: xhci-rcar: Don't reload firmware after the completion
  usb: xhci-mtk: allow bandwidth table rollover
  usb: mtu3: fix random remote wakeup
  usb: mtu3: return successful suspend status
  usb: xhci-mtk: Do not use xhci's virt_dev in drop_endpoint
  usb: xhci-mtk: modify the SOF/ITP interval for mt8195
  usb: xhci-mtk: add a member of num_esit
  usb: xhci-mtk: check boundary before check tt
  usb: xhci-mtk: update fs bus bandwidth by bw_budget_table
  usb: xhci-mtk: fix issue of out-of-bounds array access
  usb: xhci-mtk: support option to disable usb2 ports
  usb: xhci-mtk: fix use-after-free of mtk->hcd
  ...

123 files changed:
Documentation/ABI/testing/configfs-usb-gadget-uac1
Documentation/ABI/testing/configfs-usb-gadget-uac2
Documentation/devicetree/bindings/connector/usb-connector.yaml
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/mediatek,mtk-xhci.yaml
Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml
Documentation/devicetree/bindings/usb/mediatek,musb.yaml
Documentation/devicetree/bindings/usb/qcom,dwc3.yaml
Documentation/devicetree/bindings/usb/renesas,usbhs.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/thunderbolt/nhi.c
drivers/thunderbolt/switch.c
drivers/usb/Kconfig
drivers/usb/Makefile
drivers/usb/cdns3/cdnsp-mem.c
drivers/usb/chipidea/host.c
drivers/usb/common/common.c
drivers/usb/dwc2/core.c
drivers/usb/dwc2/core.h
drivers/usb/dwc2/gadget.c
drivers/usb/dwc3/core.c
drivers/usb/dwc3/core.h
drivers/usb/dwc3/drd.c
drivers/usb/dwc3/dwc3-imx8mp.c
drivers/usb/dwc3/dwc3-meson-g12a.c
drivers/usb/dwc3/dwc3-pci.c
drivers/usb/dwc3/dwc3-qcom.c
drivers/usb/dwc3/ep0.c
drivers/usb/dwc3/gadget.c
drivers/usb/gadget/Kconfig
drivers/usb/gadget/composite.c
drivers/usb/gadget/configfs.c
drivers/usb/gadget/function/f_fs.c
drivers/usb/gadget/function/f_hid.c
drivers/usb/gadget/function/f_mass_storage.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/f_uvc.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_hid.h
drivers/usb/gadget/function/u_uac1.h
drivers/usb/gadget/function/u_uac2.h
drivers/usb/gadget/function/uvc.h
drivers/usb/gadget/function/uvc_queue.c
drivers/usb/gadget/function/uvc_queue.h
drivers/usb/gadget/function/uvc_video.c
drivers/usb/gadget/function/uvc_video.h
drivers/usb/gadget/legacy/Kconfig
drivers/usb/gadget/legacy/inode.c
drivers/usb/gadget/legacy/printer.c
drivers/usb/gadget/udc/aspeed-vhub/core.c
drivers/usb/gadget/udc/aspeed-vhub/dev.c
drivers/usb/gadget/udc/aspeed-vhub/ep0.c
drivers/usb/gadget/udc/aspeed-vhub/epn.c
drivers/usb/gadget/udc/aspeed-vhub/hub.c
drivers/usb/gadget/udc/at91_udc.c
drivers/usb/gadget/udc/bdc/bdc_cmd.c
drivers/usb/gadget/udc/bdc/bdc_core.c
drivers/usb/gadget/udc/core.c
drivers/usb/gadget/udc/mv_u3d_core.c
drivers/usb/gadget/udc/pxa25x_udc.c
drivers/usb/gadget/udc/renesas_usb3.c
drivers/usb/gadget/udc/s3c2410_udc.c
drivers/usb/gadget/udc/tegra-xudc.c
drivers/usb/host/Kconfig
drivers/usb/host/ehci-brcm.c
drivers/usb/host/ehci-hcd.c
drivers/usb/host/ehci-mv.c
drivers/usb/host/ehci-orion.c
drivers/usb/host/ehci-pmcmsp.c [deleted file]
drivers/usb/host/fotg210-hcd.c
drivers/usb/host/fotg210.h
drivers/usb/host/ohci-spear.c
drivers/usb/host/ohci-tmio.c
drivers/usb/host/xhci-debugfs.c
drivers/usb/host/xhci-hub.c
drivers/usb/host/xhci-mtk-sch.c
drivers/usb/host/xhci-mtk.c
drivers/usb/host/xhci-mtk.h
drivers/usb/host/xhci-pci-renesas.c
drivers/usb/host/xhci-pci.c
drivers/usb/host/xhci-pci.h
drivers/usb/host/xhci-rcar.c
drivers/usb/host/xhci-ring.c
drivers/usb/host/xhci-trace.h
drivers/usb/host/xhci.c
drivers/usb/host/xhci.h
drivers/usb/isp1760/isp1760-core.c
drivers/usb/isp1760/isp1760-hcd.c
drivers/usb/isp1760/isp1760-regs.h
drivers/usb/isp1760/isp1760-udc.c
drivers/usb/misc/adutux.c
drivers/usb/misc/brcmstb-usb-pinmap.c
drivers/usb/mtu3/mtu3.h
drivers/usb/mtu3/mtu3_core.c
drivers/usb/mtu3/mtu3_dr.c
drivers/usb/mtu3/mtu3_dr.h
drivers/usb/mtu3/mtu3_gadget.c
drivers/usb/mtu3/mtu3_host.c
drivers/usb/mtu3/mtu3_plat.c
drivers/usb/musb/musb_dsps.c
drivers/usb/phy/phy-fsl-usb.c
drivers/usb/phy/phy-isp1301-omap.c
drivers/usb/phy/phy-tahvo.c
drivers/usb/phy/phy-twl6030-usb.c
drivers/usb/renesas_usbhs/fifo.c
drivers/usb/typec/tcpm/tcpm.c
drivers/usb/usbip/vhci_hcd.c
include/linux/of.h
include/linux/thunderbolt.h
include/linux/usb/audio-v2.h
include/linux/usb/ehci_def.h
include/linux/usb/gadget.h
include/linux/usb/otg.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 92b49bc..7eb8659 100644 (file)
@@ -111,6 +111,10 @@ properties:
       - 1.5A
       - 3.0A
 
+  pd-disable:
+    description: Set this property if the Type-C connector has no power delivery support.
+    type: boolean
+
   # The following are optional properties for "usb-c-connector" with power
   # delivery support.
   source-pdos:
index 1d49cc3..60dc278 100644 (file)
@@ -14,6 +14,7 @@ properties:
   compatible:
     enum:
       - qcom,sc7180-qmp-usb3-dp-phy
+      - qcom,sc7280-qmp-usb3-dp-phy
       - qcom,sc8180x-qmp-usb3-dp-phy
       - qcom,sdm845-qmp-usb3-dp-phy
       - qcom,sm8250-qmp-usb3-dp-phy
index 8089dc9..8913497 100644 (file)
@@ -34,6 +34,7 @@ properties:
               - allwinner,sun6i-a31-ehci
               - allwinner,sun7i-a20-ehci
               - allwinner,sun8i-a23-ehci
+              - allwinner,sun8i-a83t-ehci
               - allwinner,sun8i-h3-ehci
               - allwinner,sun8i-r40-ehci
               - allwinner,sun9i-a80-ehci
@@ -142,6 +143,11 @@ properties:
   iommus:
     maxItems: 1
 
+  dr_mode:
+    enum:
+      - host
+      - otg
+
 required:
   - compatible
   - reg
index 0f5f6ea..acbf94f 100644 (file)
@@ -24,6 +24,7 @@ properties:
               - allwinner,sun6i-a31-ohci
               - allwinner,sun7i-a20-ohci
               - allwinner,sun8i-a23-ohci
+              - allwinner,sun8i-a83t-ohci
               - allwinner,sun8i-h3-ohci
               - allwinner,sun8i-r40-ohci
               - allwinner,sun9i-a80-ohci
@@ -109,6 +110,11 @@ properties:
   iommus:
     maxItems: 1
 
+  dr_mode:
+    enum:
+      - host
+      - otg
+
 required:
   - compatible
   - reg
index 240882b..11f7bac 100644 (file)
@@ -31,6 +31,7 @@ properties:
           - mediatek,mt8173-xhci
           - mediatek,mt8183-xhci
           - mediatek,mt8192-xhci
+          - mediatek,mt8195-xhci
       - const: mediatek,mtk-xhci
 
   reg:
@@ -152,6 +153,11 @@ properties:
     description: The mask to disable u3ports, bit0 for u3port0,
       bit1 for u3port1, ... etc
 
+  mediatek,u2p-dis-msk:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description: The mask to disable u2ports, bit0 for u2port0,
+      bit1 for u2port1, ... etc
+
   "#address-cells":
     const: 1
 
index dbc7876..77db123 100644 (file)
@@ -38,7 +38,18 @@ properties:
       - const: ippc
 
   interrupts:
-    maxItems: 1
+    description:
+      use "interrupts-extended" when the interrupts are connected to the
+      separate interrupt controllers
+    minItems: 1
+    items:
+      - description: SSUSB device controller interrupt
+      - description: optional, wakeup interrupt used to support runtime PM
+
+  interrupt-names:
+    items:
+      - const: device
+      - const: wakeup
 
   power-domains:
     description: A phandle to USB power domain node to control USB's MTCMOS
@@ -106,7 +117,7 @@ properties:
   extcon:
     deprecated: true
     description: |
-      Phandle to the extcon device detecting the IDDIG/VBUS state, neede
+      Phandle to the extcon device detecting the IDDIG state, needed
       when supports dual-role mode.
       It's considered valid for compatibility reasons, not allowed for
       new bindings, and use "usb-role-switch" property instead.
@@ -116,6 +127,10 @@ properties:
     description: Support role switch.
     type: boolean
 
+  role-switch-default-mode:
+    enum: [host, peripheral]
+    default: host
+
   connector:
     $ref: /connector/usb-connector.yaml#
     description:
@@ -166,6 +181,12 @@ properties:
     description: The mask to disable u3ports, bit0 for u3port0,
       bit1 for u3port1, ... etc
 
+  mediatek,u2p-dis-msk:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description: The mask to disable u2ports, bit0 for u2port0,
+      bit1 for u2port1, ... etc; but can't disable u2port0 if dual role mode
+      is enabled, so will be skipped in this case.
+
 # Required child node when support dual-role
 patternProperties:
   "^usb@[0-9a-f]+$":
@@ -178,6 +199,7 @@ patternProperties:
 dependencies:
   connector: [ 'usb-role-switch' ]
   port: [ 'usb-role-switch' ]
+  role-switch-default-mode: [ 'usb-role-switch' ]
   wakeup-source: [ 'mediatek,syscon-wakeup' ]
 
 required:
@@ -230,7 +252,7 @@ examples:
         };
     };
 
-  # Enable/disable device by an input gpio for VBUS pin
+  # Dual role switch by gpio-usb-b-connector
   - |
     #include <dt-bindings/gpio/gpio.h>
     #include <dt-bindings/power/mt2712-power.h>
@@ -244,13 +266,27 @@ examples:
         power-domains = <&scpsys MT2712_POWER_DOMAIN_USB2>;
         clocks = <&topckgen CLK_TOP_USB30_SEL>;
         clock-names = "sys_ck";
-        dr_mode = "peripheral";
+        dr_mode = "otg";
         usb-role-switch;
+        #address-cells = <1>;
+        #size-cells = <1>;
+        ranges;
+
+        host0: usb@11270000 {
+            compatible = "mediatek,mt2712-xhci", "mediatek,mtk-xhci";
+            reg = <0x11270000 0x1000>;
+            reg-names = "mac";
+            interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_LOW>;
+            power-domains = <&scpsys MT2712_POWER_DOMAIN_USB>;
+            clocks = <&topckgen CLK_TOP_USB30_SEL>, <&clk26m>;
+            clock-names = "sys_ck", "ref_ck";
+        };
 
         connector {
             compatible = "gpio-usb-b-connector", "usb-b-connector";
             type = "micro";
-            vbus-gpios = <&pio 13 GPIO_ACTIVE_HIGH>;
+            id-gpios = <&pio 12 GPIO_ACTIVE_HIGH>;
+            vbus-supply = <&usb_p0_vbus>;
         };
     };
 
@@ -268,6 +304,7 @@ examples:
         wakeup-source;
         dr_mode = "otg";
         usb-role-switch;
+        role-switch-default-mode = "host";
         #address-cells = <1>;
         #size-cells = <1>;
         ranges;
index 84ddacf..03d62d6 100644 (file)
@@ -19,6 +19,7 @@ properties:
       - enum:
           - mediatek,mt8516-musb
           - mediatek,mt2701-musb
+          - mediatek,mt7623-musb
       - const: mediatek,mtk-musb
 
   reg:
index 4e64517..e70afc4 100644 (file)
@@ -17,6 +17,7 @@ properties:
           - qcom,msm8998-dwc3
           - qcom,sc7180-dwc3
           - qcom,sc7280-dwc3
+          - qcom,sdm660-dwc3
           - qcom,sdm845-dwc3
           - qcom,sdx55-dwc3
           - qcom,sm4250-dwc3
index ad73339..012fe80 100644 (file)
@@ -17,7 +17,9 @@ properties:
           - const: renesas,rza1-usbhs
 
       - items:
-          - const: renesas,usbhs-r7s9210 # RZ/A2
+          - enum:
+              - renesas,usbhs-r7s9210   # RZ/A2
+              - renesas,usbhs-r9a07g044 # RZ/G2{L,LC}
           - const: renesas,rza2-usbhs
 
       - items:
@@ -59,7 +61,8 @@ properties:
       - description: USB 2.0 clock selector
 
   interrupts:
-    maxItems: 1
+    minItems: 1
+    maxItems: 4
 
   renesas,buswait:
     $ref: /schemas/types.yaml#/definitions/uint32
@@ -108,6 +111,25 @@ required:
   - clocks
   - interrupts
 
+allOf:
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: renesas,usbhs-r9a07g044
+    then:
+      properties:
+        interrupts:
+          items:
+            - description: U2P_IXL_INT
+            - description: U2P_INT_DMA[0]
+            - description: U2P_INT_DMA[1]
+            - description: U2P_INT_DMAERR
+    else:
+      properties:
+        interrupts:
+          maxItems: 1
+
 additionalProperties: false
 
 examples:
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..c181130 100644 (file)
@@ -724,18 +724,28 @@ Function-specific configfs interface
 The function name to use when creating the function directory is "uac2".
 The uac2 function provides these attributes in its function directory:
 
-       =============== ====================================================
-       c_chmask        capture channel mask
-       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
-       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_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)
+       req_number       the number of pre-allocated request for both capture
+                        and playback
+       ================ ====================================================
 
 The attributes have sane default values.
 
@@ -904,16 +914,26 @@ Function-specific configfs interface
 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 188c576..903f56a 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 fa44332..c73da05 100644 (file)
@@ -35,6 +35,8 @@
 
 #define NHI_MAILBOX_TIMEOUT    500 /* ms */
 
+#define QUIRK_AUTO_CLEAR_INT   BIT(0)
+
 static int ring_interrupt_index(struct tb_ring *ring)
 {
        int bit = ring->hop;
@@ -66,14 +68,17 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active)
                else
                        index = ring->hop + ring->nhi->hop_count;
 
-               /*
-                * Ask the hardware to clear interrupt status bits automatically
-                * since we already know which interrupt was triggered.
-                */
-               misc = ioread32(ring->nhi->iobase + REG_DMA_MISC);
-               if (!(misc & REG_DMA_MISC_INT_AUTO_CLEAR)) {
-                       misc |= REG_DMA_MISC_INT_AUTO_CLEAR;
-                       iowrite32(misc, ring->nhi->iobase + REG_DMA_MISC);
+               if (ring->nhi->quirks & QUIRK_AUTO_CLEAR_INT) {
+                       /*
+                        * Ask the hardware to clear interrupt status
+                        * bits automatically since we already know
+                        * which interrupt was triggered.
+                        */
+                       misc = ioread32(ring->nhi->iobase + REG_DMA_MISC);
+                       if (!(misc & REG_DMA_MISC_INT_AUTO_CLEAR)) {
+                               misc |= REG_DMA_MISC_INT_AUTO_CLEAR;
+                               iowrite32(misc, ring->nhi->iobase + REG_DMA_MISC);
+                       }
                }
 
                ivr_base = ring->nhi->iobase + REG_INT_VEC_ALLOC_BASE;
@@ -377,11 +382,24 @@ void tb_ring_poll_complete(struct tb_ring *ring)
 }
 EXPORT_SYMBOL_GPL(tb_ring_poll_complete);
 
+static void ring_clear_msix(const struct tb_ring *ring)
+{
+       if (ring->nhi->quirks & QUIRK_AUTO_CLEAR_INT)
+               return;
+
+       if (ring->is_tx)
+               ioread32(ring->nhi->iobase + REG_RING_NOTIFY_BASE);
+       else
+               ioread32(ring->nhi->iobase + REG_RING_NOTIFY_BASE +
+                        4 * (ring->nhi->hop_count / 32));
+}
+
 static irqreturn_t ring_msix(int irq, void *data)
 {
        struct tb_ring *ring = data;
 
        spin_lock(&ring->nhi->lock);
+       ring_clear_msix(ring);
        spin_lock(&ring->lock);
        __ring_interrupt(ring);
        spin_unlock(&ring->lock);
@@ -1074,6 +1092,16 @@ static void nhi_shutdown(struct tb_nhi *nhi)
                nhi->ops->shutdown(nhi);
 }
 
+static void nhi_check_quirks(struct tb_nhi *nhi)
+{
+       /*
+        * Intel hardware supports auto clear of the interrupt status
+        * reqister right after interrupt is being issued.
+        */
+       if (nhi->pdev->vendor == PCI_VENDOR_ID_INTEL)
+               nhi->quirks |= QUIRK_AUTO_CLEAR_INT;
+}
+
 static int nhi_init_msi(struct tb_nhi *nhi)
 {
        struct pci_dev *pdev = nhi->pdev;
@@ -1190,6 +1218,8 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
        if (!nhi->tx_rings || !nhi->rx_rings)
                return -ENOMEM;
 
+       nhi_check_quirks(nhi);
+
        res = nhi_init_msi(nhi);
        if (res) {
                dev_err(&pdev->dev, "cannot enable MSI, aborting\n");
index 10d6b22..3014146 100644 (file)
@@ -724,6 +724,12 @@ static int tb_init_port(struct tb_port *port)
        int res;
        int cap;
 
+       INIT_LIST_HEAD(&port->list);
+
+       /* Control adapter does not have configuration space */
+       if (!port->port)
+               return 0;
+
        res = tb_port_read(port, &port->config, TB_CFG_PORT, 0, 8);
        if (res) {
                if (res == -ENODEV) {
@@ -736,7 +742,7 @@ static int tb_init_port(struct tb_port *port)
        }
 
        /* Port 0 is the switch itself and has no PHY. */
-       if (port->config.type == TB_TYPE_PORT && port->port != 0) {
+       if (port->config.type == TB_TYPE_PORT) {
                cap = tb_port_find_cap(port, TB_PORT_CAP_PHY);
 
                if (cap > 0)
@@ -762,7 +768,7 @@ static int tb_init_port(struct tb_port *port)
                if (!port->ctl_credits)
                        port->ctl_credits = 2;
 
-       } else if (port->port != 0) {
+       } else {
                cap = tb_port_find_cap(port, TB_PORT_CAP_ADAP);
                if (cap > 0)
                        port->cap_adap = cap;
@@ -773,10 +779,7 @@ static int tb_init_port(struct tb_port *port)
                ADP_CS_4_TOTAL_BUFFERS_SHIFT;
 
        tb_dump_port(port->sw->tb, port);
-
-       INIT_LIST_HEAD(&port->list);
        return 0;
-
 }
 
 static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid,
@@ -1498,6 +1501,7 @@ static ssize_t authorized_show(struct device *dev,
 
 static int disapprove_switch(struct device *dev, void *not_used)
 {
+       char *envp[] = { "AUTHORIZED=0", NULL };
        struct tb_switch *sw;
 
        sw = tb_to_switch(dev);
@@ -1514,7 +1518,7 @@ static int disapprove_switch(struct device *dev, void *not_used)
                        return ret;
 
                sw->authorized = 0;
-               kobject_uevent(&sw->dev.kobj, KOBJ_CHANGE);
+               kobject_uevent_env(&sw->dev.kobj, KOBJ_CHANGE, envp);
        }
 
        return 0;
@@ -1522,7 +1526,9 @@ static int disapprove_switch(struct device *dev, void *not_used)
 
 static int tb_switch_set_authorized(struct tb_switch *sw, unsigned int val)
 {
+       char envp_string[13];
        int ret = -EINVAL;
+       char *envp[] = { envp_string, NULL };
 
        if (!mutex_trylock(&sw->tb->lock))
                return restart_syscall();
@@ -1559,8 +1565,12 @@ static int tb_switch_set_authorized(struct tb_switch *sw, unsigned int val)
 
        if (!ret) {
                sw->authorized = val;
-               /* Notify status change to the userspace */
-               kobject_uevent(&sw->dev.kobj, KOBJ_CHANGE);
+               /*
+                * Notify status change to the userspace, informing the new
+                * value of /sys/bus/thunderbolt/devices/.../authorized.
+                */
+               sprintf(envp_string, "AUTHORIZED=%u", sw->authorized);
+               kobject_uevent_env(&sw->dev.kobj, KOBJ_CHANGE, envp);
        }
 
 unlock:
@@ -2443,7 +2453,7 @@ static void tb_switch_default_link_ports(struct tb_switch *sw)
 {
        int i;
 
-       for (i = 1; i <= sw->config.max_port_number; i += 2) {
+       for (i = 1; i <= sw->config.max_port_number; i++) {
                struct tb_port *port = &sw->ports[i];
                struct tb_port *subordinate;
 
index 26475b4..578a439 100644 (file)
@@ -11,7 +11,7 @@ config USB_OHCI_BIG_ENDIAN_MMIO
 
 config USB_OHCI_LITTLE_ENDIAN
        bool
-       default n if STB03xxx || PPC_MPC52xx
+       default n if PPC_MPC52xx
        default y
 
 config USB_EHCI_BIG_ENDIAN_MMIO
index 3e2cc95..643edf5 100644 (file)
@@ -31,7 +31,6 @@ obj-$(CONFIG_USB_SL811_HCD)   += host/
 obj-$(CONFIG_USB_ISP1362_HCD)  += host/
 obj-$(CONFIG_USB_U132_HCD)     += host/
 obj-$(CONFIG_USB_R8A66597_HCD) += host/
-obj-$(CONFIG_USB_HWA_HCD)      += host/
 obj-$(CONFIG_USB_FSL_USB2)     += host/
 obj-$(CONFIG_USB_FOTG210_HCD)  += host/
 obj-$(CONFIG_USB_MAX3421_HCD)  += host/
index a47948a..ad9aee3 100644 (file)
@@ -882,7 +882,7 @@ static u32 cdnsp_get_endpoint_max_burst(struct usb_gadget *g,
        if (g->speed == USB_SPEED_HIGH &&
            (usb_endpoint_xfer_isoc(pep->endpoint.desc) ||
             usb_endpoint_xfer_int(pep->endpoint.desc)))
-               return (usb_endpoint_maxp(pep->endpoint.desc) & 0x1800) >> 11;
+               return usb_endpoint_maxp_mult(pep->endpoint.desc) - 1;
 
        return 0;
 }
index e86d13c..bdc3885 100644 (file)
@@ -240,15 +240,18 @@ static int ci_ehci_hub_control(
 )
 {
        struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+       unsigned int    ports = HCS_N_PORTS(ehci->hcs_params);
        u32 __iomem     *status_reg;
-       u32             temp;
+       u32             temp, port_index;
        unsigned long   flags;
        int             retval = 0;
        bool            done = false;
        struct device *dev = hcd->self.controller;
        struct ci_hdrc *ci = dev_get_drvdata(dev);
 
-       status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1];
+       port_index = wIndex & 0xff;
+       port_index -= (port_index > 0);
+       status_reg = &ehci->regs->port_status[port_index];
 
        spin_lock_irqsave(&ehci->lock, flags);
 
@@ -260,6 +263,11 @@ static int ci_ehci_hub_control(
        }
 
        if (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_SUSPEND) {
+               if (!wIndex || wIndex > ports) {
+                       retval = -EPIPE;
+                       goto done;
+               }
+
                temp = ehci_readl(ehci, status_reg);
                if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) != 0) {
                        retval = -EPIPE;
@@ -288,7 +296,7 @@ static int ci_ehci_hub_control(
                        ehci_writel(ehci, temp, status_reg);
                }
 
-               set_bit((wIndex & 0xff) - 1, &ehci->suspended_ports);
+               set_bit(port_index, &ehci->suspended_ports);
                goto done;
        }
 
index 347fb3d..c9bdeb4 100644 (file)
@@ -200,6 +200,26 @@ enum usb_dr_mode usb_get_dr_mode(struct device *dev)
 }
 EXPORT_SYMBOL_GPL(usb_get_dr_mode);
 
+/**
+ * usb_get_role_switch_default_mode - Get default mode for given device
+ * @dev: Pointer to the given device
+ *
+ * The function gets string from property 'role-switch-default-mode',
+ * and returns the corresponding enum usb_dr_mode.
+ */
+enum usb_dr_mode usb_get_role_switch_default_mode(struct device *dev)
+{
+       const char *str;
+       int ret;
+
+       ret = device_property_read_string(dev, "role-switch-default-mode", &str);
+       if (ret < 0)
+               return USB_DR_MODE_UNKNOWN;
+
+       return usb_get_dr_mode_from_string(str);
+}
+EXPORT_SYMBOL_GPL(usb_get_role_switch_default_mode);
+
 /**
  * usb_decode_interval - Decode bInterval into the time expressed in 1us unit
  * @epd: The descriptor of the endpoint
index 272ae57..cf0bcd0 100644 (file)
@@ -295,7 +295,7 @@ void dwc2_hib_restore_common(struct dwc2_hsotg *hsotg, int rem_wakeup,
        if (dwc2_hsotg_wait_bit_set(hsotg, GINTSTS, GINTSTS_RESTOREDONE,
                                    20000)) {
                dev_dbg(hsotg->dev,
-                       "%s: Restore Done wan't generated here\n",
+                       "%s: Restore Done wasn't generated here\n",
                        __func__);
        } else {
                dev_dbg(hsotg->dev, "restore done  generated here\n");
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..837237e 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 */
 };
 
@@ -4683,12 +4709,35 @@ static int dwc2_hsotg_vbus_draw(struct usb_gadget *gadget, unsigned int mA)
        return usb_phy_set_power(hsotg->uphy, mA);
 }
 
+static void dwc2_gadget_set_speed(struct usb_gadget *g, enum usb_device_speed speed)
+{
+       struct dwc2_hsotg *hsotg = to_hsotg(g);
+       unsigned long           flags;
+
+       spin_lock_irqsave(&hsotg->lock, flags);
+       switch (speed) {
+       case USB_SPEED_HIGH:
+               hsotg->params.speed = DWC2_SPEED_PARAM_HIGH;
+               break;
+       case USB_SPEED_FULL:
+               hsotg->params.speed = DWC2_SPEED_PARAM_FULL;
+               break;
+       case USB_SPEED_LOW:
+               hsotg->params.speed = DWC2_SPEED_PARAM_LOW;
+               break;
+       default:
+               dev_err(hsotg->dev, "invalid speed (%d)\n", speed);
+       }
+       spin_unlock_irqrestore(&hsotg->lock, flags);
+}
+
 static const struct usb_gadget_ops dwc2_hsotg_gadget_ops = {
        .get_frame      = dwc2_hsotg_gadget_getframe,
        .set_selfpowered        = dwc2_hsotg_set_selfpowered,
        .udc_start              = dwc2_hsotg_udc_start,
        .udc_stop               = dwc2_hsotg_udc_stop,
        .pullup                 = dwc2_hsotg_pullup,
+       .udc_set_speed          = dwc2_gadget_set_speed,
        .vbus_session           = dwc2_hsotg_vbus_session,
        .vbus_draw              = dwc2_hsotg_vbus_draw,
 };
index ba74ad7..01866dc 100644 (file)
@@ -1050,6 +1050,15 @@ static int dwc3_core_init(struct dwc3 *dwc)
                if (!DWC3_VER_IS_PRIOR(DWC3, 290A))
                        reg |= DWC3_GUCTL1_DEV_L1_EXIT_BY_HW;
 
+               /*
+                * Decouple USB 2.0 L1 & L2 events which will allow for
+                * gadget driver to only receive U3/L2 suspend & wakeup
+                * events and prevent the more frequent L1 LPM transitions
+                * from interrupting the driver.
+                */
+               if (!DWC3_VER_IS_PRIOR(DWC3, 300A))
+                       reg |= DWC3_GUCTL1_DEV_DECOUPLE_L1L2_EVT;
+
                if (dwc->dis_tx_ipgap_linecheck_quirk)
                        reg |= DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS;
 
@@ -1267,6 +1276,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 +1292,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 +1342,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 +1412,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..5612bfd 100644 (file)
 #define DWC3_GUCTL_HSTINAUTORETRY      BIT(14)
 
 /* Global User Control 1 Register */
-#define DWC3_GUCTL1_PARKMODE_DISABLE_SS        BIT(17)
+#define DWC3_GUCTL1_DEV_DECOUPLE_L1L2_EVT      BIT(31)
 #define DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS     BIT(28)
-#define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW  BIT(24)
+#define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW          BIT(24)
+#define DWC3_GUCTL1_PARKMODE_DISABLE_SS                BIT(17)
 
 /* Global Status Register */
 #define DWC3_GSTS_OTG_IP       BIT(10)
@@ -1023,6 +1024,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 +1039,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 +1082,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 +1241,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 +1255,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 +1292,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 +1527,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 +1547,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 8fcbac1..d7f7683 100644 (file)
@@ -541,14 +541,10 @@ static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw)
 static int dwc3_setup_role_switch(struct dwc3 *dwc)
 {
        struct usb_role_switch_desc dwc3_role_switch = {NULL};
-       const char *str;
        u32 mode;
-       int ret;
 
-       ret = device_property_read_string(dwc->dev, "role-switch-default-mode",
-                                         &str);
-       if (ret >= 0  && !strncmp(str, "host", strlen("host"))) {
-               dwc->role_switch_default_mode = USB_DR_MODE_HOST;
+       dwc->role_switch_default_mode = usb_get_role_switch_default_mode(dwc->dev);
+       if (dwc->role_switch_default_mode == USB_DR_MODE_HOST) {
                mode = DWC3_GCTL_PRTCAP_HOST;
        } else {
                dwc->role_switch_default_mode = USB_DR_MODE_PERIPHERAL;
index 756faa4..d328d20 100644 (file)
@@ -152,13 +152,6 @@ static int dwc3_imx8mp_probe(struct platform_device *pdev)
        }
        dwc3_imx->irq = irq;
 
-       err = devm_request_threaded_irq(dev, irq, NULL, dwc3_imx8mp_interrupt,
-                                       IRQF_ONESHOT, dev_name(dev), dwc3_imx);
-       if (err) {
-               dev_err(dev, "failed to request IRQ #%d --> %d\n", irq, err);
-               goto disable_clks;
-       }
-
        pm_runtime_set_active(dev);
        pm_runtime_enable(dev);
        err = pm_runtime_get_sync(dev);
@@ -186,6 +179,13 @@ static int dwc3_imx8mp_probe(struct platform_device *pdev)
        }
        of_node_put(dwc3_np);
 
+       err = devm_request_threaded_irq(dev, irq, NULL, dwc3_imx8mp_interrupt,
+                                       IRQF_ONESHOT, dev_name(dev), dwc3_imx);
+       if (err) {
+               dev_err(dev, "failed to request IRQ #%d --> %d\n", irq, err);
+               goto depopulate;
+       }
+
        device_set_wakeup_capable(dev, true);
        pm_runtime_put(dev);
 
index ffe301d..d0f9b7c 100644 (file)
@@ -598,6 +598,8 @@ static int dwc3_meson_g12a_otg_init(struct platform_device *pdev,
                                   USB_R5_ID_DIG_IRQ, 0);
 
                irq = platform_get_irq(pdev, 0);
+               if (irq < 0)
+                       return irq;
                ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
                                                dwc3_meson_g12a_irq_thread,
                                                IRQF_ONESHOT, pdev->name, priv);
index 2b37bdd..7ff8fc8 100644 (file)
@@ -44,6 +44,7 @@
 #define PCI_DEVICE_ID_INTEL_ADLM               0x54ee
 #define PCI_DEVICE_ID_INTEL_ADLS               0x7ae1
 #define PCI_DEVICE_ID_INTEL_TGL                        0x9a15
+#define PCI_DEVICE_ID_AMD_MR                   0x163a
 
 #define PCI_INTEL_BXT_DSM_GUID         "732b85d5-b7a7-4a1b-9ba0-4bbd00ffd511"
 #define PCI_INTEL_BXT_FUNC_PMU_PWR     4
@@ -148,6 +149,14 @@ static const struct property_entry dwc3_pci_amd_properties[] = {
        {}
 };
 
+static const struct property_entry dwc3_pci_mr_properties[] = {
+       PROPERTY_ENTRY_STRING("dr_mode", "otg"),
+       PROPERTY_ENTRY_BOOL("usb-role-switch"),
+       PROPERTY_ENTRY_STRING("role-switch-default-mode", "host"),
+       PROPERTY_ENTRY_BOOL("linux,sysdev_is_parent"),
+       {}
+};
+
 static const struct software_node dwc3_pci_intel_swnode = {
        .properties = dwc3_pci_intel_properties,
 };
@@ -160,6 +169,10 @@ static const struct software_node dwc3_pci_amd_swnode = {
        .properties = dwc3_pci_amd_properties,
 };
 
+static const struct software_node dwc3_pci_amd_mr_swnode = {
+       .properties = dwc3_pci_mr_properties,
+};
+
 static int dwc3_pci_quirks(struct dwc3_pci *dwc)
 {
        struct pci_dev                  *pdev = dwc->pci;
@@ -401,6 +414,10 @@ static const struct pci_device_id dwc3_pci_id_table[] = {
 
        { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_NL_USB),
          (kernel_ulong_t) &dwc3_pci_amd_swnode, },
+
+       { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_MR),
+         (kernel_ulong_t)&dwc3_pci_amd_mr_swnode, },
+
        {  }    /* Terminating Entry */
 };
 MODULE_DEVICE_TABLE(pci, dwc3_pci_id_table);
index 49e6ca9..9abbd01 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;
@@ -614,6 +614,10 @@ static int dwc3_qcom_acpi_register_core(struct platform_device *pdev)
                qcom->acpi_pdata->dwc3_core_base_size;
 
        irq = platform_get_irq(pdev_irq, 0);
+       if (irq < 0) {
+               ret = irq;
+               goto out;
+       }
        child_res[1].flags = IORESOURCE_IRQ;
        child_res[1].start = child_res[1].end = irq;
 
@@ -645,6 +649,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 +658,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 +830,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);
@@ -916,6 +935,7 @@ static const struct of_device_id dwc3_qcom_of_match[] = {
        { .compatible = "qcom,dwc3" },
        { .compatible = "qcom,msm8996-dwc3" },
        { .compatible = "qcom,msm8998-dwc3" },
+       { .compatible = "qcom,sdm660-dwc3" },
        { .compatible = "qcom,sdm845-dwc3" },
        { }
 };
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 ccb68fe..804b505 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;
@@ -2508,6 +2693,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);
@@ -2595,6 +2781,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);
@@ -2616,6 +2847,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,
 };
 
@@ -4011,7 +4243,7 @@ int dwc3_gadget_init(struct dwc3 *dwc)
        }
 
 
-       usb_initialize_gadget(dwc->dev, dwc->gadget, dwc_gadget_release);
+       usb_initialize_gadget(dwc->sysdev, dwc->gadget, dwc_gadget_release);
        dev                             = &dwc->gadget->dev;
        dev->platform_data              = dwc;
        dwc->gadget->ops                = &dwc3_gadget_ops;
index 2d15257..dd58094 100644 (file)
@@ -450,6 +450,7 @@ config USB_CONFIGFS_F_UVC
        depends on USB_CONFIGFS
        depends on VIDEO_V4L2
        depends on VIDEO_DEV
+       select VIDEOBUF2_DMA_SG
        select VIDEOBUF2_VMALLOC
        select USB_F_UVC
        help
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..477e72a 100644 (file)
@@ -55,7 +55,7 @@ struct gadget_info {
 
 static inline struct gadget_info *to_gadget_info(struct config_item *item)
 {
-        return container_of(to_config_group(item), struct gadget_info, group);
+       return container_of(to_config_group(item), struct gadget_info, group);
 }
 
 struct config_usb_cfg {
@@ -365,21 +365,21 @@ static struct configfs_attribute *gadget_root_attrs[] = {
 
 static inline struct gadget_strings *to_gadget_strings(struct config_item *item)
 {
-        return container_of(to_config_group(item), struct gadget_strings,
+       return container_of(to_config_group(item), struct gadget_strings,
                         group);
 }
 
 static inline struct gadget_config_name *to_gadget_config_name(
                struct config_item *item)
 {
-        return container_of(to_config_group(item), struct gadget_config_name,
+       return container_of(to_config_group(item), struct gadget_config_name,
                         group);
 }
 
 static inline struct usb_function_instance *to_usb_function_instance(
                struct config_item *item)
 {
-        return container_of(to_config_group(item),
+       return container_of(to_config_group(item),
                         struct usb_function_instance, group);
 }
 
@@ -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 9c0c393..8260f38 100644 (file)
@@ -2081,7 +2081,7 @@ static int __must_check ffs_do_single_desc(char *data, unsigned len,
                break;
 
        case USB_TYPE_CLASS | 0x01:
-                if (*current_class == USB_INTERFACE_CLASS_HID) {
+               if (*current_class == USB_INTERFACE_CLASS_HID) {
                        pr_vdebug("hid descriptor\n");
                        if (length != sizeof(struct hid_descriptor))
                                goto inv_length;
@@ -3831,7 +3831,7 @@ static char *ffs_prepare_buffer(const char __user *buf, size_t len)
 
        data = memdup_user(buf, len);
        if (IS_ERR(data))
-               return ERR_PTR(PTR_ERR(data));
+               return data;
 
        pr_vdebug("Buffer from user space:\n");
        ffs_dump_mem("", data, len);
index bb476e1..ca0a7d9 100644 (file)
@@ -45,12 +45,25 @@ struct f_hidg {
        unsigned short                  report_desc_length;
        char                            *report_desc;
        unsigned short                  report_length;
+       /*
+        * use_out_ep - if true, the OUT Endpoint (interrupt out method)
+        *              will be used to receive reports from the host
+        *              using functions with the "intout" suffix.
+        *              Otherwise, the OUT Endpoint will not be configured
+        *              and the SETUP/SET_REPORT method ("ssreport" suffix)
+        *              will be used to receive reports.
+        */
+       bool                            use_out_ep;
 
        /* recv report */
-       struct list_head                completed_out_req;
        spinlock_t                      read_spinlock;
        wait_queue_head_t               read_queue;
+       /* recv report - interrupt out only (use_out_ep == 1) */
+       struct list_head                completed_out_req;
        unsigned int                    qlen;
+       /* recv report - setup set_report only (use_out_ep == 0) */
+       char                            *set_report_buf;
+       unsigned int                    set_report_length;
 
        /* send report */
        spinlock_t                      write_spinlock;
@@ -79,7 +92,7 @@ static struct usb_interface_descriptor hidg_interface_desc = {
        .bDescriptorType        = USB_DT_INTERFACE,
        /* .bInterfaceNumber    = DYNAMIC */
        .bAlternateSetting      = 0,
-       .bNumEndpoints          = 2,
+       /* .bNumEndpoints       = DYNAMIC (depends on use_out_ep) */
        .bInterfaceClass        = USB_CLASS_HID,
        /* .bInterfaceSubClass  = DYNAMIC */
        /* .bInterfaceProtocol  = DYNAMIC */
@@ -140,7 +153,7 @@ static struct usb_ss_ep_comp_descriptor hidg_ss_out_comp_desc = {
        /* .wBytesPerInterval   = DYNAMIC */
 };
 
-static struct usb_descriptor_header *hidg_ss_descriptors[] = {
+static struct usb_descriptor_header *hidg_ss_descriptors_intout[] = {
        (struct usb_descriptor_header *)&hidg_interface_desc,
        (struct usb_descriptor_header *)&hidg_desc,
        (struct usb_descriptor_header *)&hidg_ss_in_ep_desc,
@@ -150,6 +163,14 @@ static struct usb_descriptor_header *hidg_ss_descriptors[] = {
        NULL,
 };
 
+static struct usb_descriptor_header *hidg_ss_descriptors_ssreport[] = {
+       (struct usb_descriptor_header *)&hidg_interface_desc,
+       (struct usb_descriptor_header *)&hidg_desc,
+       (struct usb_descriptor_header *)&hidg_ss_in_ep_desc,
+       (struct usb_descriptor_header *)&hidg_ss_in_comp_desc,
+       NULL,
+};
+
 /* High-Speed Support */
 
 static struct usb_endpoint_descriptor hidg_hs_in_ep_desc = {
@@ -176,7 +197,7 @@ static struct usb_endpoint_descriptor hidg_hs_out_ep_desc = {
                                      */
 };
 
-static struct usb_descriptor_header *hidg_hs_descriptors[] = {
+static struct usb_descriptor_header *hidg_hs_descriptors_intout[] = {
        (struct usb_descriptor_header *)&hidg_interface_desc,
        (struct usb_descriptor_header *)&hidg_desc,
        (struct usb_descriptor_header *)&hidg_hs_in_ep_desc,
@@ -184,6 +205,13 @@ static struct usb_descriptor_header *hidg_hs_descriptors[] = {
        NULL,
 };
 
+static struct usb_descriptor_header *hidg_hs_descriptors_ssreport[] = {
+       (struct usb_descriptor_header *)&hidg_interface_desc,
+       (struct usb_descriptor_header *)&hidg_desc,
+       (struct usb_descriptor_header *)&hidg_hs_in_ep_desc,
+       NULL,
+};
+
 /* Full-Speed Support */
 
 static struct usb_endpoint_descriptor hidg_fs_in_ep_desc = {
@@ -210,7 +238,7 @@ static struct usb_endpoint_descriptor hidg_fs_out_ep_desc = {
                                       */
 };
 
-static struct usb_descriptor_header *hidg_fs_descriptors[] = {
+static struct usb_descriptor_header *hidg_fs_descriptors_intout[] = {
        (struct usb_descriptor_header *)&hidg_interface_desc,
        (struct usb_descriptor_header *)&hidg_desc,
        (struct usb_descriptor_header *)&hidg_fs_in_ep_desc,
@@ -218,6 +246,13 @@ static struct usb_descriptor_header *hidg_fs_descriptors[] = {
        NULL,
 };
 
+static struct usb_descriptor_header *hidg_fs_descriptors_ssreport[] = {
+       (struct usb_descriptor_header *)&hidg_interface_desc,
+       (struct usb_descriptor_header *)&hidg_desc,
+       (struct usb_descriptor_header *)&hidg_fs_in_ep_desc,
+       NULL,
+};
+
 /*-------------------------------------------------------------------------*/
 /*                                 Strings                                 */
 
@@ -241,8 +276,8 @@ static struct usb_gadget_strings *ct_func_strings[] = {
 /*-------------------------------------------------------------------------*/
 /*                              Char Device                                */
 
-static ssize_t f_hidg_read(struct file *file, char __user *buffer,
-                       size_t count, loff_t *ptr)
+static ssize_t f_hidg_intout_read(struct file *file, char __user *buffer,
+                                 size_t count, loff_t *ptr)
 {
        struct f_hidg *hidg = file->private_data;
        struct f_hidg_req_list *list;
@@ -255,15 +290,15 @@ static ssize_t f_hidg_read(struct file *file, char __user *buffer,
 
        spin_lock_irqsave(&hidg->read_spinlock, flags);
 
-#define READ_COND (!list_empty(&hidg->completed_out_req))
+#define READ_COND_INTOUT (!list_empty(&hidg->completed_out_req))
 
        /* wait for at least one buffer to complete */
-       while (!READ_COND) {
+       while (!READ_COND_INTOUT) {
                spin_unlock_irqrestore(&hidg->read_spinlock, flags);
                if (file->f_flags & O_NONBLOCK)
                        return -EAGAIN;
 
-               if (wait_event_interruptible(hidg->read_queue, READ_COND))
+               if (wait_event_interruptible(hidg->read_queue, READ_COND_INTOUT))
                        return -ERESTARTSYS;
 
                spin_lock_irqsave(&hidg->read_spinlock, flags);
@@ -313,6 +348,60 @@ static ssize_t f_hidg_read(struct file *file, char __user *buffer,
        return count;
 }
 
+#define READ_COND_SSREPORT (hidg->set_report_buf != NULL)
+
+static ssize_t f_hidg_ssreport_read(struct file *file, char __user *buffer,
+                                   size_t count, loff_t *ptr)
+{
+       struct f_hidg *hidg = file->private_data;
+       char *tmp_buf = NULL;
+       unsigned long flags;
+
+       if (!count)
+               return 0;
+
+       spin_lock_irqsave(&hidg->read_spinlock, flags);
+
+       while (!READ_COND_SSREPORT) {
+               spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+               if (file->f_flags & O_NONBLOCK)
+                       return -EAGAIN;
+
+               if (wait_event_interruptible(hidg->read_queue, READ_COND_SSREPORT))
+                       return -ERESTARTSYS;
+
+               spin_lock_irqsave(&hidg->read_spinlock, flags);
+       }
+
+       count = min_t(unsigned int, count, hidg->set_report_length);
+       tmp_buf = hidg->set_report_buf;
+       hidg->set_report_buf = NULL;
+
+       spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+
+       if (tmp_buf != NULL) {
+               count -= copy_to_user(buffer, tmp_buf, count);
+               kfree(tmp_buf);
+       } else {
+               count = -ENOMEM;
+       }
+
+       wake_up(&hidg->read_queue);
+
+       return count;
+}
+
+static ssize_t f_hidg_read(struct file *file, char __user *buffer,
+                          size_t count, loff_t *ptr)
+{
+       struct f_hidg *hidg = file->private_data;
+
+       if (hidg->use_out_ep)
+               return f_hidg_intout_read(file, buffer, count, ptr);
+       else
+               return f_hidg_ssreport_read(file, buffer, count, ptr);
+}
+
 static void f_hidg_req_complete(struct usb_ep *ep, struct usb_request *req)
 {
        struct f_hidg *hidg = (struct f_hidg *)ep->driver_data;
@@ -433,14 +522,20 @@ static __poll_t f_hidg_poll(struct file *file, poll_table *wait)
        if (WRITE_COND)
                ret |= EPOLLOUT | EPOLLWRNORM;
 
-       if (READ_COND)
-               ret |= EPOLLIN | EPOLLRDNORM;
+       if (hidg->use_out_ep) {
+               if (READ_COND_INTOUT)
+                       ret |= EPOLLIN | EPOLLRDNORM;
+       } else {
+               if (READ_COND_SSREPORT)
+                       ret |= EPOLLIN | EPOLLRDNORM;
+       }
 
        return ret;
 }
 
 #undef WRITE_COND
-#undef READ_COND
+#undef READ_COND_SSREPORT
+#undef READ_COND_INTOUT
 
 static int f_hidg_release(struct inode *inode, struct file *fd)
 {
@@ -467,7 +562,7 @@ static inline struct usb_request *hidg_alloc_ep_req(struct usb_ep *ep,
        return alloc_ep_req(ep, length);
 }
 
-static void hidg_set_report_complete(struct usb_ep *ep, struct usb_request *req)
+static void hidg_intout_complete(struct usb_ep *ep, struct usb_request *req)
 {
        struct f_hidg *hidg = (struct f_hidg *) req->context;
        struct usb_composite_dev *cdev = hidg->func.config->cdev;
@@ -502,6 +597,37 @@ free_req:
        }
 }
 
+static void hidg_ssreport_complete(struct usb_ep *ep, struct usb_request *req)
+{
+       struct f_hidg *hidg = (struct f_hidg *)req->context;
+       struct usb_composite_dev *cdev = hidg->func.config->cdev;
+       char *new_buf = NULL;
+       unsigned long flags;
+
+       if (req->status != 0 || req->buf == NULL || req->actual == 0) {
+               ERROR(cdev,
+                     "%s FAILED: status=%d, buf=%p, actual=%d\n",
+                     __func__, req->status, req->buf, req->actual);
+               return;
+       }
+
+       spin_lock_irqsave(&hidg->read_spinlock, flags);
+
+       new_buf = krealloc(hidg->set_report_buf, req->actual, GFP_ATOMIC);
+       if (new_buf == NULL) {
+               spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+               return;
+       }
+       hidg->set_report_buf = new_buf;
+
+       hidg->set_report_length = req->actual;
+       memcpy(hidg->set_report_buf, req->buf, req->actual);
+
+       spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+
+       wake_up(&hidg->read_queue);
+}
+
 static int hidg_setup(struct usb_function *f,
                const struct usb_ctrlrequest *ctrl)
 {
@@ -549,7 +675,11 @@ static int hidg_setup(struct usb_function *f,
        case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
                  | HID_REQ_SET_REPORT):
                VDBG(cdev, "set_report | wLength=%d\n", ctrl->wLength);
-               goto stall;
+               if (hidg->use_out_ep)
+                       goto stall;
+               req->complete = hidg_ssreport_complete;
+               req->context  = hidg;
+               goto respond;
                break;
 
        case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
@@ -637,15 +767,18 @@ static void hidg_disable(struct usb_function *f)
        unsigned long flags;
 
        usb_ep_disable(hidg->in_ep);
-       usb_ep_disable(hidg->out_ep);
 
-       spin_lock_irqsave(&hidg->read_spinlock, flags);
-       list_for_each_entry_safe(list, next, &hidg->completed_out_req, list) {
-               free_ep_req(hidg->out_ep, list->req);
-               list_del(&list->list);
-               kfree(list);
+       if (hidg->out_ep) {
+               usb_ep_disable(hidg->out_ep);
+
+               spin_lock_irqsave(&hidg->read_spinlock, flags);
+               list_for_each_entry_safe(list, next, &hidg->completed_out_req, list) {
+                       free_ep_req(hidg->out_ep, list->req);
+                       list_del(&list->list);
+                       kfree(list);
+               }
+               spin_unlock_irqrestore(&hidg->read_spinlock, flags);
        }
-       spin_unlock_irqrestore(&hidg->read_spinlock, flags);
 
        spin_lock_irqsave(&hidg->write_spinlock, flags);
        if (!hidg->write_pending) {
@@ -691,8 +824,7 @@ static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
                }
        }
 
-
-       if (hidg->out_ep != NULL) {
+       if (hidg->use_out_ep && hidg->out_ep != NULL) {
                /* restart endpoint */
                usb_ep_disable(hidg->out_ep);
 
@@ -717,7 +849,7 @@ static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
                                        hidg_alloc_ep_req(hidg->out_ep,
                                                          hidg->report_length);
                        if (req) {
-                               req->complete = hidg_set_report_complete;
+                               req->complete = hidg_intout_complete;
                                req->context  = hidg;
                                status = usb_ep_queue(hidg->out_ep, req,
                                                      GFP_ATOMIC);
@@ -743,7 +875,8 @@ static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
        }
        return 0;
 disable_out_ep:
-       usb_ep_disable(hidg->out_ep);
+       if (hidg->out_ep)
+               usb_ep_disable(hidg->out_ep);
 free_req_in:
        if (req_in)
                free_ep_req(hidg->in_ep, req_in);
@@ -795,14 +928,21 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f)
                goto fail;
        hidg->in_ep = ep;
 
-       ep = usb_ep_autoconfig(c->cdev->gadget, &hidg_fs_out_ep_desc);
-       if (!ep)
-               goto fail;
-       hidg->out_ep = ep;
+       hidg->out_ep = NULL;
+       if (hidg->use_out_ep) {
+               ep = usb_ep_autoconfig(c->cdev->gadget, &hidg_fs_out_ep_desc);
+               if (!ep)
+                       goto fail;
+               hidg->out_ep = ep;
+       }
+
+       /* used only if use_out_ep == 1 */
+       hidg->set_report_buf = NULL;
 
        /* set descriptor dynamic values */
        hidg_interface_desc.bInterfaceSubClass = hidg->bInterfaceSubClass;
        hidg_interface_desc.bInterfaceProtocol = hidg->bInterfaceProtocol;
+       hidg_interface_desc.bNumEndpoints = hidg->use_out_ep ? 2 : 1;
        hidg->protocol = HID_REPORT_PROTOCOL;
        hidg->idle = 1;
        hidg_ss_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length);
@@ -833,9 +973,19 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f)
        hidg_ss_out_ep_desc.bEndpointAddress =
                hidg_fs_out_ep_desc.bEndpointAddress;
 
-       status = usb_assign_descriptors(f, hidg_fs_descriptors,
-                       hidg_hs_descriptors, hidg_ss_descriptors,
-                       hidg_ss_descriptors);
+       if (hidg->use_out_ep)
+               status = usb_assign_descriptors(f,
+                       hidg_fs_descriptors_intout,
+                       hidg_hs_descriptors_intout,
+                       hidg_ss_descriptors_intout,
+                       hidg_ss_descriptors_intout);
+       else
+               status = usb_assign_descriptors(f,
+                       hidg_fs_descriptors_ssreport,
+                       hidg_hs_descriptors_ssreport,
+                       hidg_ss_descriptors_ssreport,
+                       hidg_ss_descriptors_ssreport);
+
        if (status)
                goto fail;
 
@@ -950,6 +1100,7 @@ CONFIGFS_ATTR(f_hid_opts_, name)
 
 F_HID_OPT(subclass, 8, 255);
 F_HID_OPT(protocol, 8, 255);
+F_HID_OPT(no_out_endpoint, 8, 1);
 F_HID_OPT(report_length, 16, 65535);
 
 static ssize_t f_hid_opts_report_desc_show(struct config_item *item, char *page)
@@ -1009,6 +1160,7 @@ CONFIGFS_ATTR_RO(f_hid_opts_, dev);
 static struct configfs_attribute *hid_attrs[] = {
        &f_hid_opts_attr_subclass,
        &f_hid_opts_attr_protocol,
+       &f_hid_opts_attr_no_out_endpoint,
        &f_hid_opts_attr_report_length,
        &f_hid_opts_attr_report_desc,
        &f_hid_opts_attr_dev,
@@ -1093,6 +1245,7 @@ static void hidg_free(struct usb_function *f)
        hidg = func_to_hidg(f);
        opts = container_of(f->fi, struct f_hid_opts, func_inst);
        kfree(hidg->report_desc);
+       kfree(hidg->set_report_buf);
        kfree(hidg);
        mutex_lock(&opts->lock);
        --opts->refcnt;
@@ -1139,6 +1292,7 @@ static struct usb_function *hidg_alloc(struct usb_function_instance *fi)
                        return ERR_PTR(-ENOMEM);
                }
        }
+       hidg->use_out_ep = !opts->no_out_endpoint;
 
        mutex_unlock(&opts->lock);
 
index 4a47036..6ad669d 100644 (file)
@@ -6,36 +6,6 @@
  * Copyright (C) 2009 Samsung Electronics
  *                    Author: Michal Nazarewicz <mina86@mina86.com>
  * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions, and the following disclaimer,
- *    without modification.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. The names of the above-listed copyright holders may not be used
- *    to endorse or promote products derived from this software without
- *    specific prior written permission.
- *
- * ALTERNATIVELY, this software may be distributed under the terms of the
- * GNU General Public License ("GPL") as published by the Free Software
- * Foundation, either version 2 of that License or (at your option) any
- * later version.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
- * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
- * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 /*
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..5b3502d 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..3c34995 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,17 @@ 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 +
+                               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 +
+                               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 +994,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 +1021,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 +1033,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 +1062,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 +1070,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 +1128,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 +1137,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 +1147,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 +1164,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 +1303,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 +1320,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 +1382,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 +1391,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 +1400,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 (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID))
+                       is_playback = 1;
 
-               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 (control_selector == UAC_FU_MUTE) {
+                       unsigned int mute;
 
-               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);
+                       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 +1468,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 +1555,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 +1706,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 +1722,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 +1733,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 +1741,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 +1811,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 +1843,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 +1894,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 +1933,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 +1970,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 f48a00e..9d87c0f 100644 (file)
@@ -418,6 +418,7 @@ uvc_register_video(struct uvc_device *uvc)
 
        /* TODO reference counting. */
        uvc->vdev.v4l2_dev = &uvc->v4l2_dev;
+       uvc->vdev.v4l2_dev->dev = &cdev->gadget->dev;
        uvc->vdev.fops = &uvc_v4l2_fops;
        uvc->vdev.ioctl_ops = &uvc_v4l2_ioctl_ops;
        uvc->vdev.release = video_device_release_empty;
index 9e5c950..32ef228 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 {
@@ -604,6 +624,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)
 {
@@ -663,14 +780,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,
@@ -682,7 +943,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;
@@ -700,7 +961,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,
@@ -723,6 +985,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;
 
@@ -767,10 +1030,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;
@@ -784,6 +1055,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 98d6af5..84bb702 100644 (file)
@@ -20,6 +20,7 @@ struct f_hid_opts {
        int                             minor;
        unsigned char                   subclass;
        unsigned char                   protocol;
+       unsigned char                   no_out_endpoint;
        unsigned short                  report_length;
        unsigned short                  report_desc_length;
        unsigned char                   *report_desc;
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 23ee253..255a61b 100644 (file)
@@ -65,13 +65,19 @@ extern unsigned int uvc_gadget_trace_param;
  * Driver specific constants
  */
 
-#define UVC_NUM_REQUESTS                       4
 #define UVC_MAX_REQUEST_SIZE                   64
 #define UVC_MAX_EVENTS                         4
 
 /* ------------------------------------------------------------------------
  * Structures
  */
+struct uvc_request {
+       struct usb_request *req;
+       u8 *req_buffer;
+       struct uvc_video *video;
+       struct sg_table sgt;
+       u8 header[2];
+};
 
 struct uvc_video {
        struct uvc_device *uvc;
@@ -87,13 +93,16 @@ struct uvc_video {
        unsigned int imagesize;
        struct mutex mutex;     /* protects frame parameters */
 
+       unsigned int uvc_num_requests;
+
        /* Requests */
        unsigned int req_size;
-       struct usb_request *req[UVC_NUM_REQUESTS];
-       __u8 *req_buffer[UVC_NUM_REQUESTS];
+       struct uvc_request *ureq;
        struct list_head req_free;
        spinlock_t req_lock;
 
+       unsigned int req_int_count;
+
        void (*encode) (struct usb_request *req, struct uvc_video *video,
                        struct uvc_buffer *buf);
 
index 61e2c94..7d00ad7 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/wait.h>
 
 #include <media/v4l2-common.h>
+#include <media/videobuf2-dma-sg.h>
 #include <media/videobuf2-vmalloc.h>
 
 #include "uvc.h"
@@ -43,6 +44,7 @@ static int uvc_queue_setup(struct vb2_queue *vq,
 {
        struct uvc_video_queue *queue = vb2_get_drv_priv(vq);
        struct uvc_video *video = container_of(queue, struct uvc_video, queue);
+       struct usb_composite_dev *cdev = video->uvc->func.config->cdev;
 
        if (*nbuffers > UVC_MAX_VIDEO_BUFFERS)
                *nbuffers = UVC_MAX_VIDEO_BUFFERS;
@@ -51,6 +53,11 @@ static int uvc_queue_setup(struct vb2_queue *vq,
 
        sizes[0] = video->imagesize;
 
+       if (cdev->gadget->speed < USB_SPEED_SUPER)
+               video->uvc_num_requests = 4;
+       else
+               video->uvc_num_requests = 64;
+
        return 0;
 }
 
@@ -70,7 +77,12 @@ static int uvc_buffer_prepare(struct vb2_buffer *vb)
                return -ENODEV;
 
        buf->state = UVC_BUF_STATE_QUEUED;
-       buf->mem = vb2_plane_vaddr(vb, 0);
+       if (queue->use_sg) {
+               buf->sgt = vb2_dma_sg_plane_desc(vb, 0);
+               buf->sg = buf->sgt->sgl;
+       } else {
+               buf->mem = vb2_plane_vaddr(vb, 0);
+       }
        buf->length = vb2_plane_size(vb, 0);
        if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
                buf->bytesused = 0;
@@ -110,9 +122,11 @@ static const struct vb2_ops uvc_queue_qops = {
        .wait_finish = vb2_ops_wait_finish,
 };
 
-int uvcg_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type,
+int uvcg_queue_init(struct uvc_video_queue *queue, struct device *dev, enum v4l2_buf_type type,
                    struct mutex *lock)
 {
+       struct uvc_video *video = container_of(queue, struct uvc_video, queue);
+       struct usb_composite_dev *cdev = video->uvc->func.config->cdev;
        int ret;
 
        queue->queue.type = type;
@@ -121,9 +135,17 @@ int uvcg_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type,
        queue->queue.buf_struct_size = sizeof(struct uvc_buffer);
        queue->queue.ops = &uvc_queue_qops;
        queue->queue.lock = lock;
-       queue->queue.mem_ops = &vb2_vmalloc_memops;
+       if (cdev->gadget->sg_supported) {
+               queue->queue.mem_ops = &vb2_dma_sg_memops;
+               queue->use_sg = 1;
+       } else {
+               queue->queue.mem_ops = &vb2_vmalloc_memops;
+       }
+
        queue->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC
                                     | V4L2_BUF_FLAG_TSTAMP_SRC_EOF;
+       queue->queue.dev = dev;
+
        ret = vb2_queue_init(&queue->queue);
        if (ret)
                return ret;
index 2f0fff7..05360a0 100644 (file)
@@ -34,6 +34,9 @@ struct uvc_buffer {
 
        enum uvc_buffer_state state;
        void *mem;
+       struct sg_table *sgt;
+       struct scatterlist *sg;
+       unsigned int offset;
        unsigned int length;
        unsigned int bytesused;
 };
@@ -50,6 +53,8 @@ struct uvc_video_queue {
 
        unsigned int buf_used;
 
+       bool use_sg;
+
        spinlock_t irqlock;     /* Protects flags and irqqueue */
        struct list_head irqqueue;
 };
@@ -59,7 +64,7 @@ static inline int uvc_queue_streaming(struct uvc_video_queue *queue)
        return vb2_is_streaming(&queue->queue);
 }
 
-int uvcg_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type,
+int uvcg_queue_init(struct uvc_video_queue *queue, struct device *dev, enum v4l2_buf_type type,
                    struct mutex *lock);
 
 void uvcg_free_buffers(struct uvc_video_queue *queue);
index 633e23d..b4a763e 100644 (file)
@@ -27,10 +27,10 @@ static int
 uvc_video_encode_header(struct uvc_video *video, struct uvc_buffer *buf,
                u8 *data, int len)
 {
-       data[0] = 2;
+       data[0] = UVCG_REQUEST_HEADER_LEN;
        data[1] = UVC_STREAM_EOH | video->fid;
 
-       if (buf->bytesused - video->queue.buf_used <= len - 2)
+       if (buf->bytesused - video->queue.buf_used <= len - UVCG_REQUEST_HEADER_LEN)
                data[1] |= UVC_STREAM_EOF;
 
        return 2;
@@ -94,6 +94,71 @@ uvc_video_encode_bulk(struct usb_request *req, struct uvc_video *video,
                video->payload_size = 0;
 }
 
+static void
+uvc_video_encode_isoc_sg(struct usb_request *req, struct uvc_video *video,
+               struct uvc_buffer *buf)
+{
+       unsigned int pending = buf->bytesused - video->queue.buf_used;
+       struct uvc_request *ureq = req->context;
+       struct scatterlist *sg, *iter;
+       unsigned int len = video->req_size;
+       unsigned int sg_left, part = 0;
+       unsigned int i;
+       int ret;
+
+       sg = ureq->sgt.sgl;
+       sg_init_table(sg, ureq->sgt.nents);
+
+       /* Init the header. */
+       ret = uvc_video_encode_header(video, buf, ureq->header,
+                                     video->req_size);
+       sg_set_buf(sg, ureq->header, UVCG_REQUEST_HEADER_LEN);
+       len -= ret;
+
+       if (pending <= len)
+               len = pending;
+
+       req->length = (len == pending) ?
+               len + UVCG_REQUEST_HEADER_LEN : video->req_size;
+
+       /* Init the pending sgs with payload */
+       sg = sg_next(sg);
+
+       for_each_sg(sg, iter, ureq->sgt.nents - 1, i) {
+               if (!len || !buf->sg)
+                       break;
+
+               sg_left = sg_dma_len(buf->sg) - buf->offset;
+               part = min_t(unsigned int, len, sg_left);
+
+               sg_set_page(iter, sg_page(buf->sg), part, buf->offset);
+
+               if (part == sg_left) {
+                       buf->offset = 0;
+                       buf->sg = sg_next(buf->sg);
+               } else {
+                       buf->offset += part;
+               }
+               len -= part;
+       }
+
+       /* Assign the video data with header. */
+       req->buf = NULL;
+       req->sg = ureq->sgt.sgl;
+       req->num_sgs = i + 1;
+
+       req->length -= len;
+       video->queue.buf_used += req->length - UVCG_REQUEST_HEADER_LEN;
+
+       if (buf->bytesused == video->queue.buf_used || !buf->sg) {
+               video->queue.buf_used = 0;
+               buf->state = UVC_BUF_STATE_DONE;
+               buf->offset = 0;
+               uvcg_queue_next_buffer(&video->queue, buf);
+               video->fid ^= UVC_STREAM_FID;
+       }
+}
+
 static void
 uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
                struct uvc_buffer *buf)
@@ -145,7 +210,8 @@ static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
 static void
 uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 {
-       struct uvc_video *video = req->context;
+       struct uvc_request *ureq = req->context;
+       struct uvc_video *video = ureq->video;
        struct uvc_video_queue *queue = &video->queue;
        unsigned long flags;
 
@@ -177,16 +243,23 @@ uvc_video_free_requests(struct uvc_video *video)
 {
        unsigned int i;
 
-       for (i = 0; i < UVC_NUM_REQUESTS; ++i) {
-               if (video->req[i]) {
-                       usb_ep_free_request(video->ep, video->req[i]);
-                       video->req[i] = NULL;
-               }
+       if (video->ureq) {
+               for (i = 0; i < video->uvc_num_requests; ++i) {
+                       sg_free_table(&video->ureq[i].sgt);
+
+                       if (video->ureq[i].req) {
+                               usb_ep_free_request(video->ep, video->ureq[i].req);
+                               video->ureq[i].req = NULL;
+                       }
 
-               if (video->req_buffer[i]) {
-                       kfree(video->req_buffer[i]);
-                       video->req_buffer[i] = NULL;
+                       if (video->ureq[i].req_buffer) {
+                               kfree(video->ureq[i].req_buffer);
+                               video->ureq[i].req_buffer = NULL;
+                       }
                }
+
+               kfree(video->ureq);
+               video->ureq = NULL;
        }
 
        INIT_LIST_HEAD(&video->req_free);
@@ -207,21 +280,30 @@ uvc_video_alloc_requests(struct uvc_video *video)
                 * max_t(unsigned int, video->ep->maxburst, 1)
                 * (video->ep->mult);
 
-       for (i = 0; i < UVC_NUM_REQUESTS; ++i) {
-               video->req_buffer[i] = kmalloc(req_size, GFP_KERNEL);
-               if (video->req_buffer[i] == NULL)
-                       goto error;
+       video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
+       if (video->ureq == NULL)
+               return -ENOMEM;
 
-               video->req[i] = usb_ep_alloc_request(video->ep, GFP_KERNEL);
-               if (video->req[i] == NULL)
+       for (i = 0; i < video->uvc_num_requests; ++i) {
+               video->ureq[i].req_buffer = kmalloc(req_size, GFP_KERNEL);
+               if (video->ureq[i].req_buffer == NULL)
                        goto error;
 
-               video->req[i]->buf = video->req_buffer[i];
-               video->req[i]->length = 0;
-               video->req[i]->complete = uvc_video_complete;
-               video->req[i]->context = video;
+               video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
+               if (video->ureq[i].req == NULL)
+                       goto error;
 
-               list_add_tail(&video->req[i]->list, &video->req_free);
+               video->ureq[i].req->buf = video->ureq[i].req_buffer;
+               video->ureq[i].req->length = 0;
+               video->ureq[i].req->complete = uvc_video_complete;
+               video->ureq[i].req->context = &video->ureq[i];
+               video->ureq[i].video = video;
+
+               list_add_tail(&video->ureq[i].req->list, &video->req_free);
+               /* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
+               sg_alloc_table(&video->ureq[i].sgt,
+                              DIV_ROUND_UP(req_size - 2, PAGE_SIZE) + 2,
+                              GFP_KERNEL);
        }
 
        video->req_size = req_size;
@@ -278,6 +360,19 @@ static void uvcg_video_pump(struct work_struct *work)
 
                video->encode(req, video, buf);
 
+               /* With usb3 we have more requests. This will decrease the
+                * interrupt load to a quarter but also catches the corner
+                * cases, which needs to be handled */
+               if (list_empty(&video->req_free) ||
+                   buf->state == UVC_BUF_STATE_DONE ||
+                   !(video->req_int_count %
+                      DIV_ROUND_UP(video->uvc_num_requests, 4))) {
+                       video->req_int_count = 0;
+                       req->no_interrupt = 0;
+               } else {
+                       req->no_interrupt = 1;
+               }
+
                /* Queue the USB request */
                ret = uvcg_video_ep_queue(video, req);
                spin_unlock_irqrestore(&queue->irqlock, flags);
@@ -286,6 +381,7 @@ static void uvcg_video_pump(struct work_struct *work)
                        uvcg_queue_cancel(queue, 0);
                        break;
                }
+               video->req_int_count++;
        }
 
        spin_lock_irqsave(&video->req_lock, flags);
@@ -312,9 +408,9 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
                cancel_work_sync(&video->pump);
                uvcg_queue_cancel(&video->queue, 0);
 
-               for (i = 0; i < UVC_NUM_REQUESTS; ++i)
-                       if (video->req[i])
-                               usb_ep_dequeue(video->ep, video->req[i]);
+               for (i = 0; i < video->uvc_num_requests; ++i)
+                       if (video->ureq && video->ureq[i].req)
+                               usb_ep_dequeue(video->ep, video->ureq[i].req);
 
                uvc_video_free_requests(video);
                uvcg_queue_enable(&video->queue, 0);
@@ -331,7 +427,10 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
                video->encode = uvc_video_encode_bulk;
                video->payload_size = 0;
        } else
-               video->encode = uvc_video_encode_isoc;
+               video->encode = video->queue.use_sg ?
+                       uvc_video_encode_isoc_sg : uvc_video_encode_isoc;
+
+       video->req_int_count = 0;
 
        schedule_work(&video->pump);
 
@@ -355,8 +454,8 @@ int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
        video->imagesize = 320 * 240 * 2;
 
        /* Initialize the video buffers queue. */
-       uvcg_queue_init(&video->queue, V4L2_BUF_TYPE_VIDEO_OUTPUT,
-                       &video->mutex);
+       uvcg_queue_init(&video->queue, uvc->v4l2_dev.dev->parent,
+                       V4L2_BUF_TYPE_VIDEO_OUTPUT, &video->mutex);
        return 0;
 }
 
index 03adeef..9bf1947 100644 (file)
@@ -12,6 +12,8 @@
 #ifndef __UVC_VIDEO_H__
 #define __UVC_VIDEO_H__
 
+#define UVCG_REQUEST_HEADER_LEN                        2
+
 struct uvc_video;
 
 int uvcg_video_enable(struct uvc_video *video, int enable);
index 11dd6e8..de6668e 100644 (file)
@@ -502,6 +502,7 @@ config USB_G_WEBCAM
        tristate "USB Webcam Gadget"
        depends on VIDEO_V4L2
        select USB_LIBCOMPOSITE
+       select VIDEOBUF2_DMA_SG
        select VIDEOBUF2_VMALLOC
        select USB_F_UVC
        help
index cd8e273..539220d 100644 (file)
@@ -1214,8 +1214,8 @@ dev_release (struct inode *inode, struct file *fd)
 static __poll_t
 ep0_poll (struct file *fd, poll_table *wait)
 {
-       struct dev_data         *dev = fd->private_data;
-       __poll_t                mask = 0;
+       struct dev_data         *dev = fd->private_data;
+       __poll_t                mask = 0;
 
        if (dev->state <= STATE_DEV_OPENED)
                return DEFAULT_POLLMASK;
index 2cd3895..ed762ba 100644 (file)
@@ -50,6 +50,7 @@ MODULE_PARM_DESC(iPNPstring, "MFG:linux;MDL:g_printer;CLS:PRINTER;SN:1;");
 /* Number of requests to allocate per endpoint, not used for ep0. */
 static unsigned qlen = 10;
 module_param(qlen, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(qlen, "The number of 8k buffers to use per endpoint");
 
 #define QLEN   qlen
 
index d11d3d1..7a635c4 100644 (file)
@@ -5,11 +5,6 @@
  * core.c - Top level support
  *
  * Copyright 2017 IBM Corporation
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
  */
 
 #include <linux/kernel.h>
index d268306..d918e8b 100644 (file)
@@ -5,11 +5,6 @@
  * dev.c - Individual device/gadget management (ie, a port = a gadget)
  *
  * Copyright 2017 IBM Corporation
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
  */
 
 #include <linux/kernel.h>
index 022b777..74ea36c 100644 (file)
@@ -5,11 +5,6 @@
  * ep0.c - Endpoint 0 handling
  *
  * Copyright 2017 IBM Corporation
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
  */
 
 #include <linux/kernel.h>
index cb164c6..917892c 100644 (file)
@@ -5,11 +5,6 @@
  * epn.c - Generic endpoints management
  *
  * Copyright 2017 IBM Corporation
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
  */
 
 #include <linux/kernel.h>
index 5c7dea5..b9960fd 100644 (file)
@@ -5,11 +5,6 @@
  * hub.c - virtual hub handling
  *
  * Copyright 2017 IBM Corporation
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
  */
 
 #include <linux/kernel.h>
index eede5ce..d9ad9ad 100644 (file)
@@ -1876,7 +1876,9 @@ static int at91udc_probe(struct platform_device *pdev)
        clk_disable(udc->iclk);
 
        /* request UDC and maybe VBUS irqs */
-       udc->udp_irq = platform_get_irq(pdev, 0);
+       udc->udp_irq = retval = platform_get_irq(pdev, 0);
+       if (retval < 0)
+               goto err_unprepare_iclk;
        retval = devm_request_irq(dev, udc->udp_irq, at91_udc_irq, 0,
                                  driver_name, udc);
        if (retval) {
index 995f79c..6788731 100644 (file)
@@ -153,7 +153,6 @@ int bdc_config_ep(struct bdc *bdc, struct bdc_ep *ep)
        si = clamp_val(si, 1, 16) - 1;
 
        mps = usb_endpoint_maxp(desc);
-       mps &= 0x7ff;
        param2 |= mps << MP_SHIFT;
        param2 |= usb_endpoint_type(desc) << EPT_SHIFT;
 
index 0bef6b3..fa1a390 100644 (file)
@@ -488,27 +488,14 @@ static int bdc_probe(struct platform_device *pdev)
        int irq;
        u32 temp;
        struct device *dev = &pdev->dev;
-       struct clk *clk;
        int phy_num;
 
        dev_dbg(dev, "%s()\n", __func__);
 
-       clk = devm_clk_get_optional(dev, "sw_usbd");
-       if (IS_ERR(clk))
-               return PTR_ERR(clk);
-
-       ret = clk_prepare_enable(clk);
-       if (ret) {
-               dev_err(dev, "could not enable clock\n");
-               return ret;
-       }
-
        bdc = devm_kzalloc(dev, sizeof(*bdc), GFP_KERNEL);
        if (!bdc)
                return -ENOMEM;
 
-       bdc->clk = clk;
-
        bdc->regs = devm_platform_ioremap_resource(pdev, 0);
        if (IS_ERR(bdc->regs))
                return PTR_ERR(bdc->regs);
@@ -545,10 +532,20 @@ static int bdc_probe(struct platform_device *pdev)
                }
        }
 
+       bdc->clk = devm_clk_get_optional(dev, "sw_usbd");
+       if (IS_ERR(bdc->clk))
+               return PTR_ERR(bdc->clk);
+
+       ret = clk_prepare_enable(bdc->clk);
+       if (ret) {
+               dev_err(dev, "could not enable clock\n");
+               return ret;
+       }
+
        ret = bdc_phy_init(bdc);
        if (ret) {
                dev_err(bdc->dev, "BDC phy init failure:%d\n", ret);
-               return ret;
+               goto disable_clk;
        }
 
        temp = bdc_readl(bdc->regs, BDC_BDCCAP1);
@@ -560,7 +557,8 @@ static int bdc_probe(struct platform_device *pdev)
                if (ret) {
                        dev_err(dev,
                                "No suitable DMA config available, abort\n");
-                       return -ENOTSUPP;
+                       ret = -ENOTSUPP;
+                       goto phycleanup;
                }
                dev_dbg(dev, "Using 32-bit address\n");
        }
@@ -580,6 +578,8 @@ cleanup:
        bdc_hw_exit(bdc);
 phycleanup:
        bdc_phy_exit(bdc);
+disable_clk:
+       clk_disable_unprepare(bdc->clk);
        return ret;
 }
 
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 ce3d7a3..a1057dd 100644 (file)
@@ -1921,14 +1921,6 @@ static int mv_u3d_probe(struct platform_device *dev)
                goto err_get_irq;
        }
        u3d->irq = r->start;
-       if (request_irq(u3d->irq, mv_u3d_irq,
-               IRQF_SHARED, driver_name, u3d)) {
-               u3d->irq = 0;
-               dev_err(&dev->dev, "Request irq %d for u3d failed\n",
-                       u3d->irq);
-               retval = -ENODEV;
-               goto err_request_irq;
-       }
 
        /* initialize gadget structure */
        u3d->gadget.ops = &mv_u3d_ops;  /* usb_gadget_ops */
@@ -1941,6 +1933,15 @@ static int mv_u3d_probe(struct platform_device *dev)
 
        mv_u3d_eps_init(u3d);
 
+       if (request_irq(u3d->irq, mv_u3d_irq,
+               IRQF_SHARED, driver_name, u3d)) {
+               u3d->irq = 0;
+               dev_err(&dev->dev, "Request irq %d for u3d failed\n",
+                       u3d->irq);
+               retval = -ENODEV;
+               goto err_request_irq;
+       }
+
        /* external vbus detection */
        if (u3d->vbus) {
                u3d->clock_gating = 1;
@@ -1964,8 +1965,8 @@ static int mv_u3d_probe(struct platform_device *dev)
 
 err_unregister:
        free_irq(u3d->irq, u3d);
-err_request_irq:
 err_get_irq:
+err_request_irq:
        kfree(u3d->status_req);
 err_alloc_status_req:
        kfree(u3d->eps);
index 69ef1e6..a09ec1d 100644 (file)
@@ -1093,7 +1093,7 @@ static void pxa25x_ep_fifo_flush(struct usb_ep *_ep)
 }
 
 
-static struct usb_ep_ops pxa25x_ep_ops = {
+static const struct usb_ep_ops pxa25x_ep_ops = {
        .enable         = pxa25x_ep_enable,
        .disable        = pxa25x_ep_disable,
 
index f1b35a3..57d417a 100644 (file)
@@ -2707,10 +2707,15 @@ static const struct renesas_usb3_priv renesas_usb3_priv_r8a77990 = {
 
 static const struct of_device_id usb3_of_match[] = {
        {
+               .compatible = "renesas,r8a774c0-usb3-peri",
+               .data = &renesas_usb3_priv_r8a77990,
+       }, {
                .compatible = "renesas,r8a7795-usb3-peri",
                .data = &renesas_usb3_priv_gen3,
-       },
-       {
+       }, {
+               .compatible = "renesas,r8a77990-usb3-peri",
+               .data = &renesas_usb3_priv_r8a77990,
+       }, {
                .compatible = "renesas,rcar-gen3-usb3-peri",
                .data = &renesas_usb3_priv_gen3,
        },
@@ -2719,18 +2724,10 @@ static const struct of_device_id usb3_of_match[] = {
 MODULE_DEVICE_TABLE(of, usb3_of_match);
 
 static const struct soc_device_attribute renesas_usb3_quirks_match[] = {
-       {
-               .soc_id = "r8a774c0",
-               .data = &renesas_usb3_priv_r8a77990,
-       },
        {
                .soc_id = "r8a7795", .revision = "ES1.*",
                .data = &renesas_usb3_priv_r8a7795_es1,
        },
-       {
-               .soc_id = "r8a77990",
-               .data = &renesas_usb3_priv_r8a77990,
-       },
        { /* sentinel */ },
 };
 
index 179777c..e3931da 100644 (file)
@@ -1784,6 +1784,10 @@ static int s3c2410_udc_probe(struct platform_device *pdev)
        s3c2410_udc_reinit(udc);
 
        irq_usbd = platform_get_irq(pdev, 0);
+       if (irq_usbd < 0) {
+               retval = irq_usbd;
+               goto err_udc_clk;
+       }
 
        /* irq setup after old hardware state is cleaned up */
        retval = request_irq(irq_usbd, s3c2410_udc_irq,
index c0ca714..43f1b0d 100644 (file)
@@ -1610,7 +1610,7 @@ static void tegra_xudc_ep_context_setup(struct tegra_xudc_ep *ep)
        u16 maxpacket, maxburst = 0, esit = 0;
        u32 val;
 
-       maxpacket = usb_endpoint_maxp(desc) & 0x7ff;
+       maxpacket = usb_endpoint_maxp(desc);
        if (xudc->gadget.speed == USB_SPEED_SUPER) {
                if (!usb_endpoint_xfer_control(desc))
                        maxburst = comp_desc->bMaxBurst;
@@ -1621,7 +1621,7 @@ static void tegra_xudc_ep_context_setup(struct tegra_xudc_ep *ep)
                   (usb_endpoint_xfer_int(desc) ||
                    usb_endpoint_xfer_isoc(desc))) {
                if (xudc->gadget.speed == USB_SPEED_HIGH) {
-                       maxburst = (usb_endpoint_maxp(desc) >> 11) & 0x3;
+                       maxburst = usb_endpoint_maxp_mult(desc) - 1;
                        if (maxburst == 0x3) {
                                dev_warn(xudc->dev,
                                         "invalid endpoint maxburst\n");
index df9428f..c4736d1 100644 (file)
@@ -187,15 +187,6 @@ config USB_EHCI_PCI
        depends on USB_PCI
        default y
 
-config USB_EHCI_HCD_PMC_MSP
-       tristate "EHCI support for on-chip PMC MSP71xx USB controller"
-       depends on MSP_HAS_USB
-       select USB_EHCI_BIG_ENDIAN_DESC
-       select USB_EHCI_BIG_ENDIAN_MMIO
-       help
-               Enables support for the onchip USB controller on the PMC_MSP7100 Family SoC's.
-               If unsure, say N.
-
 config XPS_USB_HCD_XILINX
        bool "Use Xilinx usb host EHCI controller core"
        depends on (PPC32 || MICROBLAZE)
index 3e0ebe8..d3626bf 100644 (file)
@@ -108,10 +108,9 @@ static int ehci_brcm_reset(struct usb_hcd *hcd)
        /*
         * SWLINUX-1705: Avoid OUT packet underflows during high memory
         *   bus usage
-        * port_status[0x0f] = Broadcom-proprietary USB_EHCI_INSNREG00 @ 0x90
         */
-       ehci_writel(ehci, 0x00800040, &ehci->regs->port_status[0x10]);
-       ehci_writel(ehci, 0x00000001, &ehci->regs->port_status[0x12]);
+       ehci_writel(ehci, 0x00800040, &ehci->regs->brcm_insnreg[1]);
+       ehci_writel(ehci, 0x00000001, &ehci->regs->brcm_insnreg[3]);
 
        return ehci_setup(hcd);
 }
@@ -223,11 +222,9 @@ static int __maybe_unused ehci_brcm_resume(struct device *dev)
        /*
         * SWLINUX-1705: Avoid OUT packet underflows during high memory
         *   bus usage
-        * port_status[0x0f] = Broadcom-proprietary USB_EHCI_INSNREG00
-        * @ 0x90
         */
-       ehci_writel(ehci, 0x00800040, &ehci->regs->port_status[0x10]);
-       ehci_writel(ehci, 0x00000001, &ehci->regs->port_status[0x12]);
+       ehci_writel(ehci, 0x00800040, &ehci->regs->brcm_insnreg[1]);
+       ehci_writel(ehci, 0x00000001, &ehci->regs->brcm_insnreg[3]);
 
        ehci_resume(hcd, false);
 
index 10b0365..6bdc6d6 100644 (file)
@@ -1296,11 +1296,6 @@ MODULE_LICENSE ("GPL");
 #define XILINX_OF_PLATFORM_DRIVER      ehci_hcd_xilinx_of_driver
 #endif
 
-#ifdef CONFIG_USB_EHCI_HCD_PMC_MSP
-#include "ehci-pmcmsp.c"
-#define        PLATFORM_DRIVER         ehci_hcd_msp_driver
-#endif
-
 #ifdef CONFIG_SPARC_LEON
 #include "ehci-grlib.c"
 #define PLATFORM_DRIVER                ehci_grlib_driver
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 a319b1d..3626758 100644 (file)
@@ -264,8 +264,11 @@ static int ehci_orion_drv_probe(struct platform_device *pdev)
         * the clock does not exists.
         */
        priv->clk = devm_clk_get(&pdev->dev, NULL);
-       if (!IS_ERR(priv->clk))
-               clk_prepare_enable(priv->clk);
+       if (!IS_ERR(priv->clk)) {
+               err = clk_prepare_enable(priv->clk);
+               if (err)
+                       goto err_put_hcd;
+       }
 
        priv->phy = devm_phy_optional_get(&pdev->dev, "usb");
        if (IS_ERR(priv->phy)) {
@@ -311,6 +314,7 @@ static int ehci_orion_drv_probe(struct platform_device *pdev)
 err_dis_clk:
        if (!IS_ERR(priv->clk))
                clk_disable_unprepare(priv->clk);
+err_put_hcd:
        usb_put_hcd(hcd);
 err:
        dev_err(&pdev->dev, "init %s fail, %d\n",
diff --git a/drivers/usb/host/ehci-pmcmsp.c b/drivers/usb/host/ehci-pmcmsp.c
deleted file mode 100644 (file)
index 5fb92b9..0000000
+++ /dev/null
@@ -1,328 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * PMC MSP EHCI (Host Controller Driver) for USB.
- *
- * (C) Copyright 2006-2010 PMC-Sierra Inc
- */
-
-/* includes */
-#include <linux/platform_device.h>
-#include <linux/gpio.h>
-#include <linux/usb.h>
-#include <msp_usb.h>
-
-/* stream disable*/
-#define USB_CTRL_MODE_STREAM_DISABLE   0x10
-
-/* threshold */
-#define USB_CTRL_FIFO_THRESH           0x00300000
-
-/* register offset for usb_mode */
-#define USB_EHCI_REG_USB_MODE          0x68
-
-/* register offset for usb fifo */
-#define USB_EHCI_REG_USB_FIFO          0x24
-
-/* register offset for usb status */
-#define USB_EHCI_REG_USB_STATUS                0x44
-
-/* serial/parallel transceiver */
-#define USB_EHCI_REG_BIT_STAT_STS      (1<<29)
-
-/* TWI USB0 host device pin */
-#define MSP_PIN_USB0_HOST_DEV          49
-
-/* TWI USB1 host device pin */
-#define MSP_PIN_USB1_HOST_DEV          50
-
-
-static void usb_hcd_tdi_set_mode(struct ehci_hcd *ehci)
-{
-       u8 *base;
-       u8 *statreg;
-       u8 *fiforeg;
-       u32 val;
-       struct ehci_regs *reg_base = ehci->regs;
-
-       /* get register base */
-       base = (u8 *)reg_base + USB_EHCI_REG_USB_MODE;
-       statreg = (u8 *)reg_base + USB_EHCI_REG_USB_STATUS;
-       fiforeg = (u8 *)reg_base + USB_EHCI_REG_USB_FIFO;
-
-       /* Disable controller mode stream */
-       val = ehci_readl(ehci, (u32 *)base);
-       ehci_writel(ehci, (val | USB_CTRL_MODE_STREAM_DISABLE),
-                       (u32 *)base);
-
-       /* clear STS to select parallel transceiver interface */
-       val = ehci_readl(ehci, (u32 *)statreg);
-       val = val & ~USB_EHCI_REG_BIT_STAT_STS;
-       ehci_writel(ehci, val, (u32 *)statreg);
-
-       /* write to set the proper fifo threshold */
-       ehci_writel(ehci, USB_CTRL_FIFO_THRESH, (u32 *)fiforeg);
-
-       /* set TWI GPIO USB_HOST_DEV pin high */
-       gpio_direction_output(MSP_PIN_USB0_HOST_DEV, 1);
-}
-
-/* called during probe() after chip reset completes */
-static int ehci_msp_setup(struct usb_hcd *hcd)
-{
-       struct ehci_hcd         *ehci = hcd_to_ehci(hcd);
-       int                     retval;
-
-       ehci->big_endian_mmio = 1;
-       ehci->big_endian_desc = 1;
-
-       ehci->caps = hcd->regs;
-       hcd->has_tt = 1;
-
-       retval = ehci_setup(hcd);
-       if (retval)
-               return retval;
-
-       usb_hcd_tdi_set_mode(ehci);
-
-       return retval;
-}
-
-
-/* configure so an HC device and id are always provided
- * always called with process context; sleeping is OK
- */
-
-static int usb_hcd_msp_map_regs(struct mspusb_device *dev)
-{
-       struct resource *res;
-       struct platform_device *pdev = &dev->dev;
-       u32 res_len;
-       int retval;
-
-       /* MAB register space */
-       res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
-       if (res == NULL)
-               return -ENOMEM;
-       res_len = resource_size(res);
-       if (!request_mem_region(res->start, res_len, "mab regs"))
-               return -EBUSY;
-
-       dev->mab_regs = ioremap(res->start, res_len);
-       if (dev->mab_regs == NULL) {
-               retval = -ENOMEM;
-               goto err1;
-       }
-
-       /* MSP USB register space */
-       res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
-       if (res == NULL) {
-               retval = -ENOMEM;
-               goto err2;
-       }
-       res_len = resource_size(res);
-       if (!request_mem_region(res->start, res_len, "usbid regs")) {
-               retval = -EBUSY;
-               goto err2;
-       }
-       dev->usbid_regs = ioremap(res->start, res_len);
-       if (dev->usbid_regs == NULL) {
-               retval = -ENOMEM;
-               goto err3;
-       }
-
-       return 0;
-err3:
-       res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
-       res_len = resource_size(res);
-       release_mem_region(res->start, res_len);
-err2:
-       iounmap(dev->mab_regs);
-err1:
-       res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
-       res_len = resource_size(res);
-       release_mem_region(res->start, res_len);
-       dev_err(&pdev->dev, "Failed to map non-EHCI regs.\n");
-       return retval;
-}
-
-/**
- * usb_hcd_msp_probe - initialize PMC MSP-based HCDs
- * @driver:    Pointer to hc driver instance
- * @dev:       USB controller to probe
- *
- * Context: task context, might sleep
- *
- * Allocates basic resources for this USB host controller, and
- * then invokes the start() method for the HCD associated with it
- * through the hotplug entry's driver_data.
- */
-int usb_hcd_msp_probe(const struct hc_driver *driver,
-                         struct platform_device *dev)
-{
-       int retval;
-       struct usb_hcd *hcd;
-       struct resource *res;
-       struct ehci_hcd         *ehci ;
-
-       hcd = usb_create_hcd(driver, &dev->dev, "pmcmsp");
-       if (!hcd)
-               return -ENOMEM;
-
-       res = platform_get_resource(dev, IORESOURCE_MEM, 0);
-       if (res == NULL) {
-               pr_debug("No IOMEM resource info for %s.\n", dev->name);
-               retval = -ENOMEM;
-               goto err1;
-       }
-       hcd->rsrc_start = res->start;
-       hcd->rsrc_len = resource_size(res);
-       if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, dev->name)) {
-               retval = -EBUSY;
-               goto err1;
-       }
-       hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
-       if (!hcd->regs) {
-               pr_debug("ioremap failed");
-               retval = -ENOMEM;
-               goto err2;
-       }
-
-       res = platform_get_resource(dev, IORESOURCE_IRQ, 0);
-       if (res == NULL) {
-               dev_err(&dev->dev, "No IRQ resource info for %s.\n", dev->name);
-               retval = -ENOMEM;
-               goto err3;
-       }
-
-       /* Map non-EHCI register spaces */
-       retval = usb_hcd_msp_map_regs(to_mspusb_device(dev));
-       if (retval != 0)
-               goto err3;
-
-       ehci = hcd_to_ehci(hcd);
-       ehci->big_endian_mmio = 1;
-       ehci->big_endian_desc = 1;
-
-
-       retval = usb_add_hcd(hcd, res->start, IRQF_SHARED);
-       if (retval == 0) {
-               device_wakeup_enable(hcd->self.controller);
-               return 0;
-       }
-
-       usb_remove_hcd(hcd);
-err3:
-       iounmap(hcd->regs);
-err2:
-       release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
-err1:
-       usb_put_hcd(hcd);
-
-       return retval;
-}
-
-
-
-/**
- * usb_hcd_msp_remove - shutdown processing for PMC MSP-based HCDs
- * @hcd: USB Host Controller being removed
- *
- * Context: task context, might sleep
- *
- * Reverses the effect of usb_hcd_msp_probe(), first invoking
- * the HCD's stop() method.  It is always called from a thread
- * context, normally "rmmod", "apmd", or something similar.
- *
- * may be called without controller electrically present
- * may be called with controller, bus, and devices active
- */
-static void usb_hcd_msp_remove(struct usb_hcd *hcd)
-{
-       usb_remove_hcd(hcd);
-       iounmap(hcd->regs);
-       release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
-       usb_put_hcd(hcd);
-}
-
-static const struct hc_driver ehci_msp_hc_driver = {
-       .description =          hcd_name,
-       .product_desc =         "PMC MSP EHCI",
-       .hcd_priv_size =        sizeof(struct ehci_hcd),
-
-       /*
-        * generic hardware linkage
-        */
-       .irq =                  ehci_irq,
-       .flags =                HCD_MEMORY | HCD_DMA | HCD_USB2 | HCD_BH,
-
-       /*
-        * basic lifecycle operations
-        */
-       .reset                  = ehci_msp_setup,
-       .shutdown               = ehci_shutdown,
-       .start                  = ehci_run,
-       .stop                   = ehci_stop,
-
-       /*
-        * managing i/o requests and associated device resources
-        */
-       .urb_enqueue            = ehci_urb_enqueue,
-       .urb_dequeue            = ehci_urb_dequeue,
-       .endpoint_disable       = ehci_endpoint_disable,
-       .endpoint_reset         = ehci_endpoint_reset,
-
-       /*
-        * scheduling support
-        */
-       .get_frame_number       = ehci_get_frame,
-
-       /*
-        * root hub support
-        */
-       .hub_status_data        = ehci_hub_status_data,
-       .hub_control            = ehci_hub_control,
-       .bus_suspend            = ehci_bus_suspend,
-       .bus_resume             = ehci_bus_resume,
-       .relinquish_port        = ehci_relinquish_port,
-       .port_handed_over       = ehci_port_handed_over,
-
-       .clear_tt_buffer_complete       = ehci_clear_tt_buffer_complete,
-};
-
-static int ehci_hcd_msp_drv_probe(struct platform_device *pdev)
-{
-       int ret;
-
-       pr_debug("In ehci_hcd_msp_drv_probe");
-
-       if (usb_disabled())
-               return -ENODEV;
-
-       gpio_request(MSP_PIN_USB0_HOST_DEV, "USB0_HOST_DEV_GPIO");
-
-       ret = usb_hcd_msp_probe(&ehci_msp_hc_driver, pdev);
-
-       return ret;
-}
-
-static int ehci_hcd_msp_drv_remove(struct platform_device *pdev)
-{
-       struct usb_hcd *hcd = platform_get_drvdata(pdev);
-
-       usb_hcd_msp_remove(hcd);
-
-       /* free TWI GPIO USB_HOST_DEV pin */
-       gpio_free(MSP_PIN_USB0_HOST_DEV);
-
-       return 0;
-}
-
-MODULE_ALIAS("pmcmsp-ehci");
-
-static struct platform_driver ehci_hcd_msp_driver = {
-       .probe          = ehci_hcd_msp_drv_probe,
-       .remove         = ehci_hcd_msp_drv_remove,
-       .driver         = {
-               .name   = "pmcmsp-ehci",
-       },
-};
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 7f857ba..08ec2ab 100644 (file)
@@ -202,6 +202,9 @@ static int ohci_hcd_tmio_drv_probe(struct platform_device *dev)
        if (!cell)
                return -EINVAL;
 
+       if (irq < 0)
+               return irq;
+
        hcd = usb_create_hcd(&ohci_tmio_hc_driver, &dev->dev, dev_name(&dev->dev));
        if (!hcd) {
                ret = -ENOMEM;
index 2c0fda5..dc832dd 100644 (file)
@@ -198,12 +198,13 @@ static void xhci_ring_dump_segment(struct seq_file *s,
        int                     i;
        dma_addr_t              dma;
        union xhci_trb          *trb;
+       char                    str[XHCI_MSG_MAX];
 
        for (i = 0; i < TRBS_PER_SEGMENT; i++) {
                trb = &seg->trbs[i];
                dma = seg->dma + i * sizeof(*trb);
                seq_printf(s, "%pad: %s\n", &dma,
-                          xhci_decode_trb(le32_to_cpu(trb->generic.field[0]),
+                          xhci_decode_trb(str, XHCI_MSG_MAX, le32_to_cpu(trb->generic.field[0]),
                                           le32_to_cpu(trb->generic.field[1]),
                                           le32_to_cpu(trb->generic.field[2]),
                                           le32_to_cpu(trb->generic.field[3])));
@@ -260,11 +261,13 @@ static int xhci_slot_context_show(struct seq_file *s, void *unused)
        struct xhci_slot_ctx    *slot_ctx;
        struct xhci_slot_priv   *priv = s->private;
        struct xhci_virt_device *dev = priv->dev;
+       char                    str[XHCI_MSG_MAX];
 
        xhci = hcd_to_xhci(bus_to_hcd(dev->udev->bus));
        slot_ctx = xhci_get_slot_ctx(xhci, dev->out_ctx);
        seq_printf(s, "%pad: %s\n", &dev->out_ctx->dma,
-                  xhci_decode_slot_context(le32_to_cpu(slot_ctx->dev_info),
+                  xhci_decode_slot_context(str,
+                                           le32_to_cpu(slot_ctx->dev_info),
                                            le32_to_cpu(slot_ctx->dev_info2),
                                            le32_to_cpu(slot_ctx->tt_info),
                                            le32_to_cpu(slot_ctx->dev_state)));
@@ -280,6 +283,7 @@ static int xhci_endpoint_context_show(struct seq_file *s, void *unused)
        struct xhci_ep_ctx      *ep_ctx;
        struct xhci_slot_priv   *priv = s->private;
        struct xhci_virt_device *dev = priv->dev;
+       char                    str[XHCI_MSG_MAX];
 
        xhci = hcd_to_xhci(bus_to_hcd(dev->udev->bus));
 
@@ -287,7 +291,8 @@ static int xhci_endpoint_context_show(struct seq_file *s, void *unused)
                ep_ctx = xhci_get_ep_ctx(xhci, dev->out_ctx, ep_index);
                dma = dev->out_ctx->dma + (ep_index + 1) * CTX_SIZE(xhci->hcc_params);
                seq_printf(s, "%pad: %s\n", &dma,
-                          xhci_decode_ep_context(le32_to_cpu(ep_ctx->ep_info),
+                          xhci_decode_ep_context(str,
+                                                 le32_to_cpu(ep_ctx->ep_info),
                                                  le32_to_cpu(ep_ctx->ep_info2),
                                                  le64_to_cpu(ep_ctx->deq),
                                                  le32_to_cpu(ep_ctx->tx_info)));
@@ -341,9 +346,10 @@ static int xhci_portsc_show(struct seq_file *s, void *unused)
 {
        struct xhci_port        *port = s->private;
        u32                     portsc;
+       char                    str[XHCI_MSG_MAX];
 
        portsc = readl(port->addr);
-       seq_printf(s, "%s\n", xhci_decode_portsc(portsc));
+       seq_printf(s, "%s\n", xhci_decode_portsc(str, portsc));
 
        return 0;
 }
index 151e93c..a3f875e 100644 (file)
@@ -1667,7 +1667,8 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf)
                        status = 1;
        }
        if (!status && !reset_change) {
-               xhci_dbg(xhci, "%s: stopping port polling.\n", __func__);
+               xhci_dbg(xhci, "%s: stopping usb%d port polling\n",
+                        __func__, hcd->self.busnum);
                clear_bit(HCD_FLAG_POLL_RH, &hcd->flags);
        }
        spin_unlock_irqrestore(&xhci->lock, flags);
@@ -1699,7 +1700,8 @@ int xhci_bus_suspend(struct usb_hcd *hcd)
                if (bus_state->resuming_ports ||        /* USB2 */
                    bus_state->port_remote_wakeup) {    /* USB3 */
                        spin_unlock_irqrestore(&xhci->lock, flags);
-                       xhci_dbg(xhci, "suspend failed because a port is resuming\n");
+                       xhci_dbg(xhci, "usb%d bus suspend to fail because a port is resuming\n",
+                                hcd->self.busnum);
                        return -EBUSY;
                }
        }
index cffcaf4..134f478 100644 (file)
@@ -80,7 +80,7 @@ decode_ep(struct usb_host_endpoint *ep, enum usb_device_speed speed)
                interval /= 1000;
        }
 
-       snprintf(buf, DBG_BUF_EN, "%s ep%d%s %s, mpkt:%d, interval:%d/%d%s\n",
+       snprintf(buf, DBG_BUF_EN, "%s ep%d%s %s, mpkt:%d, interval:%d/%d%s",
                 usb_speed_string(speed), usb_endpoint_num(epd),
                 usb_endpoint_dir_in(epd) ? "in" : "out",
                 usb_ep_type_string(usb_endpoint_type(epd)),
@@ -129,6 +129,10 @@ get_bw_info(struct xhci_hcd_mtk *mtk, struct usb_device *udev,
        int bw_index;
 
        virt_dev = xhci->devs[udev->slot_id];
+       if (!virt_dev->real_port) {
+               WARN_ONCE(1, "%s invalid real_port\n", dev_name(&udev->dev));
+               return NULL;
+       }
 
        if (udev->speed >= USB_SPEED_SUPER) {
                if (usb_endpoint_dir_out(&ep->desc))
@@ -236,14 +240,20 @@ static void drop_tt(struct usb_device *udev)
        }
 }
 
-static struct mu3h_sch_ep_info *create_sch_ep(struct usb_device *udev,
-       struct usb_host_endpoint *ep, struct xhci_ep_ctx *ep_ctx)
+static struct mu3h_sch_ep_info *
+create_sch_ep(struct xhci_hcd_mtk *mtk, struct usb_device *udev,
+             struct usb_host_endpoint *ep, struct xhci_ep_ctx *ep_ctx)
 {
        struct mu3h_sch_ep_info *sch_ep;
+       struct mu3h_sch_bw_info *bw_info;
        struct mu3h_sch_tt *tt = NULL;
        u32 len_bw_budget_table;
        size_t mem_size;
 
+       bw_info = get_bw_info(mtk, udev, ep);
+       if (!bw_info)
+               return ERR_PTR(-ENODEV);
+
        if (is_fs_or_ls(udev->speed))
                len_bw_budget_table = TT_MICROFRAMES_MAX;
        else if ((udev->speed >= USB_SPEED_SUPER)
@@ -266,11 +276,13 @@ static struct mu3h_sch_ep_info *create_sch_ep(struct usb_device *udev,
                }
        }
 
+       sch_ep->bw_info = bw_info;
        sch_ep->sch_tt = tt;
        sch_ep->ep = ep;
        sch_ep->speed = udev->speed;
        INIT_LIST_HEAD(&sch_ep->endpoint);
        INIT_LIST_HEAD(&sch_ep->tt_endpoint);
+       INIT_HLIST_NODE(&sch_ep->hentry);
 
        return sch_ep;
 }
@@ -297,6 +309,7 @@ static void setup_sch_info(struct xhci_ep_ctx *ep_ctx,
                 CTX_TO_MAX_ESIT_PAYLOAD(le32_to_cpu(ep_ctx->tx_info));
 
        sch_ep->esit = get_esit(ep_ctx);
+       sch_ep->num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit;
        sch_ep->ep_type = ep_type;
        sch_ep->maxpkt = maxpkt;
        sch_ep->offset = 0;
@@ -401,19 +414,16 @@ static void setup_sch_info(struct xhci_ep_ctx *ep_ctx,
 static u32 get_max_bw(struct mu3h_sch_bw_info *sch_bw,
        struct mu3h_sch_ep_info *sch_ep, u32 offset)
 {
-       u32 num_esit;
        u32 max_bw = 0;
        u32 bw;
-       int i;
-       int j;
+       int i, j, k;
 
-       num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit;
-       for (i = 0; i < num_esit; i++) {
+       for (i = 0; i < sch_ep->num_esit; i++) {
                u32 base = offset + i * sch_ep->esit;
 
                for (j = 0; j < sch_ep->num_budget_microframes; j++) {
-                       bw = sch_bw->bus_bw[base + j] +
-                                       sch_ep->bw_budget_table[j];
+                       k = XHCI_MTK_BW_INDEX(base + j);
+                       bw = sch_bw->bus_bw[k] + sch_ep->bw_budget_table[j];
                        if (bw > max_bw)
                                max_bw = bw;
                }
@@ -424,21 +434,17 @@ static u32 get_max_bw(struct mu3h_sch_bw_info *sch_bw,
 static void update_bus_bw(struct mu3h_sch_bw_info *sch_bw,
        struct mu3h_sch_ep_info *sch_ep, bool used)
 {
-       u32 num_esit;
        u32 base;
-       int i;
-       int j;
+       int i, j, k;
 
-       num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit;
-       for (i = 0; i < num_esit; i++) {
+       for (i = 0; i < sch_ep->num_esit; i++) {
                base = sch_ep->offset + i * sch_ep->esit;
                for (j = 0; j < sch_ep->num_budget_microframes; j++) {
+                       k = XHCI_MTK_BW_INDEX(base + j);
                        if (used)
-                               sch_bw->bus_bw[base + j] +=
-                                       sch_ep->bw_budget_table[j];
+                               sch_bw->bus_bw[k] += sch_ep->bw_budget_table[j];
                        else
-                               sch_bw->bus_bw[base + j] -=
-                                       sch_ep->bw_budget_table[j];
+                               sch_bw->bus_bw[k] -= sch_ep->bw_budget_table[j];
                }
        }
 }
@@ -446,20 +452,20 @@ static void update_bus_bw(struct mu3h_sch_bw_info *sch_bw,
 static int check_fs_bus_bw(struct mu3h_sch_ep_info *sch_ep, int offset)
 {
        struct mu3h_sch_tt *tt = sch_ep->sch_tt;
-       u32 num_esit, tmp;
+       u32 tmp;
        int base;
-       int i, j;
+       int i, j, k;
 
-       num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit;
-       for (i = 0; i < num_esit; i++) {
+       for (i = 0; i < sch_ep->num_esit; i++) {
                base = offset + i * sch_ep->esit;
 
                /*
                 * Compared with hs bus, no matter what ep type,
                 * the hub will always delay one uframe to send data
                 */
-               for (j = 0; j < sch_ep->cs_count; j++) {
-                       tmp = tt->fs_bus_bw[base + j] + sch_ep->bw_cost_per_microframe;
+               for (j = 0; j < sch_ep->num_budget_microframes; j++) {
+                       k = XHCI_MTK_BW_INDEX(base + j);
+                       tmp = tt->fs_bus_bw[k] + sch_ep->bw_budget_table[j];
                        if (tmp > FS_PAYLOAD_MAX)
                                return -ESCH_BW_OVERFLOW;
                }
@@ -533,22 +539,19 @@ static int check_sch_tt(struct mu3h_sch_ep_info *sch_ep, u32 offset)
 static void update_sch_tt(struct mu3h_sch_ep_info *sch_ep, bool used)
 {
        struct mu3h_sch_tt *tt = sch_ep->sch_tt;
-       u32 base, num_esit;
-       int bw_updated;
-       int i, j;
-
-       num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit;
-
-       if (used)
-               bw_updated = sch_ep->bw_cost_per_microframe;
-       else
-               bw_updated = -sch_ep->bw_cost_per_microframe;
+       u32 base;
+       int i, j, k;
 
-       for (i = 0; i < num_esit; i++) {
+       for (i = 0; i < sch_ep->num_esit; i++) {
                base = sch_ep->offset + i * sch_ep->esit;
 
-               for (j = 0; j < sch_ep->cs_count; j++)
-                       tt->fs_bus_bw[base + j] += bw_updated;
+               for (j = 0; j < sch_ep->num_budget_microframes; j++) {
+                       k = XHCI_MTK_BW_INDEX(base + j);
+                       if (used)
+                               tt->fs_bus_bw[k] += sch_ep->bw_budget_table[j];
+                       else
+                               tt->fs_bus_bw[k] -= sch_ep->bw_budget_table[j];
+               }
        }
 
        if (used)
@@ -570,25 +573,9 @@ static int load_ep_bw(struct mu3h_sch_bw_info *sch_bw,
        return 0;
 }
 
-static u32 get_esit_boundary(struct mu3h_sch_ep_info *sch_ep)
-{
-       u32 boundary = sch_ep->esit;
-
-       if (sch_ep->sch_tt) { /* LS/FS with TT */
-               /* tune for CS */
-               if (sch_ep->ep_type != ISOC_OUT_EP)
-                       boundary++;
-               else if (boundary > 1) /* normally esit >= 8 for FS/LS */
-                       boundary--;
-       }
-
-       return boundary;
-}
-
-static int check_sch_bw(struct mu3h_sch_bw_info *sch_bw,
-                       struct mu3h_sch_ep_info *sch_ep)
+static int check_sch_bw(struct mu3h_sch_ep_info *sch_ep)
 {
-       const u32 esit_boundary = get_esit_boundary(sch_ep);
+       struct mu3h_sch_bw_info *sch_bw = sch_ep->bw_info;
        const u32 bw_boundary = get_bw_boundary(sch_ep->speed);
        u32 offset;
        u32 worst_bw;
@@ -605,9 +592,6 @@ static int check_sch_bw(struct mu3h_sch_bw_info *sch_bw,
                if (ret)
                        continue;
 
-               if ((offset + sch_ep->num_budget_microframes) > esit_boundary)
-                       break;
-
                worst_bw = get_max_bw(sch_bw, sch_ep, offset);
                if (worst_bw > bw_boundary)
                        continue;
@@ -633,23 +617,26 @@ static int check_sch_bw(struct mu3h_sch_bw_info *sch_bw,
        return load_ep_bw(sch_bw, sch_ep, true);
 }
 
-static void destroy_sch_ep(struct usb_device *udev,
-       struct mu3h_sch_bw_info *sch_bw, struct mu3h_sch_ep_info *sch_ep)
+static void destroy_sch_ep(struct xhci_hcd_mtk *mtk, struct usb_device *udev,
+                          struct mu3h_sch_ep_info *sch_ep)
 {
        /* only release ep bw check passed by check_sch_bw() */
        if (sch_ep->allocated)
-               load_ep_bw(sch_bw, sch_ep, false);
+               load_ep_bw(sch_ep->bw_info, sch_ep, false);
 
        if (sch_ep->sch_tt)
                drop_tt(udev);
 
        list_del(&sch_ep->endpoint);
+       hlist_del(&sch_ep->hentry);
        kfree(sch_ep);
 }
 
-static bool need_bw_sch(struct usb_host_endpoint *ep,
-       enum usb_device_speed speed, int has_tt)
+static bool need_bw_sch(struct usb_device *udev,
+                       struct usb_host_endpoint *ep)
 {
+       bool has_tt = udev->tt && udev->tt->hub->parent;
+
        /* only for periodic endpoints */
        if (usb_endpoint_xfer_control(&ep->desc)
                || usb_endpoint_xfer_bulk(&ep->desc))
@@ -660,7 +647,7 @@ static bool need_bw_sch(struct usb_host_endpoint *ep,
         * a TT are also ignored, root-hub will schedule them directly,
         * but need set @bpkts field of endpoint context to 1.
         */
-       if (is_fs_or_ls(speed) && !has_tt)
+       if (is_fs_or_ls(udev->speed) && !has_tt)
                return false;
 
        /* skip endpoint with zero maxpkt */
@@ -675,7 +662,6 @@ int xhci_mtk_sch_init(struct xhci_hcd_mtk *mtk)
        struct xhci_hcd *xhci = hcd_to_xhci(mtk->hcd);
        struct mu3h_sch_bw_info *sch_array;
        int num_usb_bus;
-       int i;
 
        /* ss IN and OUT are separated */
        num_usb_bus = xhci->usb3_rhub.num_ports * 2 + xhci->usb2_rhub.num_ports;
@@ -684,12 +670,10 @@ int xhci_mtk_sch_init(struct xhci_hcd_mtk *mtk)
        if (sch_array == NULL)
                return -ENOMEM;
 
-       for (i = 0; i < num_usb_bus; i++)
-               INIT_LIST_HEAD(&sch_array[i].bw_ep_list);
-
        mtk->sch_array = sch_array;
 
        INIT_LIST_HEAD(&mtk->bw_ep_chk_list);
+       hash_init(mtk->sch_ep_hash);
 
        return 0;
 }
@@ -713,9 +697,7 @@ static int add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
        ep_index = xhci_get_endpoint_index(&ep->desc);
        ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index);
 
-       xhci_dbg(xhci, "%s %s\n", __func__, decode_ep(ep, udev->speed));
-
-       if (!need_bw_sch(ep, udev->speed, !!virt_dev->tt_info)) {
+       if (!need_bw_sch(udev, ep)) {
                /*
                 * set @bpkts to 1 if it is LS or FS periodic endpoint, and its
                 * device does not connected through an external HS hub
@@ -727,13 +709,16 @@ static int add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
                return 0;
        }
 
-       sch_ep = create_sch_ep(udev, ep, ep_ctx);
+       xhci_dbg(xhci, "%s %s\n", __func__, decode_ep(ep, udev->speed));
+
+       sch_ep = create_sch_ep(mtk, udev, ep, ep_ctx);
        if (IS_ERR_OR_NULL(sch_ep))
                return -ENOMEM;
 
        setup_sch_info(ep_ctx, sch_ep);
 
        list_add_tail(&sch_ep->endpoint, &mtk->bw_ep_chk_list);
+       hash_add(mtk->sch_ep_hash, &sch_ep->hentry, (unsigned long)ep);
 
        return 0;
 }
@@ -743,22 +728,18 @@ static void drop_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
 {
        struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd);
        struct xhci_hcd *xhci = hcd_to_xhci(hcd);
-       struct xhci_virt_device *virt_dev;
-       struct mu3h_sch_bw_info *sch_bw;
-       struct mu3h_sch_ep_info *sch_ep, *tmp;
-
-       virt_dev = xhci->devs[udev->slot_id];
-
-       xhci_dbg(xhci, "%s %s\n", __func__, decode_ep(ep, udev->speed));
+       struct mu3h_sch_ep_info *sch_ep;
+       struct hlist_node *hn;
 
-       if (!need_bw_sch(ep, udev->speed, !!virt_dev->tt_info))
+       if (!need_bw_sch(udev, ep))
                return;
 
-       sch_bw = get_bw_info(mtk, udev, ep);
+       xhci_err(xhci, "%s %s\n", __func__, decode_ep(ep, udev->speed));
 
-       list_for_each_entry_safe(sch_ep, tmp, &sch_bw->bw_ep_list, endpoint) {
+       hash_for_each_possible_safe(mtk->sch_ep_hash, sch_ep,
+                                   hn, hentry, (unsigned long)ep) {
                if (sch_ep->ep == ep) {
-                       destroy_sch_ep(udev, sch_bw, sch_ep);
+                       destroy_sch_ep(mtk, udev, sch_ep);
                        break;
                }
        }
@@ -769,30 +750,22 @@ int xhci_mtk_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev)
        struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd);
        struct xhci_hcd *xhci = hcd_to_xhci(hcd);
        struct xhci_virt_device *virt_dev = xhci->devs[udev->slot_id];
-       struct mu3h_sch_bw_info *sch_bw;
-       struct mu3h_sch_ep_info *sch_ep, *tmp;
+       struct mu3h_sch_ep_info *sch_ep;
        int ret;
 
        xhci_dbg(xhci, "%s() udev %s\n", __func__, dev_name(&udev->dev));
 
        list_for_each_entry(sch_ep, &mtk->bw_ep_chk_list, endpoint) {
-               sch_bw = get_bw_info(mtk, udev, sch_ep->ep);
+               struct xhci_ep_ctx *ep_ctx;
+               struct usb_host_endpoint *ep = sch_ep->ep;
+               unsigned int ep_index = xhci_get_endpoint_index(&ep->desc);
 
-               ret = check_sch_bw(sch_bw, sch_ep);
+               ret = check_sch_bw(sch_ep);
                if (ret) {
                        xhci_err(xhci, "Not enough bandwidth! (%s)\n",
                                 sch_error_string(-ret));
                        return -ENOSPC;
                }
-       }
-
-       list_for_each_entry_safe(sch_ep, tmp, &mtk->bw_ep_chk_list, endpoint) {
-               struct xhci_ep_ctx *ep_ctx;
-               struct usb_host_endpoint *ep = sch_ep->ep;
-               unsigned int ep_index = xhci_get_endpoint_index(&ep->desc);
-
-               sch_bw = get_bw_info(mtk, udev, ep);
-               list_move_tail(&sch_ep->endpoint, &sch_bw->bw_ep_list);
 
                ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index);
                ep_ctx->reserved[0] = cpu_to_le32(EP_BPKTS(sch_ep->pkts)
@@ -806,22 +779,23 @@ int xhci_mtk_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev)
                        sch_ep->offset, sch_ep->repeat);
        }
 
-       return xhci_check_bandwidth(hcd, udev);
+       ret = xhci_check_bandwidth(hcd, udev);
+       if (!ret)
+               INIT_LIST_HEAD(&mtk->bw_ep_chk_list);
+
+       return ret;
 }
 
 void xhci_mtk_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev)
 {
        struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd);
        struct xhci_hcd *xhci = hcd_to_xhci(hcd);
-       struct mu3h_sch_bw_info *sch_bw;
        struct mu3h_sch_ep_info *sch_ep, *tmp;
 
        xhci_dbg(xhci, "%s() udev %s\n", __func__, dev_name(&udev->dev));
 
-       list_for_each_entry_safe(sch_ep, tmp, &mtk->bw_ep_chk_list, endpoint) {
-               sch_bw = get_bw_info(mtk, udev, sch_ep->ep);
-               destroy_sch_ep(udev, sch_bw, sch_ep);
-       }
+       list_for_each_entry_safe(sch_ep, tmp, &mtk->bw_ep_chk_list, endpoint)
+               destroy_sch_ep(mtk, udev, sch_ep);
 
        xhci_reset_bandwidth(hcd, udev);
 }
index 2548976..c53f6f2 100644 (file)
 /* u2_phy_pll register */
 #define CTRL_U2_FORCE_PLL_STB  BIT(28)
 
+/* xHCI CSR */
+#define LS_EOF_CFG             0x930
+#define LSEOF_OFFSET           0x89
+
+#define FS_EOF_CFG             0x934
+#define FSEOF_OFFSET           0x2e
+
+#define SS_GEN1_EOF_CFG                0x93c
+#define SSG1EOF_OFFSET         0x78
+
+#define HFCNTR_CFG             0x944
+#define ITP_DELTA_CLK          (0xa << 1)
+#define ITP_DELTA_CLK_MASK     GENMASK(5, 1)
+#define FRMCNT_LEV1_RANG       (0x12b << 8)
+#define FRMCNT_LEV1_RANG_MASK  GENMASK(19, 8)
+
+#define SS_GEN2_EOF_CFG                0x990
+#define SSG2EOF_OFFSET         0x3c
+
+#define XSEOF_OFFSET_MASK      GENMASK(11, 0)
+
 /* usb remote wakeup registers in syscon */
 
 /* mt8173 etc */
@@ -86,6 +107,46 @@ enum ssusb_uwk_vers {
        SSUSB_UWK_V1_2,         /* specific revision 1.2 */
 };
 
+/*
+ * MT8195 has 4 controllers, the controller1~3's default SOF/ITP interval
+ * is calculated from the frame counter clock 24M, but in fact, the clock
+ * is 48M, add workaround for it.
+ */
+static void xhci_mtk_set_frame_interval(struct xhci_hcd_mtk *mtk)
+{
+       struct device *dev = mtk->dev;
+       struct usb_hcd *hcd = mtk->hcd;
+       u32 value;
+
+       if (!of_device_is_compatible(dev->of_node, "mediatek,mt8195-xhci"))
+               return;
+
+       value = readl(hcd->regs + HFCNTR_CFG);
+       value &= ~(ITP_DELTA_CLK_MASK | FRMCNT_LEV1_RANG_MASK);
+       value |= (ITP_DELTA_CLK | FRMCNT_LEV1_RANG);
+       writel(value, hcd->regs + HFCNTR_CFG);
+
+       value = readl(hcd->regs + LS_EOF_CFG);
+       value &= ~XSEOF_OFFSET_MASK;
+       value |= LSEOF_OFFSET;
+       writel(value, hcd->regs + LS_EOF_CFG);
+
+       value = readl(hcd->regs + FS_EOF_CFG);
+       value &= ~XSEOF_OFFSET_MASK;
+       value |= FSEOF_OFFSET;
+       writel(value, hcd->regs + FS_EOF_CFG);
+
+       value = readl(hcd->regs + SS_GEN1_EOF_CFG);
+       value &= ~XSEOF_OFFSET_MASK;
+       value |= SSG1EOF_OFFSET;
+       writel(value, hcd->regs + SS_GEN1_EOF_CFG);
+
+       value = readl(hcd->regs + SS_GEN2_EOF_CFG);
+       value &= ~XSEOF_OFFSET_MASK;
+       value |= SSG2EOF_OFFSET;
+       writel(value, hcd->regs + SS_GEN2_EOF_CFG);
+}
+
 static int xhci_mtk_host_enable(struct xhci_hcd_mtk *mtk)
 {
        struct mu3c_ippc_regs __iomem *ippc = mtk->ippc_regs;
@@ -115,8 +176,11 @@ static int xhci_mtk_host_enable(struct xhci_hcd_mtk *mtk)
                writel(value, &ippc->u3_ctrl_p[i]);
        }
 
-       /* power on and enable all u2 ports */
+       /* power on and enable all u2 ports except skipped ones */
        for (i = 0; i < mtk->num_u2_ports; i++) {
+               if (BIT(i) & mtk->u2p_dis_msk)
+                       continue;
+
                value = readl(&ippc->u2_ctrl_p[i]);
                value &= ~(CTRL_U2_PORT_PDN | CTRL_U2_PORT_DIS);
                value |= CTRL_U2_PORT_HOST_SEL;
@@ -163,8 +227,11 @@ static int xhci_mtk_host_disable(struct xhci_hcd_mtk *mtk)
                writel(value, &ippc->u3_ctrl_p[i]);
        }
 
-       /* power down all u2 ports */
+       /* power down all u2 ports except skipped ones */
        for (i = 0; i < mtk->num_u2_ports; i++) {
+               if (BIT(i) & mtk->u2p_dis_msk)
+                       continue;
+
                value = readl(&ippc->u2_ctrl_p[i]);
                value |= CTRL_U2_PORT_PDN;
                writel(value, &ippc->u2_ctrl_p[i]);
@@ -361,6 +428,9 @@ static int xhci_mtk_setup(struct usb_hcd *hcd)
                ret = xhci_mtk_ssusb_config(mtk);
                if (ret)
                        return ret;
+
+               /* workaround only for mt8195 */
+               xhci_mtk_set_frame_interval(mtk);
        }
 
        ret = xhci_gen_setup(hcd, xhci_mtk_quirks);
@@ -444,6 +514,8 @@ static int xhci_mtk_probe(struct platform_device *pdev)
        /* optional property, ignore the error if it does not exist */
        of_property_read_u32(node, "mediatek,u3p-dis-msk",
                             &mtk->u3p_dis_msk);
+       of_property_read_u32(node, "mediatek,u2p-dis-msk",
+                            &mtk->u2p_dis_msk);
 
        ret = usb_wakeup_of_property_parse(mtk, node);
        if (ret) {
@@ -569,7 +641,7 @@ disable_ldos:
        xhci_mtk_ldos_disable(mtk);
 
 disable_pm:
-       pm_runtime_put_sync_autosuspend(dev);
+       pm_runtime_put_noidle(dev);
        pm_runtime_disable(dev);
        return ret;
 }
@@ -704,6 +776,7 @@ static const struct dev_pm_ops xhci_mtk_pm_ops = {
 
 static const struct of_device_id mtk_xhci_of_match[] = {
        { .compatible = "mediatek,mt8173-xhci"},
+       { .compatible = "mediatek,mt8195-xhci"},
        { .compatible = "mediatek,mtk-xhci"},
        { },
 };
index ace4323..4b1ea89 100644 (file)
 #define _XHCI_MTK_H_
 
 #include <linux/clk.h>
+#include <linux/hashtable.h>
 
 #include "xhci.h"
 
 #define BULK_CLKS_NUM  5
 
+/* support at most 64 ep, use 32 size hash table */
+#define SCH_EP_HASH_BITS       5
+
 /**
  * To simplify scheduler algorithm, set a upper limit for ESIT,
  * if a synchromous ep's ESIT is larger than @XHCI_MTK_MAX_ESIT,
  * round down to the limit value, that means allocating more
  * bandwidth to it.
  */
-#define XHCI_MTK_MAX_ESIT      64
+#define XHCI_MTK_MAX_ESIT      (1 << 6)
+#define XHCI_MTK_BW_INDEX(x)   ((x) & (XHCI_MTK_MAX_ESIT - 1))
 
 /**
  * @fs_bus_bw: array to keep track of bandwidth already used for FS
@@ -36,25 +41,26 @@ struct mu3h_sch_tt {
  * struct mu3h_sch_bw_info: schedule information for bandwidth domain
  *
  * @bus_bw: array to keep track of bandwidth already used at each uframes
- * @bw_ep_list: eps in the bandwidth domain
  *
  * treat a HS root port as a bandwidth domain, but treat a SS root port as
  * two bandwidth domains, one for IN eps and another for OUT eps.
  */
 struct mu3h_sch_bw_info {
        u32 bus_bw[XHCI_MTK_MAX_ESIT];
-       struct list_head bw_ep_list;
 };
 
 /**
  * struct mu3h_sch_ep_info: schedule information for endpoint
  *
  * @esit: unit is 125us, equal to 2 << Interval field in ep-context
+ * @num_esit: number of @esit in a period
  * @num_budget_microframes: number of continuous uframes
  *             (@repeat==1) scheduled within the interval
  * @bw_cost_per_microframe: bandwidth cost per microframe
+ * @hentry: hash table entry
  * @endpoint: linked into bandwidth domain which it belongs to
  * @tt_endpoint: linked into mu3h_sch_tt's list which it belongs to
+ * @bw_info: bandwidth domain which this endpoint belongs
  * @sch_tt: mu3h_sch_tt linked into
  * @ep_type: endpoint type
  * @maxpkt: max packet size of endpoint
@@ -79,10 +85,13 @@ struct mu3h_sch_bw_info {
  */
 struct mu3h_sch_ep_info {
        u32 esit;
+       u32 num_esit;
        u32 num_budget_microframes;
        u32 bw_cost_per_microframe;
        struct list_head endpoint;
+       struct hlist_node hentry;
        struct list_head tt_endpoint;
+       struct mu3h_sch_bw_info *bw_info;
        struct mu3h_sch_tt *sch_tt;
        u32 ep_type;
        u32 maxpkt;
@@ -135,9 +144,11 @@ struct xhci_hcd_mtk {
        struct usb_hcd *hcd;
        struct mu3h_sch_bw_info *sch_array;
        struct list_head bw_ep_chk_list;
+       DECLARE_HASHTABLE(sch_ep_hash, SCH_EP_HASH_BITS);
        struct mu3c_ippc_regs __iomem *ippc_regs;
        int num_u2_ports;
        int num_u3_ports;
+       int u2p_dis_msk;
        int u3p_dis_msk;
        struct regulator *vusb33;
        struct regulator *vbus;
index ef5e91a..52599d9 100644 (file)
@@ -599,7 +599,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;
 
        /* no firmware interface available */
@@ -631,9 +631,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 1bc4fe7..9888ba7 100644 (file)
@@ -134,6 +134,13 @@ static int xhci_rcar_download_firmware(struct usb_hcd *hcd)
        const struct soc_device_attribute *attr;
        const char *firmware_name;
 
+       /*
+        * According to the datasheet, "Upon the completion of FW Download,
+        * there is no need to write or reload FW".
+        */
+       if (readl(regs + RCAR_USB3_DL_CTRL) & RCAR_USB3_DL_CTRL_FW_SUCCESS)
+               return 0;
+
        attr = soc_device_match(rcar_quirks_match);
        if (attr)
                quirks = (uintptr_t)attr->data;
index 8fea44b..e676749 100644 (file)
@@ -830,9 +830,14 @@ static void xhci_giveback_invalidated_tds(struct xhci_virt_ep *ep)
 
                ring = xhci_urb_to_transfer_ring(ep->xhci, td->urb);
 
-               if (td->cancel_status == TD_CLEARED)
+               if (td->cancel_status == TD_CLEARED) {
+                       xhci_dbg(ep->xhci, "%s: Giveback cancelled URB %p TD\n",
+                                __func__, td->urb);
                        xhci_td_cleanup(ep->xhci, td, ring, td->status);
-
+               } else {
+                       xhci_dbg(ep->xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n",
+                                __func__, td->urb, td->cancel_status);
+               }
                if (ep->xhci->xhc_state & XHCI_STATE_DYING)
                        return;
        }
@@ -850,6 +855,10 @@ static int xhci_reset_halted_ep(struct xhci_hcd *xhci, unsigned int slot_id,
                goto done;
        }
 
+       xhci_dbg(xhci, "%s-reset ep %u, slot %u\n",
+                (reset_type == EP_HARD_RESET) ? "Hard" : "Soft",
+                ep_index, slot_id);
+
        ret = xhci_queue_reset_ep(xhci, command, slot_id, ep_index, reset_type);
 done:
        if (ret)
@@ -883,7 +892,8 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci,
        }
 
        if (ep->ep_state & EP_HALTED) {
-               xhci_dbg(xhci, "Reset ep command already pending\n");
+               xhci_dbg(xhci, "Reset ep command for ep_index %d already pending\n",
+                        ep->ep_index);
                return 0;
        }
 
@@ -922,9 +932,10 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep)
 
        list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) {
                xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb,
-                               "Removing canceled TD starting at 0x%llx (dma).",
-                               (unsigned long long)xhci_trb_virt_to_dma(
-                                       td->start_seg, td->first_trb));
+                              "Removing canceled TD starting at 0x%llx (dma) in stream %u URB %p",
+                              (unsigned long long)xhci_trb_virt_to_dma(
+                                      td->start_seg, td->first_trb),
+                              td->urb->stream_id, td->urb);
                list_del_init(&td->td_list);
                ring = xhci_urb_to_transfer_ring(xhci, td->urb);
                if (!ring) {
@@ -942,17 +953,21 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep)
                                         td->urb->stream_id);
                hw_deq &= ~0xf;
 
-               if (td->cancel_status == TD_HALTED) {
-                       cached_td = td;
-               } else if (trb_in_td(xhci, td->start_seg, td->first_trb,
-                             td->last_trb, hw_deq, false)) {
+               if (td->cancel_status == TD_HALTED ||
+                   trb_in_td(xhci, td->start_seg, td->first_trb, td->last_trb, hw_deq, false)) {
                        switch (td->cancel_status) {
                        case TD_CLEARED: /* TD is already no-op */
                        case TD_CLEARING_CACHE: /* set TR deq command already queued */
                                break;
                        case TD_DIRTY: /* TD is cached, clear it */
                        case TD_HALTED:
-                               /* FIXME  stream case, several stopped rings */
+                               td->cancel_status = TD_CLEARING_CACHE;
+                               if (cached_td)
+                                       /* FIXME  stream case, several stopped rings */
+                                       xhci_dbg(xhci,
+                                                "Move dq past stream %u URB %p instead of stream %u URB %p\n",
+                                                td->urb->stream_id, td->urb,
+                                                cached_td->urb->stream_id, cached_td->urb);
                                cached_td = td;
                                break;
                        }
@@ -961,18 +976,24 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep)
                        td->cancel_status = TD_CLEARED;
                }
        }
-       if (cached_td) {
-               cached_td->cancel_status = TD_CLEARING_CACHE;
 
-               err = xhci_move_dequeue_past_td(xhci, slot_id, ep->ep_index,
-                                               cached_td->urb->stream_id,
-                                               cached_td);
-               /* Failed to move past cached td, try just setting it noop */
-               if (err) {
-                       td_to_noop(xhci, ring, cached_td, false);
-                       cached_td->cancel_status = TD_CLEARED;
+       /* If there's no need to move the dequeue pointer then we're done */
+       if (!cached_td)
+               return 0;
+
+       err = xhci_move_dequeue_past_td(xhci, slot_id, ep->ep_index,
+                                       cached_td->urb->stream_id,
+                                       cached_td);
+       if (err) {
+               /* Failed to move past cached td, just set cached TDs to no-op */
+               list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) {
+                       if (td->cancel_status != TD_CLEARING_CACHE)
+                               continue;
+                       xhci_dbg(xhci, "Failed to clear cancelled cached URB %p, mark clear anyway\n",
+                                td->urb);
+                       td_to_noop(xhci, ring, td, false);
+                       td->cancel_status = TD_CLEARED;
                }
-               cached_td = NULL;
        }
        return 0;
 }
@@ -1069,6 +1090,8 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id,
                        return;
                case EP_STATE_RUNNING:
                        /* Race, HW handled stop ep cmd before ep was running */
+                       xhci_dbg(xhci, "Stop ep completion ctx error, ep is running\n");
+
                        command = xhci_alloc_command(xhci, false, GFP_ATOMIC);
                        if (!command)
                                xhci_stop_watchdog_timer_in_irq(xhci, ep);
@@ -1212,6 +1235,7 @@ void xhci_stop_endpoint_command_watchdog(struct timer_list *t)
        struct xhci_hcd *xhci = ep->xhci;
        unsigned long flags;
        u32 usbsts;
+       char str[XHCI_MSG_MAX];
 
        spin_lock_irqsave(&xhci->lock, flags);
 
@@ -1225,7 +1249,7 @@ void xhci_stop_endpoint_command_watchdog(struct timer_list *t)
        usbsts = readl(&xhci->op_regs->status);
 
        xhci_warn(xhci, "xHCI host not responding to stop endpoint command.\n");
-       xhci_warn(xhci, "USBSTS:%s\n", xhci_decode_usbsts(usbsts));
+       xhci_warn(xhci, "USBSTS:%s\n", xhci_decode_usbsts(str, usbsts));
 
        ep->ep_state &= ~EP_STOP_CMD_PENDING;
 
@@ -1389,7 +1413,12 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id,
                ep_ring = xhci_urb_to_transfer_ring(ep->xhci, td->urb);
                if (td->cancel_status == TD_CLEARING_CACHE) {
                        td->cancel_status = TD_CLEARED;
+                       xhci_dbg(ep->xhci, "%s: Giveback cancelled URB %p TD\n",
+                                __func__, td->urb);
                        xhci_td_cleanup(ep->xhci, td, ep_ring, td->status);
+               } else {
+                       xhci_dbg(ep->xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n",
+                                __func__, td->urb, td->cancel_status);
                }
        }
 cleanup:
@@ -2002,7 +2031,8 @@ cleanup:
         * bits are still set.  When an event occurs, switch over to
         * polling to avoid losing status changes.
         */
-       xhci_dbg(xhci, "%s: starting port polling.\n", __func__);
+       xhci_dbg(xhci, "%s: starting usb%d port polling.\n",
+                __func__, hcd->self.busnum);
        set_bit(HCD_FLAG_POLL_RH, &hcd->flags);
        spin_unlock(&xhci->lock);
        /* Pass this up to the core */
index 627abd2..a5da020 100644 (file)
@@ -25,8 +25,6 @@
 #include "xhci.h"
 #include "xhci-dbgcap.h"
 
-#define XHCI_MSG_MAX   500
-
 DECLARE_EVENT_CLASS(xhci_log_msg,
        TP_PROTO(struct va_format *vaf),
        TP_ARGS(vaf),
@@ -122,6 +120,7 @@ DECLARE_EVENT_CLASS(xhci_log_trb,
                __field(u32, field1)
                __field(u32, field2)
                __field(u32, field3)
+               __dynamic_array(char, str, XHCI_MSG_MAX)
        ),
        TP_fast_assign(
                __entry->type = ring->type;
@@ -131,7 +130,7 @@ DECLARE_EVENT_CLASS(xhci_log_trb,
                __entry->field3 = le32_to_cpu(trb->field[3]);
        ),
        TP_printk("%s: %s", xhci_ring_type_string(__entry->type),
-                       xhci_decode_trb(__entry->field0, __entry->field1,
+                 xhci_decode_trb(__get_str(str), XHCI_MSG_MAX, __entry->field0, __entry->field1,
                                        __entry->field2, __entry->field3)
        )
 );
@@ -323,6 +322,7 @@ DECLARE_EVENT_CLASS(xhci_log_ep_ctx,
                __field(u32, info2)
                __field(u64, deq)
                __field(u32, tx_info)
+               __dynamic_array(char, str, XHCI_MSG_MAX)
        ),
        TP_fast_assign(
                __entry->info = le32_to_cpu(ctx->ep_info);
@@ -330,8 +330,8 @@ DECLARE_EVENT_CLASS(xhci_log_ep_ctx,
                __entry->deq = le64_to_cpu(ctx->deq);
                __entry->tx_info = le32_to_cpu(ctx->tx_info);
        ),
-       TP_printk("%s", xhci_decode_ep_context(__entry->info,
-               __entry->info2, __entry->deq, __entry->tx_info)
+       TP_printk("%s", xhci_decode_ep_context(__get_str(str),
+               __entry->info, __entry->info2, __entry->deq, __entry->tx_info)
        )
 );
 
@@ -368,6 +368,7 @@ DECLARE_EVENT_CLASS(xhci_log_slot_ctx,
                __field(u32, info2)
                __field(u32, tt_info)
                __field(u32, state)
+               __dynamic_array(char, str, XHCI_MSG_MAX)
        ),
        TP_fast_assign(
                __entry->info = le32_to_cpu(ctx->dev_info);
@@ -375,9 +376,9 @@ DECLARE_EVENT_CLASS(xhci_log_slot_ctx,
                __entry->tt_info = le64_to_cpu(ctx->tt_info);
                __entry->state = le32_to_cpu(ctx->dev_state);
        ),
-       TP_printk("%s", xhci_decode_slot_context(__entry->info,
-                       __entry->info2, __entry->tt_info,
-                       __entry->state)
+       TP_printk("%s", xhci_decode_slot_context(__get_str(str),
+                       __entry->info, __entry->info2,
+                       __entry->tt_info, __entry->state)
        )
 );
 
@@ -432,12 +433,13 @@ DECLARE_EVENT_CLASS(xhci_log_ctrl_ctx,
        TP_STRUCT__entry(
                __field(u32, drop)
                __field(u32, add)
+               __dynamic_array(char, str, XHCI_MSG_MAX)
        ),
        TP_fast_assign(
                __entry->drop = le32_to_cpu(ctrl_ctx->drop_flags);
                __entry->add = le32_to_cpu(ctrl_ctx->add_flags);
        ),
-       TP_printk("%s", xhci_decode_ctrl_ctx(__entry->drop, __entry->add)
+       TP_printk("%s", xhci_decode_ctrl_ctx(__get_str(str), __entry->drop, __entry->add)
        )
 );
 
@@ -523,6 +525,7 @@ DECLARE_EVENT_CLASS(xhci_log_portsc,
                    TP_STRUCT__entry(
                                     __field(u32, portnum)
                                     __field(u32, portsc)
+                                    __dynamic_array(char, str, XHCI_MSG_MAX)
                                     ),
                    TP_fast_assign(
                                   __entry->portnum = portnum;
@@ -530,7 +533,7 @@ DECLARE_EVENT_CLASS(xhci_log_portsc,
                                   ),
                    TP_printk("port-%d: %s",
                              __entry->portnum,
-                             xhci_decode_portsc(__entry->portsc)
+                             xhci_decode_portsc(__get_str(str), __entry->portsc)
                              )
 );
 
@@ -555,13 +558,14 @@ DECLARE_EVENT_CLASS(xhci_log_doorbell,
        TP_STRUCT__entry(
                __field(u32, slot)
                __field(u32, doorbell)
+               __dynamic_array(char, str, XHCI_MSG_MAX)
        ),
        TP_fast_assign(
                __entry->slot = slot;
                __entry->doorbell = doorbell;
        ),
        TP_printk("Ring doorbell for %s",
-               xhci_decode_doorbell(__entry->slot, __entry->doorbell)
+                 xhci_decode_doorbell(__get_str(str), __entry->slot, __entry->doorbell)
        )
 );
 
index 3618070..f3dabd0 100644 (file)
@@ -993,7 +993,8 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup)
        xhci_dbc_suspend(xhci);
 
        /* Don't poll the roothubs on bus suspend. */
-       xhci_dbg(xhci, "%s: stopping port polling.\n", __func__);
+       xhci_dbg(xhci, "%s: stopping usb%d port polling.\n",
+                __func__, hcd->self.busnum);
        clear_bit(HCD_FLAG_POLL_RH, &hcd->flags);
        del_timer_sync(&hcd->rh_timer);
        clear_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags);
@@ -1257,7 +1258,8 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated)
                usb_asmedia_modifyflowcontrol(to_pci_dev(hcd->self.controller));
 
        /* Re-enable port polling. */
-       xhci_dbg(xhci, "%s: starting port polling.\n", __func__);
+       xhci_dbg(xhci, "%s: starting usb%d port polling.\n",
+                __func__, hcd->self.busnum);
        set_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags);
        usb_hcd_poll_rh_status(xhci->shared_hcd);
        set_bit(HCD_FLAG_POLL_RH, &hcd->flags);
@@ -4705,19 +4707,19 @@ static u16 xhci_calculate_u1_timeout(struct xhci_hcd *xhci,
 {
        unsigned long long timeout_ns;
 
-       if (xhci->quirks & XHCI_INTEL_HOST)
-               timeout_ns = xhci_calculate_intel_u1_timeout(udev, desc);
-       else
-               timeout_ns = udev->u1_params.sel;
-
        /* Prevent U1 if service interval is shorter than U1 exit latency */
        if (usb_endpoint_xfer_int(desc) || usb_endpoint_xfer_isoc(desc)) {
-               if (xhci_service_interval_to_ns(desc) <= timeout_ns) {
+               if (xhci_service_interval_to_ns(desc) <= udev->u1_params.mel) {
                        dev_dbg(&udev->dev, "Disable U1, ESIT shorter than exit latency\n");
                        return USB3_LPM_DISABLED;
                }
        }
 
+       if (xhci->quirks & XHCI_INTEL_HOST)
+               timeout_ns = xhci_calculate_intel_u1_timeout(udev, desc);
+       else
+               timeout_ns = udev->u1_params.sel;
+
        /* The U1 timeout is encoded in 1us intervals.
         * Don't return a timeout of zero, because that's USB3_LPM_DISABLED.
         */
@@ -4769,19 +4771,19 @@ static u16 xhci_calculate_u2_timeout(struct xhci_hcd *xhci,
 {
        unsigned long long timeout_ns;
 
-       if (xhci->quirks & XHCI_INTEL_HOST)
-               timeout_ns = xhci_calculate_intel_u2_timeout(udev, desc);
-       else
-               timeout_ns = udev->u2_params.sel;
-
        /* Prevent U2 if service interval is shorter than U2 exit latency */
        if (usb_endpoint_xfer_int(desc) || usb_endpoint_xfer_isoc(desc)) {
-               if (xhci_service_interval_to_ns(desc) <= timeout_ns) {
+               if (xhci_service_interval_to_ns(desc) <= udev->u2_params.mel) {
                        dev_dbg(&udev->dev, "Disable U2, ESIT shorter than exit latency\n");
                        return USB3_LPM_DISABLED;
                }
        }
 
+       if (xhci->quirks & XHCI_INTEL_HOST)
+               timeout_ns = xhci_calculate_intel_u2_timeout(udev, desc);
+       else
+               timeout_ns = udev->u2_params.sel;
+
        /* The U2 timeout is encoded in 256us intervals */
        timeout_ns = DIV_ROUND_UP_ULL(timeout_ns, 256 * 1000);
        /* If the necessary timeout value is bigger than what we can set in the
index 3c7d281..dca6181 100644 (file)
@@ -22,6 +22,9 @@
 #include       "xhci-ext-caps.h"
 #include "pci-quirks.h"
 
+/* max buffer size for trace and debug messages */
+#define XHCI_MSG_MAX           500
+
 /* xHCI PCI Configuration Registers */
 #define XHCI_SBRN_OFFSET       (0x60)
 
@@ -2235,15 +2238,14 @@ static inline char *xhci_slot_state_string(u32 state)
        }
 }
 
-static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2,
-               u32 field3)
+static inline const char *xhci_decode_trb(char *str, size_t size,
+                                         u32 field0, u32 field1, u32 field2, u32 field3)
 {
-       static char str[256];
        int type = TRB_FIELD_TO_TYPE(field3);
 
        switch (type) {
        case TRB_LINK:
-               sprintf(str,
+               snprintf(str, size,
                        "LINK %08x%08x intr %d type '%s' flags %c:%c:%c:%c",
                        field1, field0, GET_INTR_TARGET(field2),
                        xhci_trb_type_string(type),
@@ -2260,7 +2262,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2,
        case TRB_HC_EVENT:
        case TRB_DEV_NOTE:
        case TRB_MFINDEX_WRAP:
-               sprintf(str,
+               snprintf(str, size,
                        "TRB %08x%08x status '%s' len %d slot %d ep %d type '%s' flags %c:%c",
                        field1, field0,
                        xhci_trb_comp_code_string(GET_COMP_CODE(field2)),
@@ -2273,7 +2275,8 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2,
 
                break;
        case TRB_SETUP:
-               sprintf(str, "bRequestType %02x bRequest %02x wValue %02x%02x wIndex %02x%02x wLength %d length %d TD size %d intr %d type '%s' flags %c:%c:%c",
+               snprintf(str, size,
+                       "bRequestType %02x bRequest %02x wValue %02x%02x wIndex %02x%02x wLength %d length %d TD size %d intr %d type '%s' flags %c:%c:%c",
                                field0 & 0xff,
                                (field0 & 0xff00) >> 8,
                                (field0 & 0xff000000) >> 24,
@@ -2290,7 +2293,8 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2,
                                field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_DATA:
-               sprintf(str, "Buffer %08x%08x length %d TD size %d intr %d type '%s' flags %c:%c:%c:%c:%c:%c:%c",
+               snprintf(str, size,
+                        "Buffer %08x%08x length %d TD size %d intr %d type '%s' flags %c:%c:%c:%c:%c:%c:%c",
                                field1, field0, TRB_LEN(field2), GET_TD_SIZE(field2),
                                GET_INTR_TARGET(field2),
                                xhci_trb_type_string(type),
@@ -2303,7 +2307,8 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2,
                                field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_STATUS:
-               sprintf(str, "Buffer %08x%08x length %d TD size %d intr %d type '%s' flags %c:%c:%c:%c",
+               snprintf(str, size,
+                        "Buffer %08x%08x length %d TD size %d intr %d type '%s' flags %c:%c:%c:%c",
                                field1, field0, TRB_LEN(field2), GET_TD_SIZE(field2),
                                GET_INTR_TARGET(field2),
                                xhci_trb_type_string(type),
@@ -2316,7 +2321,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2,
        case TRB_ISOC:
        case TRB_EVENT_DATA:
        case TRB_TR_NOOP:
-               sprintf(str,
+               snprintf(str, size,
                        "Buffer %08x%08x length %d TD size %d intr %d type '%s' flags %c:%c:%c:%c:%c:%c:%c:%c",
                        field1, field0, TRB_LEN(field2), GET_TD_SIZE(field2),
                        GET_INTR_TARGET(field2),
@@ -2333,21 +2338,21 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2,
 
        case TRB_CMD_NOOP:
        case TRB_ENABLE_SLOT:
-               sprintf(str,
+               snprintf(str, size,
                        "%s: flags %c",
                        xhci_trb_type_string(type),
                        field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_DISABLE_SLOT:
        case TRB_NEG_BANDWIDTH:
-               sprintf(str,
+               snprintf(str, size,
                        "%s: slot %d flags %c",
                        xhci_trb_type_string(type),
                        TRB_TO_SLOT_ID(field3),
                        field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_ADDR_DEV:
-               sprintf(str,
+               snprintf(str, size,
                        "%s: ctx %08x%08x slot %d flags %c:%c",
                        xhci_trb_type_string(type),
                        field1, field0,
@@ -2356,7 +2361,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2,
                        field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_CONFIG_EP:
-               sprintf(str,
+               snprintf(str, size,
                        "%s: ctx %08x%08x slot %d flags %c:%c",
                        xhci_trb_type_string(type),
                        field1, field0,
@@ -2365,7 +2370,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2,
                        field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_EVAL_CONTEXT:
-               sprintf(str,
+               snprintf(str, size,
                        "%s: ctx %08x%08x slot %d flags %c",
                        xhci_trb_type_string(type),
                        field1, field0,
@@ -2373,7 +2378,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2,
                        field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_RESET_EP:
-               sprintf(str,
+               snprintf(str, size,
                        "%s: ctx %08x%08x slot %d ep %d flags %c:%c",
                        xhci_trb_type_string(type),
                        field1, field0,
@@ -2394,7 +2399,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2,
                        field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_SET_DEQ:
-               sprintf(str,
+               snprintf(str, size,
                        "%s: deq %08x%08x stream %d slot %d ep %d flags %c",
                        xhci_trb_type_string(type),
                        field1, field0,
@@ -2405,14 +2410,14 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2,
                        field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_RESET_DEV:
-               sprintf(str,
+               snprintf(str, size,
                        "%s: slot %d flags %c",
                        xhci_trb_type_string(type),
                        TRB_TO_SLOT_ID(field3),
                        field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_FORCE_EVENT:
-               sprintf(str,
+               snprintf(str, size,
                        "%s: event %08x%08x vf intr %d vf id %d flags %c",
                        xhci_trb_type_string(type),
                        field1, field0,
@@ -2421,14 +2426,14 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2,
                        field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_SET_LT:
-               sprintf(str,
+               snprintf(str, size,
                        "%s: belt %d flags %c",
                        xhci_trb_type_string(type),
                        TRB_TO_BELT(field3),
                        field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_GET_BW:
-               sprintf(str,
+               snprintf(str, size,
                        "%s: ctx %08x%08x slot %d speed %d flags %c",
                        xhci_trb_type_string(type),
                        field1, field0,
@@ -2437,7 +2442,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2,
                        field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_FORCE_HEADER:
-               sprintf(str,
+               snprintf(str, size,
                        "%s: info %08x%08x%08x pkt type %d roothub port %d flags %c",
                        xhci_trb_type_string(type),
                        field2, field1, field0 & 0xffffffe0,
@@ -2446,7 +2451,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2,
                        field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        default:
-               sprintf(str,
+               snprintf(str, size,
                        "type '%s' -> raw %08x %08x %08x %08x",
                        xhci_trb_type_string(type),
                        field0, field1, field2, field3);
@@ -2455,10 +2460,9 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2,
        return str;
 }
 
-static inline const char *xhci_decode_ctrl_ctx(unsigned long drop,
-                                              unsigned long add)
+static inline const char *xhci_decode_ctrl_ctx(char *str,
+               unsigned long drop, unsigned long add)
 {
-       static char     str[1024];
        unsigned int    bit;
        int             ret = 0;
 
@@ -2484,10 +2488,9 @@ static inline const char *xhci_decode_ctrl_ctx(unsigned long drop,
        return str;
 }
 
-static inline const char *xhci_decode_slot_context(u32 info, u32 info2,
-               u32 tt_info, u32 state)
+static inline const char *xhci_decode_slot_context(char *str,
+               u32 info, u32 info2, u32 tt_info, u32 state)
 {
-       static char str[1024];
        u32 speed;
        u32 hub;
        u32 mtt;
@@ -2571,9 +2574,8 @@ static inline const char *xhci_portsc_link_state_string(u32 portsc)
        return "Unknown";
 }
 
-static inline const char *xhci_decode_portsc(u32 portsc)
+static inline const char *xhci_decode_portsc(char *str, u32 portsc)
 {
-       static char str[256];
        int ret;
 
        ret = sprintf(str, "%s %s %s Link:%s PortSpeed:%d ",
@@ -2617,9 +2619,8 @@ static inline const char *xhci_decode_portsc(u32 portsc)
        return str;
 }
 
-static inline const char *xhci_decode_usbsts(u32 usbsts)
+static inline const char *xhci_decode_usbsts(char *str, u32 usbsts)
 {
-       static char str[256];
        int ret = 0;
 
        if (usbsts == ~(u32)0)
@@ -2646,9 +2647,8 @@ static inline const char *xhci_decode_usbsts(u32 usbsts)
        return str;
 }
 
-static inline const char *xhci_decode_doorbell(u32 slot, u32 doorbell)
+static inline const char *xhci_decode_doorbell(char *str, u32 slot, u32 doorbell)
 {
-       static char str[256];
        u8 ep;
        u16 stream;
        int ret;
@@ -2715,10 +2715,9 @@ static inline const char *xhci_ep_type_string(u8 type)
        }
 }
 
-static inline const char *xhci_decode_ep_context(u32 info, u32 info2, u64 deq,
-               u32 tx_info)
+static inline const char *xhci_decode_ep_context(char *str, u32 info,
+               u32 info2, u64 deq, u32 tx_info)
 {
-       static char str[1024];
        int ret;
 
        u32 esit;
index ff07e28..d1d9a7d 100644 (file)
@@ -30,6 +30,7 @@ static int isp1760_init_core(struct isp1760_device *isp)
 {
        struct isp1760_hcd *hcd = &isp->hcd;
        struct isp1760_udc *udc = &isp->udc;
+       u32 otg_ctrl;
 
        /* Low-level chip reset */
        if (isp->rst_gpio) {
@@ -83,16 +84,17 @@ static int isp1760_init_core(struct isp1760_device *isp)
         *
         * TODO: Really support OTG. For now we configure port 1 in device mode
         */
-       if (((isp->devflags & ISP1760_FLAG_ISP1761) ||
-            (isp->devflags & ISP1760_FLAG_ISP1763)) &&
-           (isp->devflags & ISP1760_FLAG_PERIPHERAL_EN)) {
-               isp1760_field_set(hcd->fields, HW_DM_PULLDOWN);
-               isp1760_field_set(hcd->fields, HW_DP_PULLDOWN);
-               isp1760_field_set(hcd->fields, HW_OTG_DISABLE);
-       } else {
-               isp1760_field_set(hcd->fields, HW_SW_SEL_HC_DC);
-               isp1760_field_set(hcd->fields, HW_VBUS_DRV);
-               isp1760_field_set(hcd->fields, HW_SEL_CP_EXT);
+       if (isp->devflags & ISP1760_FLAG_ISP1761) {
+               if (isp->devflags & ISP1760_FLAG_PERIPHERAL_EN) {
+                       otg_ctrl = (ISP176x_HW_DM_PULLDOWN_CLEAR |
+                                   ISP176x_HW_DP_PULLDOWN_CLEAR |
+                                   ISP176x_HW_OTG_DISABLE);
+               } else {
+                       otg_ctrl = (ISP176x_HW_SW_SEL_HC_DC_CLEAR |
+                                   ISP176x_HW_VBUS_DRV |
+                                   ISP176x_HW_SEL_CP_EXT);
+               }
+               isp1760_reg_write(hcd->regs, ISP176x_HC_OTG_CTRL, otg_ctrl);
        }
 
        dev_info(isp->dev, "%s bus width: %u, oc: %s\n",
@@ -235,20 +237,20 @@ static const struct reg_field isp1760_hc_reg_fields[] = {
        [HC_ISO_IRQ_MASK_AND]   = REG_FIELD(ISP176x_HC_ISO_IRQ_MASK_AND, 0, 31),
        [HC_INT_IRQ_MASK_AND]   = REG_FIELD(ISP176x_HC_INT_IRQ_MASK_AND, 0, 31),
        [HC_ATL_IRQ_MASK_AND]   = REG_FIELD(ISP176x_HC_ATL_IRQ_MASK_AND, 0, 31),
-       [HW_OTG_DISABLE]        = REG_FIELD(ISP176x_HC_OTG_CTRL_SET, 10, 10),
-       [HW_SW_SEL_HC_DC]       = REG_FIELD(ISP176x_HC_OTG_CTRL_SET, 7, 7),
-       [HW_VBUS_DRV]           = REG_FIELD(ISP176x_HC_OTG_CTRL_SET, 4, 4),
-       [HW_SEL_CP_EXT]         = REG_FIELD(ISP176x_HC_OTG_CTRL_SET, 3, 3),
-       [HW_DM_PULLDOWN]        = REG_FIELD(ISP176x_HC_OTG_CTRL_SET, 2, 2),
-       [HW_DP_PULLDOWN]        = REG_FIELD(ISP176x_HC_OTG_CTRL_SET, 1, 1),
-       [HW_DP_PULLUP]          = REG_FIELD(ISP176x_HC_OTG_CTRL_SET, 0, 0),
-       [HW_OTG_DISABLE_CLEAR]  = REG_FIELD(ISP176x_HC_OTG_CTRL_CLEAR, 10, 10),
-       [HW_SW_SEL_HC_DC_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL_CLEAR, 7, 7),
-       [HW_VBUS_DRV_CLEAR]     = REG_FIELD(ISP176x_HC_OTG_CTRL_CLEAR, 4, 4),
-       [HW_SEL_CP_EXT_CLEAR]   = REG_FIELD(ISP176x_HC_OTG_CTRL_CLEAR, 3, 3),
-       [HW_DM_PULLDOWN_CLEAR]  = REG_FIELD(ISP176x_HC_OTG_CTRL_CLEAR, 2, 2),
-       [HW_DP_PULLDOWN_CLEAR]  = REG_FIELD(ISP176x_HC_OTG_CTRL_CLEAR, 1, 1),
-       [HW_DP_PULLUP_CLEAR]    = REG_FIELD(ISP176x_HC_OTG_CTRL_CLEAR, 0, 0),
+       [HW_OTG_DISABLE_CLEAR]  = REG_FIELD(ISP176x_HC_OTG_CTRL, 26, 26),
+       [HW_SW_SEL_HC_DC_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL, 23, 23),
+       [HW_VBUS_DRV_CLEAR]     = REG_FIELD(ISP176x_HC_OTG_CTRL, 20, 20),
+       [HW_SEL_CP_EXT_CLEAR]   = REG_FIELD(ISP176x_HC_OTG_CTRL, 19, 19),
+       [HW_DM_PULLDOWN_CLEAR]  = REG_FIELD(ISP176x_HC_OTG_CTRL, 18, 18),
+       [HW_DP_PULLDOWN_CLEAR]  = REG_FIELD(ISP176x_HC_OTG_CTRL, 17, 17),
+       [HW_DP_PULLUP_CLEAR]    = REG_FIELD(ISP176x_HC_OTG_CTRL, 16, 16),
+       [HW_OTG_DISABLE]        = REG_FIELD(ISP176x_HC_OTG_CTRL, 10, 10),
+       [HW_SW_SEL_HC_DC]       = REG_FIELD(ISP176x_HC_OTG_CTRL, 7, 7),
+       [HW_VBUS_DRV]           = REG_FIELD(ISP176x_HC_OTG_CTRL, 4, 4),
+       [HW_SEL_CP_EXT]         = REG_FIELD(ISP176x_HC_OTG_CTRL, 3, 3),
+       [HW_DM_PULLDOWN]        = REG_FIELD(ISP176x_HC_OTG_CTRL, 2, 2),
+       [HW_DP_PULLDOWN]        = REG_FIELD(ISP176x_HC_OTG_CTRL, 1, 1),
+       [HW_DP_PULLUP]          = REG_FIELD(ISP176x_HC_OTG_CTRL, 0, 0),
 };
 
 static const struct reg_field isp1763_hc_reg_fields[] = {
@@ -491,7 +493,7 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags,
                       (devflags & ISP1760_FLAG_ISP1761));
 
        if ((!IS_ENABLED(CONFIG_USB_ISP1760_HCD) || usb_disabled()) &&
-           (!IS_ENABLED(CONFIG_USB_ISP1761_UDC) || !udc_enabled))
+           (!udc_enabled || !IS_ENABLED(CONFIG_USB_ISP1761_UDC)))
                return -ENODEV;
 
        isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
@@ -571,7 +573,7 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags,
                        return ret;
        }
 
-       if (IS_ENABLED(CONFIG_USB_ISP1761_UDC) && udc_enabled) {
+       if (udc_enabled && IS_ENABLED(CONFIG_USB_ISP1761_UDC)) {
                ret = isp1760_udc_register(isp, irq, irqflags);
                if (ret < 0) {
                        isp1760_hcd_unregister(hcd);
index 27168b4..79d571f 100644 (file)
@@ -182,7 +182,7 @@ struct urb_listitem {
        struct urb *urb;
 };
 
-static const u32 isp1763_hc_portsc1_fields[] = {
+static const u32 isp176x_hc_portsc1_fields[] = {
        [PORT_OWNER]            = BIT(13),
        [PORT_POWER]            = BIT(12),
        [PORT_LSTATUS]          = BIT(10),
@@ -205,27 +205,28 @@ static u32 isp1760_hcd_read(struct usb_hcd *hcd, u32 field)
 }
 
 /*
- * We need, in isp1763, to write directly the values to the portsc1
+ * We need, in isp176x, to write directly the values to the portsc1
  * register so it will make the other values to trigger.
  */
 static void isp1760_hcd_portsc1_set_clear(struct isp1760_hcd *priv, u32 field,
                                          u32 val)
 {
-       u32 bit = isp1763_hc_portsc1_fields[field];
-       u32 port_status = readl(priv->base + ISP1763_HC_PORTSC1);
+       u32 bit = isp176x_hc_portsc1_fields[field];
+       u16 portsc1_reg = priv->is_isp1763 ? ISP1763_HC_PORTSC1 :
+               ISP176x_HC_PORTSC1;
+       u32 port_status = readl(priv->base + portsc1_reg);
 
        if (val)
-               writel(port_status | bit, priv->base + ISP1763_HC_PORTSC1);
+               writel(port_status | bit, priv->base + portsc1_reg);
        else
-               writel(port_status & ~bit, priv->base + ISP1763_HC_PORTSC1);
+               writel(port_status & ~bit, priv->base + portsc1_reg);
 }
 
 static void isp1760_hcd_write(struct usb_hcd *hcd, u32 field, u32 val)
 {
        struct isp1760_hcd *priv = hcd_to_priv(hcd);
 
-       if (unlikely(priv->is_isp1763 &&
-                    (field >= PORT_OWNER && field <= PORT_CONNECT)))
+       if (unlikely((field >= PORT_OWNER && field <= PORT_CONNECT)))
                return isp1760_hcd_portsc1_set_clear(priv, field, val);
 
        isp1760_field_write(priv->fields, field, val);
@@ -250,7 +251,7 @@ static int isp1760_hcd_set_and_wait(struct usb_hcd *hcd, u32 field,
        isp1760_hcd_set(hcd, field);
 
        return regmap_field_read_poll_timeout(priv->fields[field], val,
-                                             val, 10, timeout_us);
+                                             val, 0, timeout_us);
 }
 
 static int isp1760_hcd_set_and_wait_swap(struct usb_hcd *hcd, u32 field,
@@ -262,7 +263,7 @@ static int isp1760_hcd_set_and_wait_swap(struct usb_hcd *hcd, u32 field,
        isp1760_hcd_set(hcd, field);
 
        return regmap_field_read_poll_timeout(priv->fields[field], val,
-                                             !val, 10, timeout_us);
+                                             !val, 0, timeout_us);
 }
 
 static int isp1760_hcd_clear_and_wait(struct usb_hcd *hcd, u32 field,
@@ -274,7 +275,7 @@ static int isp1760_hcd_clear_and_wait(struct usb_hcd *hcd, u32 field,
        isp1760_hcd_clear(hcd, field);
 
        return regmap_field_read_poll_timeout(priv->fields[field], val,
-                                             !val, 10, timeout_us);
+                                             !val, 0, timeout_us);
 }
 
 static bool isp1760_hcd_is_set(struct usb_hcd *hcd, u32 field)
@@ -367,8 +368,7 @@ static void isp1760_mem_read(struct usb_hcd *hcd, u32 src_offset, void *dst,
 {
        struct isp1760_hcd *priv = hcd_to_priv(hcd);
 
-       isp1760_hcd_write(hcd, MEM_BANK_SEL, ISP_BANK_0);
-       isp1760_hcd_write(hcd, MEM_START_ADDR, src_offset);
+       isp1760_reg_write(priv->regs, ISP176x_HC_MEMORY, src_offset);
        ndelay(100);
 
        bank_reads8(priv->base, src_offset, ISP_BANK_0, dst, bytes);
@@ -496,8 +496,7 @@ static void isp1760_ptd_read(struct usb_hcd *hcd, u32 ptd_offset, u32 slot,
        u16 src_offset = ptd_offset + slot * sizeof(*ptd);
        struct isp1760_hcd *priv = hcd_to_priv(hcd);
 
-       isp1760_hcd_write(hcd, MEM_BANK_SEL, ISP_BANK_0);
-       isp1760_hcd_write(hcd, MEM_START_ADDR, src_offset);
+       isp1760_reg_write(priv->regs, ISP176x_HC_MEMORY, src_offset);
        ndelay(90);
 
        bank_reads8(priv->base, src_offset, ISP_BANK_0, (void *)ptd,
@@ -588,8 +587,8 @@ static void init_memory(struct isp1760_hcd *priv)
 
        payload_addr = PAYLOAD_OFFSET;
 
-       for (i = 0, curr = 0; i < ARRAY_SIZE(mem->blocks); i++) {
-               for (j = 0; j < mem->blocks[i]; j++, curr++) {
+       for (i = 0, curr = 0; i < ARRAY_SIZE(mem->blocks); i++, curr += j) {
+               for (j = 0; j < mem->blocks[i]; j++) {
                        priv->memory_pool[curr + j].start = payload_addr;
                        priv->memory_pool[curr + j].size = mem->blocks_size[i];
                        priv->memory_pool[curr + j].free = 1;
@@ -731,12 +730,15 @@ static int isp1760_hc_setup(struct usb_hcd *hcd)
 
        isp1760_hcd_write(hcd, HC_SCRATCH, pattern);
 
-       /* Change bus pattern */
-       scratch = isp1760_hcd_read(hcd, HC_CHIP_ID_HIGH);
-       dev_err(hcd->self.controller, "Scratch test 0x%08x\n", scratch);
+       /*
+        * we do not care about the read value here we just want to
+        * change bus pattern.
+        */
+       isp1760_hcd_read(hcd, HC_CHIP_ID_HIGH);
        scratch = isp1760_hcd_read(hcd, HC_SCRATCH);
        if (scratch != pattern) {
-               dev_err(hcd->self.controller, "Scratch test failed. 0x%08x\n", scratch);
+               dev_err(hcd->self.controller, "Scratch test failed. 0x%08x\n",
+                       scratch);
                return -ENODEV;
        }
 
@@ -1826,9 +1828,11 @@ static void packetize_urb(struct usb_hcd *hcd,
                        goto cleanup;
 
                if (len > mem->blocks_size[ISP176x_BLOCK_NUM - 1])
-                       len = mem->blocks_size[ISP176x_BLOCK_NUM - 1];
+                       this_qtd_len = mem->blocks_size[ISP176x_BLOCK_NUM - 1];
+               else
+                       this_qtd_len = len;
 
-               this_qtd_len = qtd_fill(qtd, buf, len);
+               this_qtd_len = qtd_fill(qtd, buf, this_qtd_len);
                list_add_tail(&qtd->qtd_list, head);
 
                len -= this_qtd_len;
@@ -1851,7 +1855,7 @@ static void packetize_urb(struct usb_hcd *hcd,
                                packet_type = OUT_PID;
                        else
                                packet_type = IN_PID;
-               } else if (usb_pipebulk(urb->pipe)
+               } else if (usb_pipebulk(urb->pipe) && maxpacketsize
                                && (urb->transfer_flags & URB_ZERO_PACKET)
                                && !(urb->transfer_buffer_length %
                                                        maxpacketsize)) {
@@ -1916,7 +1920,6 @@ static int isp1760_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
        if (list_empty(&new_qtds))
                return -ENOMEM;
 
-       retval = 0;
        spin_lock_irqsave(&priv->lock, spinflags);
 
        if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) {
@@ -1972,16 +1975,20 @@ static void kill_transfer(struct usb_hcd *hcd, struct urb *urb,
        /* We need to forcefully reclaim the slot since some transfers never
           return, e.g. interrupt transfers and NAKed bulk transfers. */
        if (usb_pipecontrol(urb->pipe) || usb_pipebulk(urb->pipe)) {
-               skip_map = isp1760_hcd_read(hcd, HC_ATL_PTD_SKIPMAP);
-               skip_map |= (1 << qh->slot);
-               isp1760_hcd_write(hcd, HC_ATL_PTD_SKIPMAP, skip_map);
-               ndelay(100);
+               if (qh->slot != -1) {
+                       skip_map = isp1760_hcd_read(hcd, HC_ATL_PTD_SKIPMAP);
+                       skip_map |= (1 << qh->slot);
+                       isp1760_hcd_write(hcd, HC_ATL_PTD_SKIPMAP, skip_map);
+                       ndelay(100);
+               }
                priv->atl_slots[qh->slot].qh = NULL;
                priv->atl_slots[qh->slot].qtd = NULL;
        } else {
-               skip_map = isp1760_hcd_read(hcd, HC_INT_PTD_SKIPMAP);
-               skip_map |= (1 << qh->slot);
-               isp1760_hcd_write(hcd, HC_INT_PTD_SKIPMAP, skip_map);
+               if (qh->slot != -1) {
+                       skip_map = isp1760_hcd_read(hcd, HC_INT_PTD_SKIPMAP);
+                       skip_map |= (1 << qh->slot);
+                       isp1760_hcd_write(hcd, HC_INT_PTD_SKIPMAP, skip_map);
+               }
                priv->int_slots[qh->slot].qh = NULL;
                priv->int_slots[qh->slot].qtd = NULL;
        }
@@ -2528,17 +2535,23 @@ int __init isp1760_init_kmem_once(void)
                        SLAB_MEM_SPREAD, NULL);
 
        if (!qtd_cachep)
-               return -ENOMEM;
+               goto destroy_urb_listitem;
 
        qh_cachep = kmem_cache_create("isp1760_qh", sizeof(struct isp1760_qh),
                        0, SLAB_TEMPORARY | SLAB_MEM_SPREAD, NULL);
 
-       if (!qh_cachep) {
-               kmem_cache_destroy(qtd_cachep);
-               return -ENOMEM;
-       }
+       if (!qh_cachep)
+               goto destroy_qtd;
 
        return 0;
+
+destroy_qtd:
+       kmem_cache_destroy(qtd_cachep);
+
+destroy_urb_listitem:
+       kmem_cache_destroy(urb_listitem_cachep);
+
+       return -ENOMEM;
 }
 
 void isp1760_deinit_kmem_cache(void)
index 94ea60c..3a67511 100644 (file)
@@ -61,6 +61,7 @@
 #define ISP176x_HC_INT_IRQ_MASK_AND    0x328
 #define ISP176x_HC_ATL_IRQ_MASK_AND    0x32c
 
+#define ISP176x_HC_OTG_CTRL            0x374
 #define ISP176x_HC_OTG_CTRL_SET                0x374
 #define ISP176x_HC_OTG_CTRL_CLEAR      0x376
 
@@ -179,6 +180,21 @@ enum isp176x_host_controller_fields {
 #define ISP176x_DC_IESUSP              BIT(3)
 #define ISP176x_DC_IEBRST              BIT(0)
 
+#define ISP176x_HW_OTG_DISABLE_CLEAR   BIT(26)
+#define ISP176x_HW_SW_SEL_HC_DC_CLEAR  BIT(23)
+#define ISP176x_HW_VBUS_DRV_CLEAR      BIT(20)
+#define ISP176x_HW_SEL_CP_EXT_CLEAR    BIT(19)
+#define ISP176x_HW_DM_PULLDOWN_CLEAR   BIT(18)
+#define ISP176x_HW_DP_PULLDOWN_CLEAR   BIT(17)
+#define ISP176x_HW_DP_PULLUP_CLEAR     BIT(16)
+#define ISP176x_HW_OTG_DISABLE         BIT(10)
+#define ISP176x_HW_SW_SEL_HC_DC                BIT(7)
+#define ISP176x_HW_VBUS_DRV            BIT(4)
+#define ISP176x_HW_SEL_CP_EXT          BIT(3)
+#define ISP176x_HW_DM_PULLDOWN         BIT(2)
+#define ISP176x_HW_DP_PULLDOWN         BIT(1)
+#define ISP176x_HW_DP_PULLUP           BIT(0)
+
 #define ISP176x_DC_ENDPTYP_ISOC                0x01
 #define ISP176x_DC_ENDPTYP_BULK                0x02
 #define ISP176x_DC_ENDPTYP_INTERRUPT   0x03
index a78da59..5cafd23 100644 (file)
@@ -1363,7 +1363,7 @@ static irqreturn_t isp1760_udc_irq(int irq, void *dev)
 
        status = isp1760_udc_irq_get_status(udc);
 
-       if (status & DC_IEVBUS) {
+       if (status & ISP176x_DC_IEVBUS) {
                dev_dbg(udc->isp->dev, "%s(VBUS)\n", __func__);
                /* The VBUS interrupt is only triggered when VBUS appears. */
                spin_lock(&udc->lock);
@@ -1371,7 +1371,7 @@ static irqreturn_t isp1760_udc_irq(int irq, void *dev)
                spin_unlock(&udc->lock);
        }
 
-       if (status & DC_IEBRST) {
+       if (status & ISP176x_DC_IEBRST) {
                dev_dbg(udc->isp->dev, "%s(BRST)\n", __func__);
 
                isp1760_udc_reset(udc);
@@ -1391,18 +1391,18 @@ static irqreturn_t isp1760_udc_irq(int irq, void *dev)
                }
        }
 
-       if (status & DC_IEP0SETUP) {
+       if (status & ISP176x_DC_IEP0SETUP) {
                dev_dbg(udc->isp->dev, "%s(EP0SETUP)\n", __func__);
 
                isp1760_ep0_setup(udc);
        }
 
-       if (status & DC_IERESM) {
+       if (status & ISP176x_DC_IERESM) {
                dev_dbg(udc->isp->dev, "%s(RESM)\n", __func__);
                isp1760_udc_resume(udc);
        }
 
-       if (status & DC_IESUSP) {
+       if (status & ISP176x_DC_IESUSP) {
                dev_dbg(udc->isp->dev, "%s(SUSP)\n", __func__);
 
                spin_lock(&udc->lock);
@@ -1413,7 +1413,7 @@ static irqreturn_t isp1760_udc_irq(int irq, void *dev)
                spin_unlock(&udc->lock);
        }
 
-       if (status & DC_IEHS_STA) {
+       if (status & ISP176x_DC_IEHS_STA) {
                dev_dbg(udc->isp->dev, "%s(HS_STA)\n", __func__);
                udc->gadget.speed = USB_SPEED_HIGH;
        }
index 6d15a09..ed6a192 100644 (file)
@@ -394,13 +394,10 @@ static ssize_t adu_read(struct file *file, __user char *buffer, size_t count,
                        spin_lock_irqsave (&dev->buflock, flags);
                        if (dev->read_buffer_length) {
                                /* we secure access to the primary */
-                               char *tmp;
                                dev_dbg(&dev->udev->dev,
                                        "%s : swap, read_buffer_length = %d\n",
                                        __func__, dev->read_buffer_length);
-                               tmp = dev->read_buffer_secondary;
-                               dev->read_buffer_secondary = dev->read_buffer_primary;
-                               dev->read_buffer_primary = tmp;
+                               swap(dev->read_buffer_primary, dev->read_buffer_secondary);
                                dev->secondary_head = 0;
                                dev->secondary_tail = dev->read_buffer_length;
                                dev->read_buffer_length = 0;
index 3366530..2b2019c 100644 (file)
@@ -293,6 +293,8 @@ static int __init brcmstb_usb_pinmap_probe(struct platform_device *pdev)
 
                /* Enable interrupt for out pins */
                irq = platform_get_irq(pdev, 0);
+               if (irq < 0)
+                       return irq;
                err = devm_request_irq(&pdev->dev, irq,
                                       brcmstb_usb_pinmap_ovr_isr,
                                       IRQF_TRIGGER_RISING,
index 5546b86..022bbdc 100644 (file)
@@ -199,6 +199,7 @@ struct mtu3_gpd_ring {
 * @id_nb : notifier for iddig(idpin) detection
 * @dr_work : work for drd mode switch, used to avoid sleep in atomic context
 * @desired_role : role desired to switch
+* @default_role : default mode while usb role is USB_ROLE_NONE
 * @role_sw : use USB Role Switch to support dual-role switch, can't use
 *              extcon at the same time, and extcon is deprecated.
 * @role_sw_used : true when the USB Role Switch is used.
@@ -212,6 +213,7 @@ struct otg_switch_mtk {
        struct notifier_block id_nb;
        struct work_struct dr_work;
        enum usb_role desired_role;
+       enum usb_role default_role;
        struct usb_role_switch *role_sw;
        bool role_sw_used;
        bool is_u3_drd;
@@ -226,6 +228,9 @@ struct otg_switch_mtk {
  *             host only, device only or dual-role mode
  * @u2_ports: number of usb2.0 host ports
  * @u3_ports: number of usb3.0 host ports
+ * @u2p_dis_msk: mask of disabling usb2 ports, e.g. bit0==1 to
+ *             disable u2port0, bit1==1 to disable u2port1,... etc,
+ *             but when use dual-role mode, can't disable u2port0
  * @u3p_dis_msk: mask of disabling usb3 ports, for example, bit0==1 to
  *             disable u3port0, bit1==1 to disable u3port1,... etc
  * @dbgfs_root: only used when supports manual dual-role switch via debugfs
@@ -241,6 +246,7 @@ struct ssusb_mtk {
        void __iomem *ippc_base;
        struct phy **phys;
        int num_phys;
+       int wakeup_irq;
        /* common power & clock */
        struct regulator *vusb33;
        struct clk_bulk_data clks[BULK_CLKS_CNT];
@@ -250,6 +256,7 @@ struct ssusb_mtk {
        bool is_host;
        int u2_ports;
        int u3_ports;
+       int u2p_dis_msk;
        int u3p_dis_msk;
        struct dentry *dbgfs_root;
        /* usb wakeup for host mode */
@@ -349,6 +356,7 @@ struct mtu3 {
        unsigned is_u3_ip:1;
        unsigned delayed_status:1;
        unsigned gen2cp:1;
+       unsigned connected:1;
 
        u8 address;
        u8 test_mode_nr;
index 562f435..c4a2c37 100644 (file)
@@ -141,6 +141,28 @@ static void mtu3_device_disable(struct mtu3 *mtu)
        mtu3_setbits(ibase, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN);
 }
 
+static void mtu3_dev_power_on(struct mtu3 *mtu)
+{
+       void __iomem *ibase = mtu->ippc_base;
+
+       mtu3_clrbits(ibase, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN);
+       if (mtu->is_u3_ip)
+               mtu3_clrbits(ibase, SSUSB_U3_CTRL(0), SSUSB_U3_PORT_PDN);
+
+       mtu3_clrbits(ibase, SSUSB_U2_CTRL(0), SSUSB_U2_PORT_PDN);
+}
+
+static void mtu3_dev_power_down(struct mtu3 *mtu)
+{
+       void __iomem *ibase = mtu->ippc_base;
+
+       if (mtu->is_u3_ip)
+               mtu3_setbits(ibase, SSUSB_U3_CTRL(0), SSUSB_U3_PORT_PDN);
+
+       mtu3_setbits(ibase, SSUSB_U2_CTRL(0), SSUSB_U2_PORT_PDN);
+       mtu3_setbits(ibase, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN);
+}
+
 /* reset U3D's device module. */
 static void mtu3_device_reset(struct mtu3 *mtu)
 {
@@ -227,11 +249,13 @@ static void mtu3_set_speed(struct mtu3 *mtu, enum usb_device_speed speed)
                mtu3_setbits(mbase, U3D_POWER_MANAGEMENT, HS_ENABLE);
                break;
        case USB_SPEED_SUPER:
+               mtu3_setbits(mbase, U3D_POWER_MANAGEMENT, HS_ENABLE);
                mtu3_clrbits(mtu->ippc_base, SSUSB_U3_CTRL(0),
                             SSUSB_U3_PORT_SSP_SPEED);
                break;
        case USB_SPEED_SUPER_PLUS:
-                       mtu3_setbits(mtu->ippc_base, SSUSB_U3_CTRL(0),
+               mtu3_setbits(mbase, U3D_POWER_MANAGEMENT, HS_ENABLE);
+               mtu3_setbits(mtu->ippc_base, SSUSB_U3_CTRL(0),
                             SSUSB_U3_PORT_SSP_SPEED);
                break;
        default:
@@ -333,12 +357,7 @@ void mtu3_start(struct mtu3 *mtu)
        dev_dbg(mtu->dev, "%s devctl 0x%x\n", __func__,
                mtu3_readl(mbase, U3D_DEVICE_CONTROL));
 
-       mtu3_clrbits(mtu->ippc_base, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN);
-       if (mtu->is_u3_ip)
-               mtu3_clrbits(mtu->ippc_base, SSUSB_U3_CTRL(0), SSUSB_U3_PORT_PDN);
-
-       mtu3_clrbits(mtu->ippc_base, SSUSB_U2_CTRL(0), SSUSB_U2_PORT_PDN);
-
+       mtu3_dev_power_on(mtu);
        mtu3_csr_init(mtu);
        mtu3_set_speed(mtu, mtu->speed);
 
@@ -360,12 +379,25 @@ void mtu3_stop(struct mtu3 *mtu)
                mtu3_dev_on_off(mtu, 0);
 
        mtu->is_active = 0;
+       mtu3_dev_power_down(mtu);
+}
 
-       if (mtu->is_u3_ip)
-               mtu3_setbits(mtu->ippc_base, SSUSB_U3_CTRL(0), SSUSB_U3_PORT_PDN);
+static void mtu3_dev_suspend(struct mtu3 *mtu)
+{
+       if (!mtu->is_active)
+               return;
 
-       mtu3_setbits(mtu->ippc_base, SSUSB_U2_CTRL(0), SSUSB_U2_PORT_PDN);
-       mtu3_setbits(mtu->ippc_base, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN);
+       mtu3_intr_disable(mtu);
+       mtu3_dev_power_down(mtu);
+}
+
+static void mtu3_dev_resume(struct mtu3 *mtu)
+{
+       if (!mtu->is_active)
+               return;
+
+       mtu3_dev_power_on(mtu);
+       mtu3_intr_enable(mtu);
 }
 
 /* for non-ep0 */
@@ -688,11 +720,15 @@ static irqreturn_t mtu3_link_isr(struct mtu3 *mtu)
        mtu->g.speed = udev_speed;
        mtu->g.ep0->maxpacket = maxpkt;
        mtu->ep0_state = MU3D_EP0_STATE_SETUP;
+       mtu->connected = !!(udev_speed != USB_SPEED_UNKNOWN);
 
-       if (udev_speed == USB_SPEED_UNKNOWN)
+       if (udev_speed == USB_SPEED_UNKNOWN) {
                mtu3_gadget_disconnect(mtu);
-       else
+               pm_runtime_put(mtu->dev);
+       } else {
+               pm_runtime_get(mtu->dev);
                mtu3_ep0_setup(mtu);
+       }
 
        return IRQ_HANDLED;
 }
@@ -888,9 +924,16 @@ int ssusb_gadget_init(struct ssusb_mtk *ssusb)
        if (mtu == NULL)
                return -ENOMEM;
 
-       mtu->irq = platform_get_irq(pdev, 0);
-       if (mtu->irq < 0)
-               return mtu->irq;
+       mtu->irq = platform_get_irq_byname_optional(pdev, "device");
+       if (mtu->irq < 0) {
+               if (mtu->irq == -EPROBE_DEFER)
+                       return mtu->irq;
+
+               /* for backward compatibility */
+               mtu->irq = platform_get_irq(pdev, 0);
+               if (mtu->irq < 0)
+                       return mtu->irq;
+       }
        dev_info(dev, "irq %d\n", mtu->irq);
 
        mtu->mac_base = devm_platform_ioremap_resource_byname(pdev, "mac");
@@ -965,3 +1008,47 @@ void ssusb_gadget_exit(struct ssusb_mtk *ssusb)
        device_init_wakeup(ssusb->dev, false);
        mtu3_hw_exit(mtu);
 }
+
+bool ssusb_gadget_ip_sleep_check(struct ssusb_mtk *ssusb)
+{
+       struct mtu3 *mtu = ssusb->u3d;
+
+       /* host only, should wait for ip sleep */
+       if (!mtu)
+               return true;
+
+       /* device is started and pullup D+, ip can sleep */
+       if (mtu->is_active && mtu->softconnect)
+               return true;
+
+       /* ip can't sleep if not pullup D+ when support device mode */
+       return false;
+}
+
+int ssusb_gadget_suspend(struct ssusb_mtk *ssusb, pm_message_t msg)
+{
+       struct mtu3 *mtu = ssusb->u3d;
+
+       if (!mtu->gadget_driver)
+               return 0;
+
+       if (mtu->connected)
+               return -EBUSY;
+
+       mtu3_dev_suspend(mtu);
+       synchronize_irq(mtu->irq);
+
+       return 0;
+}
+
+int ssusb_gadget_resume(struct ssusb_mtk *ssusb, pm_message_t msg)
+{
+       struct mtu3 *mtu = ssusb->u3d;
+
+       if (!mtu->gadget_driver)
+               return 0;
+
+       mtu3_dev_resume(mtu);
+
+       return 0;
+}
index 318fbc6..a6b0483 100644 (file)
@@ -137,14 +137,19 @@ static void ssusb_mode_sw_work(struct work_struct *work)
 
        current_role = ssusb->is_host ? USB_ROLE_HOST : USB_ROLE_DEVICE;
 
-       if (desired_role == USB_ROLE_NONE)
+       if (desired_role == USB_ROLE_NONE) {
+               /* the default mode is host as probe does */
                desired_role = USB_ROLE_HOST;
+               if (otg_sx->default_role == USB_ROLE_DEVICE)
+                       desired_role = USB_ROLE_DEVICE;
+       }
 
        if (current_role == desired_role)
                return;
 
        dev_dbg(ssusb->dev, "set role : %s\n", usb_role_string(desired_role));
        mtu3_dbg_trace(ssusb->dev, "set role : %s", usb_role_string(desired_role));
+       pm_runtime_get_sync(ssusb->dev);
 
        switch (desired_role) {
        case USB_ROLE_HOST:
@@ -165,6 +170,7 @@ static void ssusb_mode_sw_work(struct work_struct *work)
        default:
                dev_err(ssusb->dev, "invalid role\n");
        }
+       pm_runtime_put(ssusb->dev);
 }
 
 static void ssusb_set_mode(struct otg_switch_mtk *otg_sx, enum usb_role role)
@@ -274,17 +280,29 @@ static int ssusb_role_sw_register(struct otg_switch_mtk *otg_sx)
 {
        struct usb_role_switch_desc role_sx_desc = { 0 };
        struct ssusb_mtk *ssusb = otg_sx_to_ssusb(otg_sx);
+       struct device *dev = ssusb->dev;
+       enum usb_dr_mode mode;
 
        if (!otg_sx->role_sw_used)
                return 0;
 
+       mode = usb_get_role_switch_default_mode(dev);
+       if (mode == USB_DR_MODE_PERIPHERAL)
+               otg_sx->default_role = USB_ROLE_DEVICE;
+       else
+               otg_sx->default_role = USB_ROLE_HOST;
+
        role_sx_desc.set = ssusb_role_sw_set;
        role_sx_desc.get = ssusb_role_sw_get;
-       role_sx_desc.fwnode = dev_fwnode(ssusb->dev);
+       role_sx_desc.fwnode = dev_fwnode(dev);
        role_sx_desc.driver_data = ssusb;
-       otg_sx->role_sw = usb_role_switch_register(ssusb->dev, &role_sx_desc);
+       otg_sx->role_sw = usb_role_switch_register(dev, &role_sx_desc);
+       if (IS_ERR(otg_sx->role_sw))
+               return PTR_ERR(otg_sx->role_sw);
 
-       return PTR_ERR_OR_ZERO(otg_sx->role_sw);
+       ssusb_set_mode(otg_sx, otg_sx->default_role);
+
+       return 0;
 }
 
 int ssusb_otg_switch_init(struct ssusb_mtk *ssusb)
index 760fe7d..e325508 100644 (file)
@@ -16,8 +16,8 @@ int ssusb_host_init(struct ssusb_mtk *ssusb, struct device_node *parent_dn);
 void ssusb_host_exit(struct ssusb_mtk *ssusb);
 int ssusb_wakeup_of_property_parse(struct ssusb_mtk *ssusb,
                                struct device_node *dn);
-int ssusb_host_enable(struct ssusb_mtk *ssusb);
-int ssusb_host_disable(struct ssusb_mtk *ssusb, bool suspend);
+int ssusb_host_resume(struct ssusb_mtk *ssusb, bool p0_skipped);
+int ssusb_host_suspend(struct ssusb_mtk *ssusb);
 void ssusb_wakeup_set(struct ssusb_mtk *ssusb, bool enable);
 
 #else
@@ -38,12 +38,12 @@ static inline int ssusb_wakeup_of_property_parse(
        return 0;
 }
 
-static inline int ssusb_host_enable(struct ssusb_mtk *ssusb)
+static inline int ssusb_host_resume(struct ssusb_mtk *ssusb, bool p0_skipped)
 {
        return 0;
 }
 
-static inline int ssusb_host_disable(struct ssusb_mtk *ssusb, bool suspend)
+static inline int ssusb_host_suspend(struct ssusb_mtk *ssusb)
 {
        return 0;
 }
@@ -57,6 +57,10 @@ static inline void ssusb_wakeup_set(struct ssusb_mtk *ssusb, bool enable)
 #if IS_ENABLED(CONFIG_USB_MTU3_GADGET) || IS_ENABLED(CONFIG_USB_MTU3_DUAL_ROLE)
 int ssusb_gadget_init(struct ssusb_mtk *ssusb);
 void ssusb_gadget_exit(struct ssusb_mtk *ssusb);
+int ssusb_gadget_suspend(struct ssusb_mtk *ssusb, pm_message_t msg);
+int ssusb_gadget_resume(struct ssusb_mtk *ssusb, pm_message_t msg);
+bool ssusb_gadget_ip_sleep_check(struct ssusb_mtk *ssusb);
+
 #else
 static inline int ssusb_gadget_init(struct ssusb_mtk *ssusb)
 {
@@ -65,6 +69,24 @@ static inline int ssusb_gadget_init(struct ssusb_mtk *ssusb)
 
 static inline void ssusb_gadget_exit(struct ssusb_mtk *ssusb)
 {}
+
+static inline int
+ssusb_gadget_suspend(struct ssusb_mtk *ssusb, pm_message_t msg)
+{
+       return 0;
+}
+
+static inline int
+ssusb_gadget_resume(struct ssusb_mtk *ssusb, pm_message_t msg)
+{
+       return 0;
+}
+
+static inline bool ssusb_gadget_ip_sleep_check(struct ssusb_mtk *ssusb)
+{
+       return true;
+}
+
 #endif
 
 
index 5e21ba0..a9a65b4 100644 (file)
@@ -64,14 +64,12 @@ static int mtu3_ep_enable(struct mtu3_ep *mep)
        u32 interval = 0;
        u32 mult = 0;
        u32 burst = 0;
-       int max_packet;
        int ret;
 
        desc = mep->desc;
        comp_desc = mep->comp_desc;
        mep->type = usb_endpoint_type(desc);
-       max_packet = usb_endpoint_maxp(desc);
-       mep->maxp = max_packet & GENMASK(10, 0);
+       mep->maxp = usb_endpoint_maxp(desc);
 
        switch (mtu->g.speed) {
        case USB_SPEED_SUPER:
@@ -92,7 +90,7 @@ static int mtu3_ep_enable(struct mtu3_ep *mep)
                                usb_endpoint_xfer_int(desc)) {
                        interval = desc->bInterval;
                        interval = clamp_val(interval, 1, 16) - 1;
-                       burst = (max_packet & GENMASK(12, 11)) >> 11;
+                       mult = usb_endpoint_maxp_mult(desc) - 1;
                }
                break;
        default:
@@ -469,6 +467,8 @@ static int mtu3_gadget_pullup(struct usb_gadget *gadget, int is_on)
        dev_dbg(mtu->dev, "%s (%s) for %sactive device\n", __func__,
                is_on ? "on" : "off", mtu->is_active ? "" : "in");
 
+       pm_runtime_get_sync(mtu->dev);
+
        /* we'd rather not pullup unless the device is active. */
        spin_lock_irqsave(&mtu->lock, flags);
 
@@ -482,6 +482,7 @@ static int mtu3_gadget_pullup(struct usb_gadget *gadget, int is_on)
        }
 
        spin_unlock_irqrestore(&mtu->lock, flags);
+       pm_runtime_put(mtu->dev);
 
        return 0;
 }
@@ -499,6 +500,7 @@ static int mtu3_gadget_start(struct usb_gadget *gadget,
        }
 
        dev_dbg(mtu->dev, "bind driver %s\n", driver->function);
+       pm_runtime_get_sync(mtu->dev);
 
        spin_lock_irqsave(&mtu->lock, flags);
 
@@ -509,6 +511,7 @@ static int mtu3_gadget_start(struct usb_gadget *gadget,
                mtu3_start(mtu);
 
        spin_unlock_irqrestore(&mtu->lock, flags);
+       pm_runtime_put(mtu->dev);
 
        return 0;
 }
index 93a1a4c..f390336 100644 (file)
@@ -8,7 +8,6 @@
  */
 
 #include <linux/clk.h>
-#include <linux/iopoll.h>
 #include <linux/irq.h>
 #include <linux/kernel.h>
 #include <linux/mfd/syscon.h>
@@ -63,7 +62,7 @@ static void ssusb_wakeup_ip_sleep_set(struct ssusb_mtk *ssusb, bool enable)
        case SSUSB_UWK_V1_1:
                reg = ssusb->uwk_reg_base + PERI_WK_CTRL0;
                msk = WC0_IS_EN | WC0_IS_C(0xf) | WC0_IS_P;
-               val = enable ? (WC0_IS_EN | WC0_IS_C(0x8)) : 0;
+               val = enable ? (WC0_IS_EN | WC0_IS_C(0x1)) : 0;
                break;
        case SSUSB_UWK_V1_2:
                reg = ssusb->uwk_reg_base + PERI_WK_CTRL0;
@@ -126,7 +125,7 @@ static void host_ports_num_get(struct ssusb_mtk *ssusb)
 }
 
 /* only configure ports will be used later */
-int ssusb_host_enable(struct ssusb_mtk *ssusb)
+static int ssusb_host_enable(struct ssusb_mtk *ssusb)
 {
        void __iomem *ibase = ssusb->ippc_base;
        int num_u3p = ssusb->u3_ports;
@@ -155,6 +154,9 @@ int ssusb_host_enable(struct ssusb_mtk *ssusb)
 
        /* power on and enable all u2 ports */
        for (i = 0; i < num_u2p; i++) {
+               if ((0x1 << i) & ssusb->u2p_dis_msk)
+                       continue;
+
                value = mtu3_readl(ibase, SSUSB_U2_CTRL(i));
                value &= ~(SSUSB_U2_PORT_PDN | SSUSB_U2_PORT_DIS);
                value |= SSUSB_U2_PORT_HOST_SEL;
@@ -168,13 +170,12 @@ int ssusb_host_enable(struct ssusb_mtk *ssusb)
        return ssusb_check_clocks(ssusb, check_clk);
 }
 
-int ssusb_host_disable(struct ssusb_mtk *ssusb, bool suspend)
+static int ssusb_host_disable(struct ssusb_mtk *ssusb)
 {
        void __iomem *ibase = ssusb->ippc_base;
        int num_u3p = ssusb->u3_ports;
        int num_u2p = ssusb->u2_ports;
        u32 value;
-       int ret;
        int i;
 
        /* power down and disable u3 ports except skipped ones */
@@ -183,32 +184,101 @@ int ssusb_host_disable(struct ssusb_mtk *ssusb, bool suspend)
                        continue;
 
                value = mtu3_readl(ibase, SSUSB_U3_CTRL(i));
-               value |= SSUSB_U3_PORT_PDN;
-               value |= suspend ? 0 : SSUSB_U3_PORT_DIS;
+               value |= SSUSB_U3_PORT_PDN | SSUSB_U3_PORT_DIS;
                mtu3_writel(ibase, SSUSB_U3_CTRL(i), value);
        }
 
-       /* power down and disable all u2 ports */
+       /* power down and disable u2 ports except skipped ones */
        for (i = 0; i < num_u2p; i++) {
+               if ((0x1 << i) & ssusb->u2p_dis_msk)
+                       continue;
+
                value = mtu3_readl(ibase, SSUSB_U2_CTRL(i));
-               value |= SSUSB_U2_PORT_PDN;
-               value |= suspend ? 0 : SSUSB_U2_PORT_DIS;
+               value |= SSUSB_U2_PORT_PDN | SSUSB_U2_PORT_DIS;
                mtu3_writel(ibase, SSUSB_U2_CTRL(i), value);
        }
 
        /* power down host ip */
        mtu3_setbits(ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN);
 
-       if (!suspend)
-               return 0;
+       return 0;
+}
 
-       /* wait for host ip to sleep */
-       ret = readl_poll_timeout(ibase + U3D_SSUSB_IP_PW_STS1, value,
-                         (value & SSUSB_IP_SLEEP_STS), 100, 100000);
-       if (ret)
-               dev_err(ssusb->dev, "ip sleep failed!!!\n");
+int ssusb_host_resume(struct ssusb_mtk *ssusb, bool p0_skipped)
+{
+       void __iomem *ibase = ssusb->ippc_base;
+       int u3p_skip_msk = ssusb->u3p_dis_msk;
+       int u2p_skip_msk = ssusb->u2p_dis_msk;
+       int num_u3p = ssusb->u3_ports;
+       int num_u2p = ssusb->u2_ports;
+       u32 value;
+       int i;
 
-       return ret;
+       if (p0_skipped) {
+               u2p_skip_msk |= 0x1;
+               if (ssusb->otg_switch.is_u3_drd)
+                       u3p_skip_msk |= 0x1;
+       }
+
+       /* power on host ip */
+       mtu3_clrbits(ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN);
+
+       /* power on u3 ports except skipped ones */
+       for (i = 0; i < num_u3p; i++) {
+               if ((0x1 << i) & u3p_skip_msk)
+                       continue;
+
+               value = mtu3_readl(ibase, SSUSB_U3_CTRL(i));
+               value &= ~SSUSB_U3_PORT_PDN;
+               mtu3_writel(ibase, SSUSB_U3_CTRL(i), value);
+       }
+
+       /* power on all u2 ports except skipped ones */
+       for (i = 0; i < num_u2p; i++) {
+               if ((0x1 << i) & u2p_skip_msk)
+                       continue;
+
+               value = mtu3_readl(ibase, SSUSB_U2_CTRL(i));
+               value &= ~SSUSB_U2_PORT_PDN;
+               mtu3_writel(ibase, SSUSB_U2_CTRL(i), value);
+       }
+
+       return 0;
+}
+
+/* here not skip port0 due to PDN can be set repeatedly */
+int ssusb_host_suspend(struct ssusb_mtk *ssusb)
+{
+       void __iomem *ibase = ssusb->ippc_base;
+       int num_u3p = ssusb->u3_ports;
+       int num_u2p = ssusb->u2_ports;
+       u32 value;
+       int i;
+
+       /* power down u3 ports except skipped ones */
+       for (i = 0; i < num_u3p; i++) {
+               if ((0x1 << i) & ssusb->u3p_dis_msk)
+                       continue;
+
+               value = mtu3_readl(ibase, SSUSB_U3_CTRL(i));
+               value |= SSUSB_U3_PORT_PDN;
+               mtu3_writel(ibase, SSUSB_U3_CTRL(i), value);
+       }
+
+       /* power down u2 ports except skipped ones */
+       for (i = 0; i < num_u2p; i++) {
+               if ((0x1 << i) & ssusb->u2p_dis_msk)
+                       continue;
+
+               value = mtu3_readl(ibase, SSUSB_U2_CTRL(i));
+               value |= SSUSB_U2_PORT_PDN;
+               mtu3_writel(ibase, SSUSB_U2_CTRL(i), value);
+       }
+
+       /* power down host ip */
+       mtu3_setbits(ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN);
+
+       return 0;
 }
 
 static void ssusb_host_setup(struct ssusb_mtk *ssusb)
@@ -231,7 +301,7 @@ static void ssusb_host_cleanup(struct ssusb_mtk *ssusb)
        if (ssusb->is_host)
                ssusb_set_vbus(&ssusb->otg_switch, 0);
 
-       ssusb_host_disable(ssusb, false);
+       ssusb_host_disable(ssusb);
 }
 
 /*
index c0615f6..f135310 100644 (file)
@@ -12,6 +12,7 @@
 #include <linux/of_address.h>
 #include <linux/of_irq.h>
 #include <linux/platform_device.h>
+#include <linux/pm_wakeirq.h>
 
 #include "mtu3.h"
 #include "mtu3_dr.h"
@@ -44,6 +45,32 @@ int ssusb_check_clocks(struct ssusb_mtk *ssusb, u32 ex_clks)
        return 0;
 }
 
+static int wait_for_ip_sleep(struct ssusb_mtk *ssusb)
+{
+       bool sleep_check = true;
+       u32 value;
+       int ret;
+
+       if (!ssusb->is_host)
+               sleep_check = ssusb_gadget_ip_sleep_check(ssusb);
+
+       if (!sleep_check)
+               return 0;
+
+       /* wait for ip enter sleep mode */
+       ret = readl_poll_timeout(ssusb->ippc_base + U3D_SSUSB_IP_PW_STS1, value,
+                                (value & SSUSB_IP_SLEEP_STS), 100, 100000);
+       if (ret) {
+               dev_err(ssusb->dev, "ip sleep failed!!!\n");
+               ret = -EBUSY;
+       } else {
+               /* workaround: avoid wrong wakeup signal latch for some soc */
+               usleep_range(100, 200);
+       }
+
+       return ret;
+}
+
 static int ssusb_phy_init(struct ssusb_mtk *ssusb)
 {
        int i;
@@ -208,6 +235,10 @@ static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb)
        if (IS_ERR(ssusb->ippc_base))
                return PTR_ERR(ssusb->ippc_base);
 
+       ssusb->wakeup_irq = platform_get_irq_byname_optional(pdev, "wakeup");
+       if (ssusb->wakeup_irq == -EPROBE_DEFER)
+               return ssusb->wakeup_irq;
+
        ssusb->dr_mode = usb_get_dr_mode(dev);
        if (ssusb->dr_mode == USB_DR_MODE_UNKNOWN)
                ssusb->dr_mode = USB_DR_MODE_OTG;
@@ -225,6 +256,8 @@ static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb)
        /* optional property, ignore the error if it does not exist */
        of_property_read_u32(node, "mediatek,u3p-dis-msk",
                             &ssusb->u3p_dis_msk);
+       of_property_read_u32(node, "mediatek,u2p-dis-msk",
+                            &ssusb->u2p_dis_msk);
 
        otg_sx->vbus = devm_regulator_get(dev, "vbus");
        if (IS_ERR(otg_sx->vbus)) {
@@ -241,6 +274,9 @@ static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb)
                of_property_read_bool(node, "enable-manual-drd");
        otg_sx->role_sw_used = of_property_read_bool(node, "usb-role-switch");
 
+       /* can't disable port0 when use dual-role mode */
+       ssusb->u2p_dis_msk &= ~0x1;
+
        if (otg_sx->role_sw_used || otg_sx->manual_drd_enabled)
                goto out;
 
@@ -253,9 +289,11 @@ static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb)
        }
 
 out:
-       dev_info(dev, "dr_mode: %d, is_u3_dr: %d, u3p_dis_msk: %x, drd: %s\n",
-               ssusb->dr_mode, otg_sx->is_u3_drd, ssusb->u3p_dis_msk,
+       dev_info(dev, "dr_mode: %d, is_u3_dr: %d, drd: %s\n",
+                ssusb->dr_mode, otg_sx->is_u3_drd,
                otg_sx->manual_drd_enabled ? "manual" : "auto");
+       dev_info(dev, "u2p_dis_msk: %x, u3p_dis_msk: %x\n",
+                ssusb->u2p_dis_msk, ssusb->u3p_dis_msk);
 
        return 0;
 }
@@ -288,14 +326,25 @@ static int mtu3_probe(struct platform_device *pdev)
        ssusb_debugfs_create_root(ssusb);
 
        /* enable power domain */
+       pm_runtime_set_active(dev);
+       pm_runtime_use_autosuspend(dev);
+       pm_runtime_set_autosuspend_delay(dev, 4000);
        pm_runtime_enable(dev);
        pm_runtime_get_sync(dev);
-       device_enable_async_suspend(dev);
 
        ret = ssusb_rscs_init(ssusb);
        if (ret)
                goto comm_init_err;
 
+       if (ssusb->wakeup_irq > 0) {
+               ret = dev_pm_set_dedicated_wake_irq(dev, ssusb->wakeup_irq);
+               if (ret) {
+                       dev_err(dev, "failed to set wakeup irq %d\n", ssusb->wakeup_irq);
+                       goto comm_exit;
+               }
+               dev_info(dev, "wakeup irq %d\n", ssusb->wakeup_irq);
+       }
+
        ssusb_ip_sw_reset(ssusb);
 
        if (IS_ENABLED(CONFIG_USB_MTU3_HOST))
@@ -346,6 +395,11 @@ static int mtu3_probe(struct platform_device *pdev)
                goto comm_exit;
        }
 
+       device_enable_async_suspend(dev);
+       pm_runtime_mark_last_busy(dev);
+       pm_runtime_put_autosuspend(dev);
+       pm_runtime_forbid(dev);
+
        return 0;
 
 host_exit:
@@ -355,7 +409,7 @@ gadget_exit:
 comm_exit:
        ssusb_rscs_exit(ssusb);
 comm_init_err:
-       pm_runtime_put_sync(dev);
+       pm_runtime_put_noidle(dev);
        pm_runtime_disable(dev);
        ssusb_debugfs_remove_root(ssusb);
 
@@ -366,6 +420,8 @@ static int mtu3_remove(struct platform_device *pdev)
 {
        struct ssusb_mtk *ssusb = platform_get_drvdata(pdev);
 
+       pm_runtime_get_sync(&pdev->dev);
+
        switch (ssusb->dr_mode) {
        case USB_DR_MODE_PERIPHERAL:
                ssusb_gadget_exit(ssusb);
@@ -383,45 +439,87 @@ static int mtu3_remove(struct platform_device *pdev)
        }
 
        ssusb_rscs_exit(ssusb);
-       pm_runtime_put_sync(&pdev->dev);
-       pm_runtime_disable(&pdev->dev);
        ssusb_debugfs_remove_root(ssusb);
+       pm_runtime_disable(&pdev->dev);
+       pm_runtime_put_noidle(&pdev->dev);
+       pm_runtime_set_suspended(&pdev->dev);
 
        return 0;
 }
 
-/*
- * when support dual-role mode, we reject suspend when
- * it works as device mode;
- */
-static int __maybe_unused mtu3_suspend(struct device *dev)
+static int resume_ip_and_ports(struct ssusb_mtk *ssusb, pm_message_t msg)
+{
+       switch (ssusb->dr_mode) {
+       case USB_DR_MODE_PERIPHERAL:
+               ssusb_gadget_resume(ssusb, msg);
+               break;
+       case USB_DR_MODE_HOST:
+               ssusb_host_resume(ssusb, false);
+               break;
+       case USB_DR_MODE_OTG:
+               ssusb_host_resume(ssusb, !ssusb->is_host);
+               if (!ssusb->is_host)
+                       ssusb_gadget_resume(ssusb, msg);
+
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int mtu3_suspend_common(struct device *dev, pm_message_t msg)
 {
        struct ssusb_mtk *ssusb = dev_get_drvdata(dev);
+       int ret = 0;
 
        dev_dbg(dev, "%s\n", __func__);
 
-       /* REVISIT: disconnect it for only device mode? */
-       if (!ssusb->is_host)
-               return 0;
+       switch (ssusb->dr_mode) {
+       case USB_DR_MODE_PERIPHERAL:
+               ret = ssusb_gadget_suspend(ssusb, msg);
+               if (ret)
+                       goto err;
+
+               break;
+       case USB_DR_MODE_HOST:
+               ssusb_host_suspend(ssusb);
+               break;
+       case USB_DR_MODE_OTG:
+               if (!ssusb->is_host) {
+                       ret = ssusb_gadget_suspend(ssusb, msg);
+                       if (ret)
+                               goto err;
+               }
+               ssusb_host_suspend(ssusb);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       ret = wait_for_ip_sleep(ssusb);
+       if (ret)
+               goto sleep_err;
 
-       ssusb_host_disable(ssusb, true);
        ssusb_phy_power_off(ssusb);
        clk_bulk_disable_unprepare(BULK_CLKS_CNT, ssusb->clks);
        ssusb_wakeup_set(ssusb, true);
-
        return 0;
+
+sleep_err:
+       resume_ip_and_ports(ssusb, msg);
+err:
+       return ret;
 }
 
-static int __maybe_unused mtu3_resume(struct device *dev)
+static int mtu3_resume_common(struct device *dev, pm_message_t msg)
 {
        struct ssusb_mtk *ssusb = dev_get_drvdata(dev);
        int ret;
 
        dev_dbg(dev, "%s\n", __func__);
 
-       if (!ssusb->is_host)
-               return 0;
-
        ssusb_wakeup_set(ssusb, false);
        ret = clk_bulk_prepare_enable(BULK_CLKS_CNT, ssusb->clks);
        if (ret)
@@ -431,9 +529,7 @@ static int __maybe_unused mtu3_resume(struct device *dev)
        if (ret)
                goto phy_err;
 
-       ssusb_host_enable(ssusb);
-
-       return 0;
+       return resume_ip_and_ports(ssusb, msg);
 
 phy_err:
        clk_bulk_disable_unprepare(BULK_CLKS_CNT, ssusb->clks);
@@ -441,8 +537,36 @@ clks_err:
        return ret;
 }
 
+static int __maybe_unused mtu3_suspend(struct device *dev)
+{
+       return mtu3_suspend_common(dev, PMSG_SUSPEND);
+}
+
+static int __maybe_unused mtu3_resume(struct device *dev)
+{
+       return mtu3_resume_common(dev, PMSG_SUSPEND);
+}
+
+static int __maybe_unused mtu3_runtime_suspend(struct device *dev)
+{
+       if (!device_may_wakeup(dev))
+               return 0;
+
+       return mtu3_suspend_common(dev, PMSG_AUTO_SUSPEND);
+}
+
+static int __maybe_unused mtu3_runtime_resume(struct device *dev)
+{
+       if (!device_may_wakeup(dev))
+               return 0;
+
+       return mtu3_resume_common(dev, PMSG_AUTO_SUSPEND);
+}
+
 static const struct dev_pm_ops mtu3_pm_ops = {
        SET_SYSTEM_SLEEP_PM_OPS(mtu3_suspend, mtu3_resume)
+       SET_RUNTIME_PM_OPS(mtu3_runtime_suspend,
+                          mtu3_runtime_resume, NULL)
 };
 
 #define DEV_PM_OPS (IS_ENABLED(CONFIG_PM) ? &mtu3_pm_ops : NULL)
index 5892f3c..ce9fc46 100644 (file)
@@ -890,23 +890,22 @@ static int dsps_probe(struct platform_device *pdev)
        if (!glue->usbss_base)
                return -ENXIO;
 
-       if (usb_get_dr_mode(&pdev->dev) == USB_DR_MODE_PERIPHERAL) {
-               ret = dsps_setup_optional_vbus_irq(pdev, glue);
-               if (ret)
-                       goto err_iounmap;
-       }
-
        platform_set_drvdata(pdev, glue);
        pm_runtime_enable(&pdev->dev);
        ret = dsps_create_musb_pdev(glue, pdev);
        if (ret)
                goto err;
 
+       if (usb_get_dr_mode(&pdev->dev) == USB_DR_MODE_PERIPHERAL) {
+               ret = dsps_setup_optional_vbus_irq(pdev, glue);
+               if (ret)
+                       goto err;
+       }
+
        return 0;
 
 err:
        pm_runtime_disable(&pdev->dev);
-err_iounmap:
        iounmap(glue->usbss_base);
        return ret;
 }
index f34c943..9727042 100644 (file)
@@ -873,6 +873,8 @@ int usb_otg_start(struct platform_device *pdev)
 
        /* request irq */
        p_otg->irq = platform_get_irq(pdev, 0);
+       if (p_otg->irq < 0)
+               return p_otg->irq;
        status = request_irq(p_otg->irq, fsl_otg_isr,
                                IRQF_SHARED, driver_name, p_otg);
        if (status) {
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 baebb1f..a3e043e 100644 (file)
@@ -393,7 +393,9 @@ static int tahvo_usb_probe(struct platform_device *pdev)
 
        dev_set_drvdata(&pdev->dev, tu);
 
-       tu->irq = platform_get_irq(pdev, 0);
+       tu->irq = ret = platform_get_irq(pdev, 0);
+       if (ret < 0)
+               return ret;
        ret = request_threaded_irq(tu->irq, NULL, tahvo_usb_vbus_interrupt,
                                   IRQF_ONESHOT,
                                   "tahvo-vbus", tu);
index 8ba6c5a..ab3c38a 100644 (file)
@@ -348,6 +348,11 @@ static int twl6030_usb_probe(struct platform_device *pdev)
        twl->irq2               = platform_get_irq(pdev, 1);
        twl->linkstat           = MUSB_UNKNOWN;
 
+       if (twl->irq1 < 0)
+               return twl->irq1;
+       if (twl->irq2 < 0)
+               return twl->irq2;
+
        twl->comparator.set_vbus        = twl6030_set_vbus;
        twl->comparator.start_srp       = twl6030_start_srp;
 
index a3c2b01..10607e2 100644 (file)
@@ -366,7 +366,7 @@ static int usbhs_dcp_dir_switch_to_write(struct usbhs_pkt *pkt, int *is_done)
 
        ret = usbhsf_fifo_select(pipe, fifo, 1);
        if (ret < 0) {
-               dev_err(dev, "%s() faile\n", __func__);
+               dev_err(dev, "%s() failed\n", __func__);
                return ret;
        }
 
index 5d05de6..a4d3720 100644 (file)
@@ -278,7 +278,7 @@ struct pd_mode_data {
  * @req_max_curr: Requested max current of the port partner
  * @req_out_volt: Requested output voltage to the port partner
  * @req_op_curr: Requested operating current to the port partner
- * @supported: Parter has atleast one APDO hence supports PPS
+ * @supported: Parter has at least one APDO hence supports PPS
  * @active: PPS mode is active
  */
 struct pd_pps_data {
@@ -316,6 +316,7 @@ struct tcpm_port {
        struct typec_partner *partner;
 
        enum typec_cc_status cc_req;
+       enum typec_cc_status src_rp;    /* work only if pd_supported == false */
 
        enum typec_cc_status cc1;
        enum typec_cc_status cc2;
@@ -323,6 +324,7 @@ struct tcpm_port {
 
        bool attached;
        bool connected;
+       bool pd_supported;
        enum typec_port_type port_type;
 
        /*
@@ -817,6 +819,9 @@ static enum typec_cc_status tcpm_rp_cc(struct tcpm_port *port)
        int nr_pdo = port->nr_src_pdo;
        int i;
 
+       if (!port->pd_supported)
+               return port->src_rp;
+
        /*
         * Search for first entry with matching voltage.
         * It should report the maximum supported current.
@@ -2063,7 +2068,7 @@ enum pdo_err {
 
 static const char * const pdo_err_msg[] = {
        [PDO_ERR_NO_VSAFE5V] =
-       " err: source/sink caps should atleast have vSafe5V",
+       " err: source/sink caps should at least have vSafe5V",
        [PDO_ERR_VSAFE5V_NOT_FIRST] =
        " err: vSafe5V Fixed Supply Object Shall always be the first object",
        [PDO_ERR_PDO_TYPE_NOT_IN_ORDER] =
@@ -3571,9 +3576,11 @@ static int tcpm_src_attach(struct tcpm_port *port)
        if (ret < 0)
                return ret;
 
-       ret = port->tcpc->set_pd_rx(port->tcpc, true);
-       if (ret < 0)
-               goto out_disable_mux;
+       if (port->pd_supported) {
+               ret = port->tcpc->set_pd_rx(port->tcpc, true);
+               if (ret < 0)
+                       goto out_disable_mux;
+       }
 
        /*
         * USB Type-C specification, version 1.2,
@@ -3603,7 +3610,8 @@ static int tcpm_src_attach(struct tcpm_port *port)
 out_disable_vconn:
        tcpm_set_vconn(port, false);
 out_disable_pd:
-       port->tcpc->set_pd_rx(port->tcpc, false);
+       if (port->pd_supported)
+               port->tcpc->set_pd_rx(port->tcpc, false);
 out_disable_mux:
        tcpm_mux_set(port, TYPEC_STATE_SAFE, USB_ROLE_NONE,
                     TYPEC_ORIENTATION_NONE);
@@ -3807,6 +3815,20 @@ static enum typec_pwr_opmode tcpm_get_pwr_opmode(enum typec_cc_status cc)
        }
 }
 
+static enum typec_cc_status tcpm_pwr_opmode_to_rp(enum typec_pwr_opmode opmode)
+{
+       switch (opmode) {
+       case TYPEC_PWR_MODE_USB:
+               return TYPEC_CC_RP_DEF;
+       case TYPEC_PWR_MODE_1_5A:
+               return TYPEC_CC_RP_1_5;
+       case TYPEC_PWR_MODE_3_0A:
+       case TYPEC_PWR_MODE_PD:
+       default:
+               return TYPEC_CC_RP_3_0;
+       }
+}
+
 static void run_state_machine(struct tcpm_port *port)
 {
        int ret;
@@ -3917,6 +3939,10 @@ static void run_state_machine(struct tcpm_port *port)
                if (port->ams == POWER_ROLE_SWAP ||
                    port->ams == FAST_ROLE_SWAP)
                        tcpm_ams_finish(port);
+               if (!port->pd_supported) {
+                       tcpm_set_state(port, SRC_READY, 0);
+                       break;
+               }
                port->upcoming_state = SRC_SEND_CAPABILITIES;
                tcpm_ams_start(port, POWER_NEGOTIATION);
                break;
@@ -4164,7 +4190,10 @@ static void run_state_machine(struct tcpm_port *port)
                                current_lim = PD_P_SNK_STDBY_MW / 5;
                        tcpm_set_current_limit(port, current_lim, 5000);
                        tcpm_set_charge(port, true);
-                       tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0);
+                       if (!port->pd_supported)
+                               tcpm_set_state(port, SNK_READY, 0);
+                       else
+                               tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0);
                        break;
                }
                /*
@@ -4392,7 +4421,8 @@ static void run_state_machine(struct tcpm_port *port)
                tcpm_set_vbus(port, true);
                if (port->ams == HARD_RESET)
                        tcpm_ams_finish(port);
-               port->tcpc->set_pd_rx(port->tcpc, true);
+               if (port->pd_supported)
+                       port->tcpc->set_pd_rx(port->tcpc, true);
                tcpm_set_attached_state(port, true);
                tcpm_set_state(port, SRC_UNATTACHED, PD_T_PS_SOURCE_ON);
                break;
@@ -5894,6 +5924,7 @@ EXPORT_SYMBOL_GPL(tcpm_tcpc_reset);
 static int tcpm_fw_get_caps(struct tcpm_port *port,
                            struct fwnode_handle *fwnode)
 {
+       const char *opmode_str;
        const char *cap_str;
        int ret;
        u32 mw, frs_current;
@@ -5928,22 +5959,37 @@ static int tcpm_fw_get_caps(struct tcpm_port *port,
                return ret;
        port->typec_caps.type = ret;
        port->port_type = port->typec_caps.type;
+       port->pd_supported = !fwnode_property_read_bool(fwnode, "pd-disable");
 
        port->slow_charger_loop = fwnode_property_read_bool(fwnode, "slow-charger-loop");
        if (port->port_type == TYPEC_PORT_SNK)
                goto sink;
 
-       /* Get source pdos */
-       ret = fwnode_property_count_u32(fwnode, "source-pdos");
-       if (ret <= 0)
-               return -EINVAL;
+       /* Get Source PDOs for the PD port or Source Rp value for the non-PD port */
+       if (port->pd_supported) {
+               ret = fwnode_property_count_u32(fwnode, "source-pdos");
+               if (ret == 0)
+                       return -EINVAL;
+               else if (ret < 0)
+                       return ret;
 
-       port->nr_src_pdo = min(ret, PDO_MAX_OBJECTS);
-       ret = fwnode_property_read_u32_array(fwnode, "source-pdos",
-                                            port->src_pdo, port->nr_src_pdo);
-       if ((ret < 0) || tcpm_validate_caps(port, port->src_pdo,
-                                           port->nr_src_pdo))
-               return -EINVAL;
+               port->nr_src_pdo = min(ret, PDO_MAX_OBJECTS);
+               ret = fwnode_property_read_u32_array(fwnode, "source-pdos",
+                                                    port->src_pdo, port->nr_src_pdo);
+               if (ret)
+                       return ret;
+               ret = tcpm_validate_caps(port, port->src_pdo, port->nr_src_pdo);
+               if (ret)
+                       return ret;
+       } else {
+               ret = fwnode_property_read_string(fwnode, "typec-power-opmode", &opmode_str);
+               if (ret)
+                       return ret;
+               ret = typec_find_pwr_opmode(opmode_str);
+               if (ret < 0)
+                       return ret;
+               port->src_rp = tcpm_pwr_opmode_to_rp(ret);
+       }
 
        if (port->port_type == TYPEC_PORT_SRC)
                return 0;
@@ -5957,6 +6003,11 @@ static int tcpm_fw_get_caps(struct tcpm_port *port,
        if (port->typec_caps.prefer_role < 0)
                return -EINVAL;
 sink:
+       port->self_powered = fwnode_property_read_bool(fwnode, "self-powered");
+
+       if (!port->pd_supported)
+               return 0;
+
        /* Get sink pdos */
        ret = fwnode_property_count_u32(fwnode, "sink-pdos");
        if (ret <= 0)
@@ -5973,9 +6024,7 @@ sink:
                return -EINVAL;
        port->operating_snk_mw = mw / 1000;
 
-       port->self_powered = fwnode_property_read_bool(fwnode, "self-powered");
-
-       /* FRS can only be supported byb DRP ports */
+       /* FRS can only be supported by DRP ports */
        if (port->port_type == TYPEC_PORT_DRP) {
                ret = fwnode_property_read_u32(fwnode, "new-source-frs-typec-current",
                                               &frs_current);
index 4ba6bcd..2332655 100644 (file)
@@ -455,8 +455,14 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
                        vhci_hcd->port_status[rhport] &= ~(1 << USB_PORT_FEAT_RESET);
                        vhci_hcd->re_timeout = 0;
 
+                       /*
+                        * A few drivers do usb reset during probe when
+                        * the device could be in VDEV_ST_USED state
+                        */
                        if (vhci_hcd->vdev[rhport].ud.status ==
-                           VDEV_ST_NOTASSIGNED) {
+                               VDEV_ST_NOTASSIGNED ||
+                           vhci_hcd->vdev[rhport].ud.status ==
+                               VDEV_ST_USED) {
                                usbip_dbg_vhci_rh(
                                        " enable rhport %d (status %u)\n",
                                        rhport,
@@ -945,7 +951,8 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
        return 0;
 }
 
-static void vhci_device_unlink_cleanup(struct vhci_device *vdev)
+static void vhci_cleanup_unlink_list(struct vhci_device *vdev,
+               struct list_head *unlink_list)
 {
        struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev);
        struct usb_hcd *hcd = vhci_hcd_to_hcd(vhci_hcd);
@@ -956,25 +963,11 @@ static void vhci_device_unlink_cleanup(struct vhci_device *vdev)
        spin_lock_irqsave(&vhci->lock, flags);
        spin_lock(&vdev->priv_lock);
 
-       list_for_each_entry_safe(unlink, tmp, &vdev->unlink_tx, list) {
-               pr_info("unlink cleanup tx %lu\n", unlink->unlink_seqnum);
-               list_del(&unlink->list);
-               kfree(unlink);
-       }
-
-       while (!list_empty(&vdev->unlink_rx)) {
+       list_for_each_entry_safe(unlink, tmp, unlink_list, list) {
                struct urb *urb;
 
-               unlink = list_first_entry(&vdev->unlink_rx, struct vhci_unlink,
-                       list);
-
-               /* give back URB of unanswered unlink request */
-               pr_info("unlink cleanup rx %lu\n", unlink->unlink_seqnum);
-
                urb = pickup_urb_and_free_priv(vdev, unlink->unlink_seqnum);
                if (!urb) {
-                       pr_info("the urb (seqnum %lu) was already given back\n",
-                               unlink->unlink_seqnum);
                        list_del(&unlink->list);
                        kfree(unlink);
                        continue;
@@ -1001,6 +994,15 @@ static void vhci_device_unlink_cleanup(struct vhci_device *vdev)
        spin_unlock_irqrestore(&vhci->lock, flags);
 }
 
+static void vhci_device_unlink_cleanup(struct vhci_device *vdev)
+{
+       /* give back URB of unsent unlink request */
+       vhci_cleanup_unlink_list(vdev, &vdev->unlink_tx);
+
+       /* give back URB of unanswered unlink request */
+       vhci_cleanup_unlink_list(vdev, &vdev->unlink_rx);
+}
+
 /*
  * The important thing is that only one context begins cleanup.
  * This is why error handling and cleanup become simple.
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 e7c96c3..124e13c 100644 (file)
@@ -468,6 +468,7 @@ static inline struct tb_xdomain *tb_service_parent(struct tb_service *svc)
  * @interrupt_work: Work scheduled to handle ring interrupt when no
  *                 MSI-X is used.
  * @hop_count: Number of rings (end point hops) supported by NHI.
+ * @quirks: NHI specific quirks if any
  */
 struct tb_nhi {
        spinlock_t lock;
@@ -480,6 +481,7 @@ struct tb_nhi {
        bool going_away;
        struct work_struct interrupt_work;
        u32 hop_count;
+       unsigned long quirks;
 };
 
 /**
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 78e0063..c892c5b 100644 (file)
@@ -45,6 +45,7 @@ struct ehci_caps {
 #define HCS_PORTROUTED(p)      ((p)&(1 << 7))  /* true: port routing */
 #define HCS_PPC(p)             ((p)&(1 << 4))  /* true: port power control */
 #define HCS_N_PORTS(p)         (((p)>>0)&0xf)  /* bits 3:0, ports on HC */
+#define HCS_N_PORTS_MAX                15              /* N_PORTS valid 0x1-0xF */
 
        u32             hcc_params;      /* HCCPARAMS - offset 0x8 */
 /* EHCI 1.1 addendum */
@@ -126,8 +127,9 @@ struct ehci_regs {
        u32             configured_flag;
 #define FLAG_CF                (1<<0)          /* true: we'll support "high speed" */
 
-       /* PORTSC: offset 0x44 */
-       u32             port_status[0]; /* up to N_PORTS */
+       union {
+               /* PORTSC: offset 0x44 */
+               u32     port_status[HCS_N_PORTS_MAX];   /* up to N_PORTS */
 /* EHCI 1.1 addendum */
 #define PORTSC_SUSPEND_STS_ACK 0
 #define PORTSC_SUSPEND_STS_NYET 1
@@ -164,28 +166,35 @@ struct ehci_regs {
 #define PORT_CSC       (1<<1)          /* connect status change */
 #define PORT_CONNECT   (1<<0)          /* device connected */
 #define PORT_RWC_BITS   (PORT_CSC | PORT_PEC | PORT_OCC)
-
-       u32             reserved3[9];
-
-       /* USBMODE: offset 0x68 */
-       u32             usbmode;        /* USB Device mode */
+               struct {
+                       u32     reserved3[9];
+                       /* USBMODE: offset 0x68 */
+                       u32     usbmode;        /* USB Device mode */
+               };
 #define USBMODE_SDIS   (1<<3)          /* Stream disable */
 #define USBMODE_BE     (1<<2)          /* BE/LE endianness select */
 #define USBMODE_CM_HC  (3<<0)          /* host controller mode */
 #define USBMODE_CM_IDLE        (0<<0)          /* idle state */
-
-       u32             reserved4[6];
+       };
 
 /* Moorestown has some non-standard registers, partially due to the fact that
  * its EHCI controller has both TT and LPM support. HOSTPCx are extensions to
  * PORTSCx
  */
-       /* HOSTPC: offset 0x84 */
-       u32             hostpc[0];      /* HOSTPC extension */
+       union {
+               struct {
+                       u32     reserved4;
+                       /* HOSTPC: offset 0x84 */
+                       u32     hostpc[HCS_N_PORTS_MAX];
 #define HOSTPC_PHCD    (1<<22)         /* Phy clock disable */
 #define HOSTPC_PSPD    (3<<25)         /* Port speed detection */
+               };
+
+               /* Broadcom-proprietary USB_EHCI_INSNREG00 @ 0x80 */
+               u32     brcm_insnreg[4];
+       };
 
-       u32             reserved5[17];
+       u32             reserved5[2];
 
        /* USBMODE_EX: offset 0xc8 */
        u32             usbmode_ex;     /* USB Device mode extension */
index 75c7538..10fe57c 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);
 };
 
 /**
@@ -491,7 +492,7 @@ extern char *usb_get_gadget_udc_name(void);
  */
 static inline size_t usb_ep_align(struct usb_ep *ep, size_t len)
 {
-       int max_packet_size = (size_t)usb_endpoint_maxp(ep->desc) & 0x7ff;
+       int max_packet_size = (size_t)usb_endpoint_maxp(ep->desc);
 
        return round_up(len, max_packet_size);
 }
@@ -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 */
 
 /*-------------------------------------------------------------------------*/
index 7ceeecb..6475f88 100644 (file)
@@ -128,5 +128,6 @@ enum usb_dr_mode {
  * and returns the corresponding enum usb_dr_mode
  */
 extern enum usb_dr_mode usb_get_dr_mode(struct device *dev);
+extern enum usb_dr_mode usb_get_role_switch_default_mode(struct device *dev);
 
 #endif /* __LINUX_USB_OTG_H */