Merge 5.17-rc6 into usb-next
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 28 Feb 2022 20:42:36 +0000 (21:42 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 28 Feb 2022 20:42:36 +0000 (21:42 +0100)
We need the USB fixes in here, and it resolves a merge conflict in:
drivers/usb/dwc3/dwc3-pci.c

Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
75 files changed:
Documentation/ABI/testing/configfs-usb-gadget-uac1
Documentation/ABI/testing/configfs-usb-gadget-uac2
Documentation/ABI/testing/sysfs-driver-eud [new file with mode: 0644]
Documentation/devicetree/bindings/soc/qcom/qcom,eud.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/usb/dwc2.yaml
Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml
Documentation/devicetree/bindings/usb/microchip,mpfs-musb.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/usb/qcom,dwc3.yaml
Documentation/devicetree/bindings/usb/richtek,rt1719.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/usb/snps,dwc3.yaml
Documentation/devicetree/bindings/usb/willsemi,wusb3801.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/vendor-prefixes.yaml
Documentation/usb/gadget-testing.rst
MAINTAINERS
arch/arm64/boot/dts/intel/socfpga_agilex.dtsi
arch/arm64/boot/dts/qcom/ipq6018.dtsi
arch/arm64/boot/dts/qcom/sc7280-idp.dts
arch/arm64/boot/dts/qcom/sc7280.dtsi
arch/arm64/boot/dts/xilinx/zynqmp-clk-ccf.dtsi
arch/arm64/boot/dts/xilinx/zynqmp.dtsi
drivers/usb/cdns3/cdnsp-debug.h
drivers/usb/cdns3/cdnsp-gadget.c
drivers/usb/chipidea/udc.c
drivers/usb/common/ulpi.c
drivers/usb/core/devio.c
drivers/usb/core/hcd-pci.c
drivers/usb/core/hub.c
drivers/usb/core/usb-acpi.c
drivers/usb/core/usb.c
drivers/usb/dwc2/params.c
drivers/usb/dwc3/core.c
drivers/usb/dwc3/core.h
drivers/usb/dwc3/drd.c
drivers/usb/dwc3/dwc3-pci.c
drivers/usb/gadget/function/f_fs.c
drivers/usb/gadget/function/f_mass_storage.c
drivers/usb/gadget/function/f_phonet.c
drivers/usb/gadget/function/f_serial.c
drivers/usb/gadget/function/f_uac1.c
drivers/usb/gadget/function/f_uac2.c
drivers/usb/gadget/function/u_audio.c
drivers/usb/gadget/function/u_audio.h
drivers/usb/gadget/function/u_uac1.h
drivers/usb/gadget/function/u_uac2.h
drivers/usb/gadget/function/uac_common.h [new file with mode: 0644]
drivers/usb/gadget/legacy/audio.c
drivers/usb/gadget/udc/tegra-xudc.c
drivers/usb/host/ehci-dbg.c
drivers/usb/host/ehci-pci.c
drivers/usb/host/ehci-platform.c
drivers/usb/host/ehci-q.c
drivers/usb/host/ehci-sched.c
drivers/usb/host/fotg210-hcd.c
drivers/usb/host/ohci-dbg.c
drivers/usb/host/xhci-dbgcap.c
drivers/usb/host/xhci-dbgcap.h
drivers/usb/host/xhci-dbgtty.c
drivers/usb/host/xhci-mem.c
drivers/usb/host/xhci-mtk-sch.c
drivers/usb/host/xhci-mtk.c
drivers/usb/host/xhci-mtk.h
drivers/usb/host/xhci-plat.c
drivers/usb/host/xhci.c
drivers/usb/host/xhci.h
drivers/usb/misc/Kconfig
drivers/usb/misc/Makefile
drivers/usb/misc/qcom_eud.c [new file with mode: 0644]
drivers/usb/typec/Kconfig
drivers/usb/typec/Makefile
drivers/usb/typec/class.c
drivers/usb/typec/rt1719.c [new file with mode: 0644]
drivers/usb/typec/tcpm/tcpm.c
drivers/usb/typec/wusb3801.c [new file with mode: 0644]
drivers/usb/usbip/vudc_main.c
include/linux/usb/typec.h

index d4b8cf4..c4ba92f 100644 (file)
@@ -6,7 +6,7 @@ Description:
 
                =====================   =======================================
                c_chmask                capture channel mask
-               c_srate                 capture sampling rate
+               c_srate                 list of capture sampling rates (comma-separated)
                c_ssize                 capture sample size (bytes)
                c_mute_present          capture mute control enable
                c_volume_present        capture volume control enable
@@ -17,7 +17,7 @@ Description:
                c_volume_res            capture volume control resolution
                                        (in 1/256 dB)
                p_chmask                playback channel mask
-               p_srate                 playback sampling rate
+               p_srate                 list of playback sampling rates (comma-separated)
                p_ssize                 playback sample size (bytes)
                p_mute_present          playback mute control enable
                p_volume_present        playback volume control enable
@@ -29,4 +29,5 @@ Description:
                                        (in 1/256 dB)
                req_number              the number of pre-allocated requests
                                        for both capture and playback
+               function_name           name of the interface
                =====================   =======================================
index 7fb3dbe..3371c39 100644 (file)
@@ -6,8 +6,9 @@ Description:
 
                =====================   =======================================
                c_chmask                capture channel mask
-               c_srate                 capture sampling rate
+               c_srate                 list of capture sampling rates (comma-separated)
                c_ssize                 capture sample size (bytes)
+               c_hs_bint               capture bInterval for HS/SS (1-4: fixed, 0: auto)
                c_sync                  capture synchronization type
                                        (async/adaptive)
                c_mute_present          capture mute control enable
@@ -20,8 +21,9 @@ Description:
                                        (in 1/256 dB)
                fb_max                  maximum extra bandwidth in async mode
                p_chmask                playback channel mask
-               p_srate                 playback sampling rate
+               p_srate                 list of playback sampling rates (comma-separated)
                p_ssize                 playback sample size (bytes)
+               p_hs_bint               playback bInterval for HS/SS (1-4: fixed, 0: auto)
                p_mute_present          playback mute control enable
                p_volume_present        playback volume control enable
                p_volume_min            playback volume control min value
@@ -32,4 +34,5 @@ Description:
                                        (in 1/256 dB)
                req_number              the number of pre-allocated requests
                                        for both capture and playback
+               function_name           name of the interface
                =====================   =======================================
diff --git a/Documentation/ABI/testing/sysfs-driver-eud b/Documentation/ABI/testing/sysfs-driver-eud
new file mode 100644 (file)
index 0000000..83f3872
--- /dev/null
@@ -0,0 +1,9 @@
+What:          /sys/bus/platform/drivers/eud/.../enable
+Date:           February 2022
+Contact:        Souradeep Chowdhury <quic_schowdhu@quicinc.com>
+Description:
+               The Enable/Disable sysfs interface for Embedded
+               USB Debugger(EUD). This enables and disables the
+               EUD based on a 1 or a 0 value. By enabling EUD,
+               the user is able to activate the mini-usb hub of
+               EUD for debug and trace capabilities.
diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,eud.yaml b/Documentation/devicetree/bindings/soc/qcom/qcom,eud.yaml
new file mode 100644 (file)
index 0000000..c98aab2
--- /dev/null
@@ -0,0 +1,77 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/soc/qcom/qcom,eud.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Qualcomm Embedded USB Debugger
+
+maintainers:
+  - Souradeep Chowdhury <quic_schowdhu@quicinc.com>
+
+description:
+  This binding is used to describe the Qualcomm Embedded USB Debugger, which is
+  mini USB-hub implemented on chip to support USB-based debug capabilities.
+
+properties:
+  compatible:
+    items:
+      - enum:
+          - qcom,sc7280-eud
+      - const: qcom,eud
+
+  reg:
+    items:
+      - description: EUD Base Register Region
+      - description: EUD Mode Manager Register
+
+  interrupts:
+    description: EUD interrupt
+    maxItems: 1
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+    description:
+      These ports is to be attached to the endpoint of the DWC3 controller node
+      and type C connector node. The controller has the "usb-role-switch"
+      property.
+
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: This port is to be attached to the DWC3 controller.
+
+      port@1:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: This port is to be attached to the type C connector.
+
+required:
+  - compatible
+  - reg
+  - ports
+
+additionalProperties: false
+
+examples:
+  - |
+    eud@88e0000 {
+           compatible = "qcom,sc7280-eud","qcom,eud";
+           reg = <0x88e0000 0x2000>,
+                 <0x88e2000 0x1000>;
+           ports {
+                   #address-cells = <1>;
+                   #size-cells = <0>;
+                   port@0 {
+                           reg = <0>;
+                           eud_ep: endpoint {
+                                   remote-endpoint = <&usb2_role_switch>;
+                           };
+                   };
+                   port@1 {
+                           reg = <1>;
+                           eud_con: endpoint {
+                                   remote-endpoint = <&con_eud>;
+                           };
+                   };
+           };
+    };
index f00867e..481aaa0 100644 (file)
@@ -53,6 +53,7 @@ properties:
           - const: st,stm32mp15-hsotg
           - const: snps,dwc2
       - const: samsung,s3c6400-hsotg
+      - const: intel,socfpga-agilex-hsotg
 
   reg:
     maxItems: 1
index 11f7bac..41efb51 100644 (file)
@@ -146,7 +146,11 @@ properties:
             2 - used by mt2712 etc, revision 2 following IPM rule;
             101 - used by mt8183, specific 1.01;
             102 - used by mt8192, specific 1.02;
-          enum: [1, 2, 101, 102]
+            103 - used by mt8195, IP0, specific 1.03;
+            104 - used by mt8195, IP1, specific 1.04;
+            105 - used by mt8195, IP2, specific 1.05;
+            106 - used by mt8195, IP3, specific 1.06;
+          enum: [1, 2, 101, 102, 103, 104, 105, 106]
 
   mediatek,u3p-dis-msk:
     $ref: /schemas/types.yaml#/definitions/uint32
diff --git a/Documentation/devicetree/bindings/usb/microchip,mpfs-musb.yaml b/Documentation/devicetree/bindings/usb/microchip,mpfs-musb.yaml
new file mode 100644 (file)
index 0000000..48c458c
--- /dev/null
@@ -0,0 +1,59 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/microchip,mpfs-musb.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip MPFS USB Controller Device Tree Bindings
+
+allOf:
+  - $ref: usb-drd.yaml#
+
+maintainers:
+  - Conor Dooley <conor.dooley@microchip.com>
+
+properties:
+  compatible:
+    enum:
+      - microchip,mpfs-musb
+
+  dr_mode: true
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    minItems: 2
+    maxItems: 2
+
+  interrupt-names:
+    items:
+      - const: dma
+      - const: mc
+
+  clocks:
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - interrupt-names
+  - clocks
+
+additionalProperties: false
+
+examples:
+  - |
+    #include "dt-bindings/clock/microchip,mpfs-clock.h"
+    usb@20201000 {
+        compatible = "microchip,mpfs-musb";
+        reg = <0x20201000 0x1000>;
+        clocks = <&clkcfg CLK_USB>;
+        interrupt-parent = <&plic>;
+        interrupts = <86>, <87>;
+        interrupt-names = "dma", "mc";
+        dr_mode = "host";
+    };
+
+...
index 2d23a4f..ce252db 100644 (file)
@@ -16,6 +16,7 @@ properties:
           - qcom,ipq4019-dwc3
           - qcom,ipq6018-dwc3
           - qcom,ipq8064-dwc3
+          - qcom,msm8953-dwc3
           - qcom,msm8996-dwc3
           - qcom,msm8998-dwc3
           - qcom,sc7180-dwc3
diff --git a/Documentation/devicetree/bindings/usb/richtek,rt1719.yaml b/Documentation/devicetree/bindings/usb/richtek,rt1719.yaml
new file mode 100644 (file)
index 0000000..65a93f7
--- /dev/null
@@ -0,0 +1,85 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/usb/richtek,rt1719.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Richtek RT1719 sink-only Type-C PD controller bindings
+
+maintainers:
+  - ChiYuan Huang <cy_huang@richtek.com>
+
+description: |
+  The RT1719 is a sink-only USB Type-C contoller that complies with the latest
+  USB Type-C and PD standards. It does the USB Type-C detection including attach
+  and orientation. It integrates the physical layer of the USB BMC power
+  delivery protocol to allow up to 100W of power. The BMC PD block enables full
+  support for alternative interfaces of the Type-C specification.
+
+properties:
+  compatible:
+    enum:
+      - richtek,rt1719
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  wakeup-source:
+    description: enable IRQ remote wakeup, see power/wakeup-source.txt
+    type: boolean
+
+  connector:
+    type: object
+    $ref: ../connector/usb-connector.yaml#
+    description:
+      Properties for usb c connector.
+
+additionalProperties: false
+
+required:
+  - compatible
+  - reg
+  - connector
+  - interrupts
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+    i2c0 {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      rt1719@43 {
+        compatible = "richtek,rt1719";
+        reg = <0x43>;
+        interrupts-extended = <&gpio26 2 IRQ_TYPE_LEVEL_LOW>;
+        wakeup-source;
+
+        connector {
+          compatible = "usb-c-connector";
+          label = "USB-C";
+
+          ports {
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            port@0 {
+              reg = <0>;
+              endpoint {
+                remote-endpoint = <&usb_hs>;
+              };
+            };
+            port@1 {
+              reg = <1>;
+              endpoint {
+                remote-endpoint = <&usb_ss>;
+              };
+            };
+          };
+        };
+      };
+    };
+...
index d29ffcd..f4471f8 100644 (file)
@@ -263,8 +263,11 @@ properties:
       Value for REFCLKPER field of GUCTL register for reference clock period in
       nanoseconds, when the hardware set default does not match the actual
       clock.
-    minimum: 1
-    maximum: 0x3ff
+
+      This binding is deprecated. Instead, provide an appropriate reference clock.
+    minimum: 8
+    maximum: 62
+    deprecated: true
 
   snps,rx-thr-num-pkt-prd:
     description:
@@ -332,6 +335,12 @@ properties:
     items:
       enum: [1, 4, 8, 16, 32, 64, 128, 256]
 
+  port:
+    $ref: /schemas/graph.yaml#/properties/port
+    description:
+      This port is used with the 'usb-role-switch' property  to connect the
+      dwc3 to type C connector.
+
 unevaluatedProperties: false
 
 required:
diff --git a/Documentation/devicetree/bindings/usb/willsemi,wusb3801.yaml b/Documentation/devicetree/bindings/usb/willsemi,wusb3801.yaml
new file mode 100644 (file)
index 0000000..c2b2243
--- /dev/null
@@ -0,0 +1,75 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/willsemi,wusb3801.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: WUSB3801 Type-C port controller DT bindings
+
+description:
+  The Will Semiconductor WUSB3801 is a USB Type-C port controller which
+  supports role and plug orientation detection using the CC pins. It is
+  compatible with the USB Type-C Cable and Connector Specification v1.2.
+
+maintainers:
+  - Samuel Holland <samuel@sholland.org>
+
+properties:
+  compatible:
+    enum:
+      - willsemi,wusb3801
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  connector:
+    type: object
+    $ref: ../connector/usb-connector.yaml#
+    description:
+      The managed USB Type-C connector. Since WUSB3801 does not support
+      Power Delivery, the node should have the "pd-disable" property.
+
+    properties:
+      compatible:
+        const: usb-c-connector
+
+    required:
+      - pd-disable
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - connector
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    i2c {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      tcpc@60 {
+        compatible = "willsemi,wusb3801";
+        reg = <0x60>;
+        interrupt-parent = <&gpio0>;
+        interrupts = <4 IRQ_TYPE_LEVEL_LOW>;
+
+        connector {
+          compatible = "usb-c-connector";
+          label = "USB-C";
+          vbus-supply = <&otg_switch>;
+          power-role = "dual";
+          try-power-role = "sink";
+          data-role = "dual";
+          typec-power-opmode = "default";
+          pd-disable;
+        };
+      };
+    };
index 294093d..a8ab977 100644 (file)
@@ -1340,6 +1340,8 @@ patternProperties:
     description: Wi2Wi, Inc.
   "^wiligear,.*":
     description: Wiligear, Ltd.
+  "^willsemi,.*":
+    description: Will Semiconductor Ltd.
   "^winbond,.*":
     description: Winbond Electronics corp.
   "^wingtech,.*":
index cbbd948..c6d034a 100644 (file)
@@ -726,7 +726,7 @@ The uac2 function provides these attributes in its function directory:
 
        ================ ====================================================
        c_chmask         capture channel mask
-       c_srate          capture sampling rate
+       c_srate          list of capture sampling rates (comma-separated)
        c_ssize          capture sample size (bytes)
        c_sync           capture synchronization type (async/adaptive)
        c_mute_present   capture mute control enable
@@ -734,17 +734,20 @@ The uac2 function provides these attributes in its function directory:
        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)
+       c_hs_bint        capture bInterval for HS/SS (1-4: fixed, 0: auto)
        fb_max           maximum extra bandwidth in async mode
        p_chmask         playback channel mask
-       p_srate          playback sampling rate
+       p_srate          list of playback sampling rates (comma-separated)
        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)
+       p_hs_bint        playback bInterval for HS/SS (1-4: fixed, 0: auto)
        req_number       the number of pre-allocated request for both capture
                         and playback
+       function_name    name of the interface
        ================ ====================================================
 
 The attributes have sane default values.
@@ -916,7 +919,7 @@ The uac1 function provides these attributes in its function directory:
 
        ================ ====================================================
        c_chmask         capture channel mask
-       c_srate          capture sampling rate
+       c_srate          list of capture sampling rates (comma-separated)
        c_ssize          capture sample size (bytes)
        c_mute_present   capture mute control enable
        c_volume_present capture volume control enable
@@ -924,7 +927,7 @@ The uac1 function provides these attributes in its function directory:
        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_srate          list of playback sampling rates (comma-separated)
        p_ssize          playback sample size (bytes)
        p_mute_present   playback mute control enable
        p_volume_present playback volume control enable
@@ -933,6 +936,7 @@ The uac1 function provides these attributes in its function directory:
        p_volume_res     playback volume control resolution (in 1/256 dB)
        req_number       the number of pre-allocated requests for both capture
                         and playback
+       function_name    name of the interface
        ================ ====================================================
 
 The attributes have sane default values.
index 1ba1e4a..63db239 100644 (file)
@@ -15770,6 +15770,14 @@ F:     sound/soc/codecs/wcd-clsh-v2.*
 F:     sound/soc/codecs/wsa881x.c
 F:     sound/soc/qcom/
 
+QCOM EMBEDDED USB DEBUGGER (EUD)
+M:     Souradeep Chowdhury <quic_schowdhu@quicinc.com>
+L:     linux-arm-msm@vger.kernel.org
+S:     Maintained
+F:     Documentation/ABI/testing/sysfs-driver-eud
+F:     Documentation/devicetree/bindings/soc/qcom/qcom,eud.yaml
+F:     drivers/usb/misc/qcom_eud.c
+
 QCOM IPA DRIVER
 M:     Alex Elder <elder@kernel.org>
 L:     netdev@vger.kernel.org
index 0dd2d2e..f4270cf 100644 (file)
                };
 
                usb0: usb@ffb00000 {
-                       compatible = "snps,dwc2";
+                       compatible = "intel,socfpga-agilex-hsotg", "snps,dwc2";
                        reg = <0xffb00000 0x40000>;
                        interrupts = <GIC_SPI 93 IRQ_TYPE_LEVEL_HIGH>;
                        phys = <&usbphy0>;
                };
 
                usb1: usb@ffb40000 {
-                       compatible = "snps,dwc2";
+                       compatible = "intel,socfpga-agilex-hsotg", "snps,dwc2";
                        reg = <0xffb40000 0x40000>;
                        interrupts = <GIC_SPI 94 IRQ_TYPE_LEVEL_HIGH>;
                        phys = <&usbphy0>;
index 66ec561..a614b9f 100644 (file)
                                interrupts = <GIC_SPI 140 IRQ_TYPE_LEVEL_HIGH>;
                                phys = <&qusb_phy_0>, <&usb0_ssphy>;
                                phy-names = "usb2-phy", "usb3-phy";
+                               clocks = <&xo>;
+                               clock-names = "ref";
                                tx-fifo-resize;
                                snps,is-utmi-l1-suspend;
                                snps,hird-threshold = /bits/ 8 <0x0>;
                                snps,dis_u2_susphy_quirk;
                                snps,dis_u3_susphy_quirk;
-                               snps,ref-clock-period-ns = <0x32>;
                                dr_mode = "host";
                        };
                };
index 9b991ba..f40eaa5 100644 (file)
        modem-init;
 };
 
+&usb_2_dwc3 {
+       dr_mode = "otg";
+};
+
 &pmk8350_rtc {
        status = "okay";
 };
index 937c2e0..96917fe 100644 (file)
                                phys = <&usb_2_hsphy>;
                                phy-names = "usb2-phy";
                                maximum-speed = "high-speed";
+                               usb-role-switch;
+                               port {
+                                       usb2_role_switch: endpoint {
+                                               remote-endpoint = <&eud_ep>;
+                                       };
+                               };
                        };
                };
 
                        interrupts = <GIC_SPI 582 IRQ_TYPE_LEVEL_HIGH>;
                };
 
+               eud: eud@88e0000 {
+                       compatible = "qcom,sc7280-eud","qcom,eud";
+                       reg = <0 0x88e0000 0 0x2000>,
+                             <0 0x88e2000 0 0x1000>;
+                       interrupts-extended = <&pdc 11 IRQ_TYPE_LEVEL_HIGH>;
+                       ports {
+                               port@0 {
+                                       eud_ep: endpoint {
+                                               remote-endpoint = <&usb2_role_switch>;
+                                       };
+                               };
+                               port@1 {
+                                       eud_con: endpoint {
+                                               remote-endpoint = <&con_eud>;
+                                       };
+                               };
+                       };
+               };
+
+               eud_typec: connector {
+                       compatible = "usb-c-connector";
+                       ports {
+                               port@0 {
+                                       con_eud: endpoint {
+                                               remote-endpoint = <&eud_con>;
+                                       };
+                               };
+                       };
+               };
+
                nsp_noc: interconnect@a0c0000 {
                        reg = <0 0x0a0c0000 0 0x10000>;
                        compatible = "qcom,sc7280-nsp-noc";
index 1e0b1bc..8493dd7 100644 (file)
        clocks = <&zynqmp_clk UART1_REF>, <&zynqmp_clk LPD_LSBUS>;
 };
 
-&usb0 {
+&dwc3_0 {
        clocks = <&zynqmp_clk USB0_BUS_REF>, <&zynqmp_clk USB3_DUAL_REF>;
 };
 
-&usb1 {
+&dwc3_1 {
        clocks = <&zynqmp_clk USB1_BUS_REF>, <&zynqmp_clk USB3_DUAL_REF>;
 };
 
index 74e6644..ba68fb8 100644 (file)
                        status = "disabled";
                        compatible = "xlnx,zynqmp-dwc3";
                        reg = <0x0 0xff9d0000 0x0 0x100>;
-                       clock-names = "bus_clk", "ref_clk";
                        power-domains = <&zynqmp_firmware PD_USB_0>;
                        resets = <&zynqmp_reset ZYNQMP_RESET_USB0_CORERESET>,
                                 <&zynqmp_reset ZYNQMP_RESET_USB0_HIBERRESET>,
                                interrupt-parent = <&gic>;
                                interrupt-names = "dwc_usb3", "otg";
                                interrupts = <0 65 4>, <0 69 4>;
+                               clock-names = "bus_early", "ref";
                                #stream-id-cells = <1>;
                                iommus = <&smmu 0x860>;
                                snps,quirk-frame-length-adjustment = <0x20>;
                        status = "disabled";
                        compatible = "xlnx,zynqmp-dwc3";
                        reg = <0x0 0xff9e0000 0x0 0x100>;
-                       clock-names = "bus_clk", "ref_clk";
                        power-domains = <&zynqmp_firmware PD_USB_1>;
                        resets = <&zynqmp_reset ZYNQMP_RESET_USB1_CORERESET>,
                                 <&zynqmp_reset ZYNQMP_RESET_USB1_HIBERRESET>,
                                interrupt-parent = <&gic>;
                                interrupt-names = "dwc_usb3", "otg";
                                interrupts = <0 70 4>, <0 74 4>;
+                               clock-names = "bus_early", "ref";
                                #stream-id-cells = <1>;
                                iommus = <&smmu 0x861>;
                                snps,quirk-frame-length-adjustment = <0x20>;
index a8776df..f0ca865 100644 (file)
@@ -182,208 +182,211 @@ static inline const char *cdnsp_decode_trb(char *str, size_t size, u32 field0,
        int ep_id = TRB_TO_EP_INDEX(field3) - 1;
        int type = TRB_FIELD_TO_TYPE(field3);
        unsigned int ep_num;
-       int ret = 0;
+       int ret;
        u32 temp;
 
        ep_num = DIV_ROUND_UP(ep_id, 2);
 
        switch (type) {
        case TRB_LINK:
-               ret += snprintf(str, size,
-                               "LINK %08x%08x intr %ld type '%s' flags %c:%c:%c:%c",
-                               field1, field0, GET_INTR_TARGET(field2),
-                               cdnsp_trb_type_string(type),
-                               field3 & TRB_IOC ? 'I' : 'i',
-                               field3 & TRB_CHAIN ? 'C' : 'c',
-                               field3 & TRB_TC ? 'T' : 't',
-                               field3 & TRB_CYCLE ? 'C' : 'c');
+               ret = snprintf(str, size,
+                              "LINK %08x%08x intr %ld type '%s' flags %c:%c:%c:%c",
+                              field1, field0, GET_INTR_TARGET(field2),
+                              cdnsp_trb_type_string(type),
+                              field3 & TRB_IOC ? 'I' : 'i',
+                              field3 & TRB_CHAIN ? 'C' : 'c',
+                              field3 & TRB_TC ? 'T' : 't',
+                              field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_TRANSFER:
        case TRB_COMPLETION:
        case TRB_PORT_STATUS:
        case TRB_HC_EVENT:
-               ret += snprintf(str, size,
-                               "ep%d%s(%d) type '%s' TRB %08x%08x status '%s'"
-                               " len %ld slot %ld flags %c:%c",
-                               ep_num, ep_id % 2 ? "out" : "in",
-                               TRB_TO_EP_INDEX(field3),
-                               cdnsp_trb_type_string(type), field1, field0,
-                               cdnsp_trb_comp_code_string(GET_COMP_CODE(field2)),
-                               EVENT_TRB_LEN(field2), TRB_TO_SLOT_ID(field3),
-                               field3 & EVENT_DATA ? 'E' : 'e',
-                               field3 & TRB_CYCLE ? 'C' : 'c');
+               ret = snprintf(str, size,
+                              "ep%d%s(%d) type '%s' TRB %08x%08x status '%s'"
+                              " len %ld slot %ld flags %c:%c",
+                              ep_num, ep_id % 2 ? "out" : "in",
+                              TRB_TO_EP_INDEX(field3),
+                              cdnsp_trb_type_string(type), field1, field0,
+                              cdnsp_trb_comp_code_string(GET_COMP_CODE(field2)),
+                              EVENT_TRB_LEN(field2), TRB_TO_SLOT_ID(field3),
+                              field3 & EVENT_DATA ? 'E' : 'e',
+                              field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_MFINDEX_WRAP:
-               ret += snprintf(str, size, "%s: flags %c",
-                               cdnsp_trb_type_string(type),
-                               field3 & TRB_CYCLE ? 'C' : 'c');
+               ret = snprintf(str, size, "%s: flags %c",
+                              cdnsp_trb_type_string(type),
+                              field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_SETUP:
-               ret += snprintf(str, size,
-                               "type '%s' bRequestType %02x bRequest %02x "
-                               "wValue %02x%02x wIndex %02x%02x wLength %d "
-                               "length %ld TD size %ld intr %ld Setup ID %ld "
-                               "flags %c:%c:%c",
-                               cdnsp_trb_type_string(type),
-                               field0 & 0xff,
-                               (field0 & 0xff00) >> 8,
-                               (field0 & 0xff000000) >> 24,
-                               (field0 & 0xff0000) >> 16,
-                               (field1 & 0xff00) >> 8,
-                               field1 & 0xff,
-                               (field1 & 0xff000000) >> 16 |
-                               (field1 & 0xff0000) >> 16,
-                               TRB_LEN(field2), GET_TD_SIZE(field2),
-                               GET_INTR_TARGET(field2),
-                               TRB_SETUPID_TO_TYPE(field3),
-                               field3 & TRB_IDT ? 'D' : 'd',
-                               field3 & TRB_IOC ? 'I' : 'i',
-                               field3 & TRB_CYCLE ? 'C' : 'c');
+               ret = snprintf(str, size,
+                              "type '%s' bRequestType %02x bRequest %02x "
+                              "wValue %02x%02x wIndex %02x%02x wLength %d "
+                              "length %ld TD size %ld intr %ld Setup ID %ld "
+                              "flags %c:%c:%c",
+                              cdnsp_trb_type_string(type),
+                              field0 & 0xff,
+                              (field0 & 0xff00) >> 8,
+                              (field0 & 0xff000000) >> 24,
+                              (field0 & 0xff0000) >> 16,
+                              (field1 & 0xff00) >> 8,
+                              field1 & 0xff,
+                              (field1 & 0xff000000) >> 16 |
+                              (field1 & 0xff0000) >> 16,
+                              TRB_LEN(field2), GET_TD_SIZE(field2),
+                              GET_INTR_TARGET(field2),
+                              TRB_SETUPID_TO_TYPE(field3),
+                              field3 & TRB_IDT ? 'D' : 'd',
+                              field3 & TRB_IOC ? 'I' : 'i',
+                              field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_DATA:
-               ret += snprintf(str, size,
-                               "type '%s' Buffer %08x%08x length %ld TD size %ld "
-                               "intr %ld flags %c:%c:%c:%c:%c:%c:%c",
-                               cdnsp_trb_type_string(type),
-                               field1, field0, TRB_LEN(field2),
-                               GET_TD_SIZE(field2),
-                               GET_INTR_TARGET(field2),
-                               field3 & TRB_IDT ? 'D' : 'i',
-                               field3 & TRB_IOC ? 'I' : 'i',
-                               field3 & TRB_CHAIN ? 'C' : 'c',
-                               field3 & TRB_NO_SNOOP ? 'S' : 's',
-                               field3 & TRB_ISP ? 'I' : 'i',
-                               field3 & TRB_ENT ? 'E' : 'e',
-                               field3 & TRB_CYCLE ? 'C' : 'c');
+               ret = snprintf(str, size,
+                              "type '%s' Buffer %08x%08x length %ld TD size %ld "
+                              "intr %ld flags %c:%c:%c:%c:%c:%c:%c",
+                              cdnsp_trb_type_string(type),
+                              field1, field0, TRB_LEN(field2),
+                              GET_TD_SIZE(field2),
+                              GET_INTR_TARGET(field2),
+                              field3 & TRB_IDT ? 'D' : 'i',
+                              field3 & TRB_IOC ? 'I' : 'i',
+                              field3 & TRB_CHAIN ? 'C' : 'c',
+                              field3 & TRB_NO_SNOOP ? 'S' : 's',
+                              field3 & TRB_ISP ? 'I' : 'i',
+                              field3 & TRB_ENT ? 'E' : 'e',
+                              field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_STATUS:
-               ret += snprintf(str, size,
-                               "Buffer %08x%08x length %ld TD size %ld intr"
-                               "%ld type '%s' flags %c:%c:%c:%c",
-                               field1, field0, TRB_LEN(field2),
-                               GET_TD_SIZE(field2),
-                               GET_INTR_TARGET(field2),
-                               cdnsp_trb_type_string(type),
-                               field3 & TRB_IOC ? 'I' : 'i',
-                               field3 & TRB_CHAIN ? 'C' : 'c',
-                               field3 & TRB_ENT ? 'E' : 'e',
-                               field3 & TRB_CYCLE ? 'C' : 'c');
+               ret = snprintf(str, size,
+                              "Buffer %08x%08x length %ld TD size %ld intr"
+                              "%ld type '%s' flags %c:%c:%c:%c",
+                              field1, field0, TRB_LEN(field2),
+                              GET_TD_SIZE(field2),
+                              GET_INTR_TARGET(field2),
+                              cdnsp_trb_type_string(type),
+                              field3 & TRB_IOC ? 'I' : 'i',
+                              field3 & TRB_CHAIN ? 'C' : 'c',
+                              field3 & TRB_ENT ? 'E' : 'e',
+                              field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_NORMAL:
        case TRB_ISOC:
        case TRB_EVENT_DATA:
        case TRB_TR_NOOP:
-               ret += snprintf(str, size,
-                               "type '%s' Buffer %08x%08x length %ld "
-                               "TD size %ld intr %ld "
-                               "flags %c:%c:%c:%c:%c:%c:%c:%c:%c",
-                               cdnsp_trb_type_string(type),
-                               field1, field0, TRB_LEN(field2),
-                               GET_TD_SIZE(field2),
-                               GET_INTR_TARGET(field2),
-                               field3 & TRB_BEI ? 'B' : 'b',
-                               field3 & TRB_IDT ? 'T' : 't',
-                               field3 & TRB_IOC ? 'I' : 'i',
-                               field3 & TRB_CHAIN ? 'C' : 'c',
-                               field3 & TRB_NO_SNOOP ? 'S' : 's',
-                               field3 & TRB_ISP ? 'I' : 'i',
-                               field3 & TRB_ENT ? 'E' : 'e',
-                               field3 & TRB_CYCLE ? 'C' : 'c',
-                               !(field3 & TRB_EVENT_INVALIDATE) ? 'V' : 'v');
+               ret = snprintf(str, size,
+                              "type '%s' Buffer %08x%08x length %ld "
+                              "TD size %ld intr %ld "
+                              "flags %c:%c:%c:%c:%c:%c:%c:%c:%c",
+                              cdnsp_trb_type_string(type),
+                              field1, field0, TRB_LEN(field2),
+                              GET_TD_SIZE(field2),
+                              GET_INTR_TARGET(field2),
+                              field3 & TRB_BEI ? 'B' : 'b',
+                              field3 & TRB_IDT ? 'T' : 't',
+                              field3 & TRB_IOC ? 'I' : 'i',
+                              field3 & TRB_CHAIN ? 'C' : 'c',
+                              field3 & TRB_NO_SNOOP ? 'S' : 's',
+                              field3 & TRB_ISP ? 'I' : 'i',
+                              field3 & TRB_ENT ? 'E' : 'e',
+                              field3 & TRB_CYCLE ? 'C' : 'c',
+                              !(field3 & TRB_EVENT_INVALIDATE) ? 'V' : 'v');
                break;
        case TRB_CMD_NOOP:
        case TRB_ENABLE_SLOT:
-               ret += snprintf(str, size, "%s: flags %c",
-                               cdnsp_trb_type_string(type),
-                               field3 & TRB_CYCLE ? 'C' : 'c');
+               ret = snprintf(str, size, "%s: flags %c",
+                              cdnsp_trb_type_string(type),
+                              field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_DISABLE_SLOT:
-               ret += snprintf(str, size, "%s: slot %ld flags %c",
-                               cdnsp_trb_type_string(type),
-                               TRB_TO_SLOT_ID(field3),
-                               field3 & TRB_CYCLE ? 'C' : 'c');
+               ret = snprintf(str, size, "%s: slot %ld flags %c",
+                              cdnsp_trb_type_string(type),
+                              TRB_TO_SLOT_ID(field3),
+                              field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_ADDR_DEV:
-               ret += snprintf(str, size,
-                               "%s: ctx %08x%08x slot %ld flags %c:%c",
-                               cdnsp_trb_type_string(type), field1, field0,
-                               TRB_TO_SLOT_ID(field3),
-                               field3 & TRB_BSR ? 'B' : 'b',
-                               field3 & TRB_CYCLE ? 'C' : 'c');
+               ret = snprintf(str, size,
+                              "%s: ctx %08x%08x slot %ld flags %c:%c",
+                              cdnsp_trb_type_string(type), field1, field0,
+                              TRB_TO_SLOT_ID(field3),
+                              field3 & TRB_BSR ? 'B' : 'b',
+                              field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_CONFIG_EP:
-               ret += snprintf(str, size,
-                               "%s: ctx %08x%08x slot %ld flags %c:%c",
-                               cdnsp_trb_type_string(type), field1, field0,
-                               TRB_TO_SLOT_ID(field3),
-                               field3 & TRB_DC ? 'D' : 'd',
-                               field3 & TRB_CYCLE ? 'C' : 'c');
+               ret = snprintf(str, size,
+                              "%s: ctx %08x%08x slot %ld flags %c:%c",
+                              cdnsp_trb_type_string(type), field1, field0,
+                              TRB_TO_SLOT_ID(field3),
+                              field3 & TRB_DC ? 'D' : 'd',
+                              field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_EVAL_CONTEXT:
-               ret += snprintf(str, size,
-                               "%s: ctx %08x%08x slot %ld flags %c",
-                               cdnsp_trb_type_string(type), field1, field0,
-                               TRB_TO_SLOT_ID(field3),
-                               field3 & TRB_CYCLE ? 'C' : 'c');
+               ret = snprintf(str, size,
+                              "%s: ctx %08x%08x slot %ld flags %c",
+                              cdnsp_trb_type_string(type), field1, field0,
+                              TRB_TO_SLOT_ID(field3),
+                              field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_RESET_EP:
        case TRB_HALT_ENDPOINT:
        case TRB_FLUSH_ENDPOINT:
-               ret += snprintf(str, size,
-                               "%s: ep%d%s(%d) ctx %08x%08x slot %ld flags %c",
-                               cdnsp_trb_type_string(type),
-                               ep_num, ep_id % 2 ? "out" : "in",
-                               TRB_TO_EP_INDEX(field3), field1, field0,
-                               TRB_TO_SLOT_ID(field3),
-                               field3 & TRB_CYCLE ? 'C' : 'c');
+               ret = snprintf(str, size,
+                              "%s: ep%d%s(%d) ctx %08x%08x slot %ld flags %c",
+                              cdnsp_trb_type_string(type),
+                              ep_num, ep_id % 2 ? "out" : "in",
+                              TRB_TO_EP_INDEX(field3), field1, field0,
+                              TRB_TO_SLOT_ID(field3),
+                              field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_STOP_RING:
-               ret += snprintf(str, size,
-                               "%s: ep%d%s(%d) slot %ld sp %d flags %c",
-                               cdnsp_trb_type_string(type),
-                               ep_num, ep_id % 2 ? "out" : "in",
-                               TRB_TO_EP_INDEX(field3),
-                               TRB_TO_SLOT_ID(field3),
-                               TRB_TO_SUSPEND_PORT(field3),
-                               field3 & TRB_CYCLE ? 'C' : 'c');
+               ret = snprintf(str, size,
+                              "%s: ep%d%s(%d) slot %ld sp %d flags %c",
+                              cdnsp_trb_type_string(type),
+                              ep_num, ep_id % 2 ? "out" : "in",
+                              TRB_TO_EP_INDEX(field3),
+                              TRB_TO_SLOT_ID(field3),
+                              TRB_TO_SUSPEND_PORT(field3),
+                              field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_SET_DEQ:
-               ret += snprintf(str, size,
-                               "%s: ep%d%s(%d) deq %08x%08x stream %ld slot %ld  flags %c",
-                               cdnsp_trb_type_string(type),
-                               ep_num, ep_id % 2 ? "out" : "in",
-                               TRB_TO_EP_INDEX(field3), field1, field0,
-                               TRB_TO_STREAM_ID(field2),
-                               TRB_TO_SLOT_ID(field3),
-                               field3 & TRB_CYCLE ? 'C' : 'c');
+               ret = snprintf(str, size,
+                              "%s: ep%d%s(%d) deq %08x%08x stream %ld slot %ld  flags %c",
+                              cdnsp_trb_type_string(type),
+                              ep_num, ep_id % 2 ? "out" : "in",
+                              TRB_TO_EP_INDEX(field3), field1, field0,
+                              TRB_TO_STREAM_ID(field2),
+                              TRB_TO_SLOT_ID(field3),
+                              field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_RESET_DEV:
-               ret += snprintf(str, size, "%s: slot %ld flags %c",
-                               cdnsp_trb_type_string(type),
-                               TRB_TO_SLOT_ID(field3),
-                               field3 & TRB_CYCLE ? 'C' : 'c');
+               ret = snprintf(str, size, "%s: slot %ld flags %c",
+                              cdnsp_trb_type_string(type),
+                              TRB_TO_SLOT_ID(field3),
+                              field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        case TRB_ENDPOINT_NRDY:
-               temp  = TRB_TO_HOST_STREAM(field2);
-
-               ret += snprintf(str, size,
-                               "%s: ep%d%s(%d) H_SID %x%s%s D_SID %lx flags %c:%c",
-                               cdnsp_trb_type_string(type),
-                               ep_num, ep_id % 2 ? "out" : "in",
-                               TRB_TO_EP_INDEX(field3), temp,
-                               temp == STREAM_PRIME_ACK ? "(PRIME)" : "",
-                               temp == STREAM_REJECTED ? "(REJECTED)" : "",
-                               TRB_TO_DEV_STREAM(field0),
-                               field3 & TRB_STAT ? 'S' : 's',
-                               field3 & TRB_CYCLE ? 'C' : 'c');
+               temp = TRB_TO_HOST_STREAM(field2);
+
+               ret = snprintf(str, size,
+                              "%s: ep%d%s(%d) H_SID %x%s%s D_SID %lx flags %c:%c",
+                              cdnsp_trb_type_string(type),
+                              ep_num, ep_id % 2 ? "out" : "in",
+                              TRB_TO_EP_INDEX(field3), temp,
+                              temp == STREAM_PRIME_ACK ? "(PRIME)" : "",
+                              temp == STREAM_REJECTED ? "(REJECTED)" : "",
+                              TRB_TO_DEV_STREAM(field0),
+                              field3 & TRB_STAT ? 'S' : 's',
+                              field3 & TRB_CYCLE ? 'C' : 'c');
                break;
        default:
-               ret += snprintf(str, size,
-                               "type '%s' -> raw %08x %08x %08x %08x",
-                               cdnsp_trb_type_string(type),
-                               field0, field1, field2, field3);
+               ret = snprintf(str, size,
+                              "type '%s' -> raw %08x %08x %08x %08x",
+                              cdnsp_trb_type_string(type),
+                              field0, field1, field2, field3);
        }
 
+       if (ret >= size)
+               pr_info("CDNSP: buffer overflowed.\n");
+
        return str;
 }
 
index 5c9d07c..c67715f 100644 (file)
@@ -1243,12 +1243,9 @@ static int cdnsp_run(struct cdnsp_device *pdev,
                     enum usb_device_speed speed)
 {
        u32 fs_speed = 0;
-       u64 temp_64;
        u32 temp;
        int ret;
 
-       temp_64 = cdnsp_read_64(&pdev->ir_set->erst_dequeue);
-       temp_64 &= ~ERST_PTR_MASK;
        temp = readl(&pdev->ir_set->irq_control);
        temp &= ~IMOD_INTERVAL_MASK;
        temp |= ((IMOD_DEFAULT_INTERVAL / 250) & IMOD_INTERVAL_MASK);
index f9ca501..dc6c96e 100644 (file)
@@ -2152,7 +2152,7 @@ static void udc_id_switch_for_host(struct ci_hdrc *ci)
 {
        /*
         * host doesn't care B_SESSION_VALID event
-        * so clear and disbale BSV irq
+        * so clear and disable BSV irq
         */
        if (ci->is_otg)
                hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, OTGSC_BSVIS);
index 5509d38..0a4f441 100644 (file)
@@ -13,6 +13,7 @@
 #include <linux/module.h>
 #include <linux/slab.h>
 #include <linux/acpi.h>
+#include <linux/debugfs.h>
 #include <linux/of.h>
 #include <linux/of_device.h>
 #include <linux/clk/clk-conf.h>
@@ -232,9 +233,64 @@ err:
        return 0;
 }
 
+static int ulpi_regs_read(struct seq_file *seq, void *data)
+{
+       struct ulpi *ulpi = seq->private;
+
+#define ulpi_print(name, reg) do { \
+       int ret = ulpi_read(ulpi, reg); \
+       if (ret < 0) \
+               return ret; \
+       seq_printf(seq, name " %.02x\n", ret); \
+} while (0)
+
+       ulpi_print("Vendor ID Low               ", ULPI_VENDOR_ID_LOW);
+       ulpi_print("Vendor ID High              ", ULPI_VENDOR_ID_HIGH);
+       ulpi_print("Product ID Low              ", ULPI_PRODUCT_ID_LOW);
+       ulpi_print("Product ID High             ", ULPI_PRODUCT_ID_HIGH);
+       ulpi_print("Function Control            ", ULPI_FUNC_CTRL);
+       ulpi_print("Interface Control           ", ULPI_IFC_CTRL);
+       ulpi_print("OTG Control                 ", ULPI_OTG_CTRL);
+       ulpi_print("USB Interrupt Enable Rising ", ULPI_USB_INT_EN_RISE);
+       ulpi_print("USB Interrupt Enable Falling", ULPI_USB_INT_EN_FALL);
+       ulpi_print("USB Interrupt Status        ", ULPI_USB_INT_STS);
+       ulpi_print("USB Interrupt Latch         ", ULPI_USB_INT_LATCH);
+       ulpi_print("Debug                       ", ULPI_DEBUG);
+       ulpi_print("Scratch Register            ", ULPI_SCRATCH);
+       ulpi_print("Carkit Control              ", ULPI_CARKIT_CTRL);
+       ulpi_print("Carkit Interrupt Delay      ", ULPI_CARKIT_INT_DELAY);
+       ulpi_print("Carkit Interrupt Enable     ", ULPI_CARKIT_INT_EN);
+       ulpi_print("Carkit Interrupt Status     ", ULPI_CARKIT_INT_STS);
+       ulpi_print("Carkit Interrupt Latch      ", ULPI_CARKIT_INT_LATCH);
+       ulpi_print("Carkit Pulse Control        ", ULPI_CARKIT_PLS_CTRL);
+       ulpi_print("Transmit Positive Width     ", ULPI_TX_POS_WIDTH);
+       ulpi_print("Transmit Negative Width     ", ULPI_TX_NEG_WIDTH);
+       ulpi_print("Receive Polarity Recovery   ", ULPI_POLARITY_RECOVERY);
+
+       return 0;
+}
+
+static int ulpi_regs_open(struct inode *inode, struct file *f)
+{
+       struct ulpi *ulpi = inode->i_private;
+
+       return single_open(f, ulpi_regs_read, ulpi);
+}
+
+static const struct file_operations ulpi_regs_ops = {
+       .owner = THIS_MODULE,
+       .open = ulpi_regs_open,
+       .release = single_release,
+       .read = seq_read,
+       .llseek = seq_lseek
+};
+
+#define ULPI_ROOT debugfs_lookup(KBUILD_MODNAME, NULL)
+
 static int ulpi_register(struct device *dev, struct ulpi *ulpi)
 {
        int ret;
+       struct dentry *root;
 
        ulpi->dev.parent = dev; /* needed early for ops */
        ulpi->dev.bus = &ulpi_bus;
@@ -259,6 +315,9 @@ static int ulpi_register(struct device *dev, struct ulpi *ulpi)
                return ret;
        }
 
+       root = debugfs_create_dir(dev_name(dev), ULPI_ROOT);
+       debugfs_create_file("regs", 0444, root, ulpi, &ulpi_regs_ops);
+
        dev_dbg(&ulpi->dev, "registered ULPI PHY: vendor %04x, product %04x\n",
                ulpi->id.vendor, ulpi->id.product);
 
@@ -304,6 +363,8 @@ EXPORT_SYMBOL_GPL(ulpi_register_interface);
  */
 void ulpi_unregister_interface(struct ulpi *ulpi)
 {
+       debugfs_remove_recursive(debugfs_lookup(dev_name(&ulpi->dev),
+                                               ULPI_ROOT));
        device_unregister(&ulpi->dev);
 }
 EXPORT_SYMBOL_GPL(ulpi_unregister_interface);
@@ -312,13 +373,21 @@ EXPORT_SYMBOL_GPL(ulpi_unregister_interface);
 
 static int __init ulpi_init(void)
 {
-       return bus_register(&ulpi_bus);
+       int ret;
+       struct dentry *root;
+
+       root = debugfs_create_dir(KBUILD_MODNAME, NULL);
+       ret = bus_register(&ulpi_bus);
+       if (ret)
+               debugfs_remove(root);
+       return ret;
 }
 subsys_initcall(ulpi_init);
 
 static void __exit ulpi_exit(void)
 {
        bus_unregister(&ulpi_bus);
+       debugfs_remove_recursive(ULPI_ROOT);
 }
 module_exit(ulpi_exit);
 
index fa66e6e..6abb729 100644 (file)
@@ -139,30 +139,42 @@ MODULE_PARM_DESC(usbfs_memory_mb,
 /* Hard limit, necessary to avoid arithmetic overflow */
 #define USBFS_XFER_MAX         (UINT_MAX / 2 - 1000000)
 
-static atomic64_t usbfs_memory_usage;  /* Total memory currently allocated */
+static DEFINE_SPINLOCK(usbfs_memory_usage_lock);
+static u64 usbfs_memory_usage; /* Total memory currently allocated */
 
 /* Check whether it's okay to allocate more memory for a transfer */
 static int usbfs_increase_memory_usage(u64 amount)
 {
-       u64 lim;
+       u64 lim, total_mem;
+       unsigned long flags;
+       int ret;
 
        lim = READ_ONCE(usbfs_memory_mb);
        lim <<= 20;
 
-       atomic64_add(amount, &usbfs_memory_usage);
-
-       if (lim > 0 && atomic64_read(&usbfs_memory_usage) > lim) {
-               atomic64_sub(amount, &usbfs_memory_usage);
-               return -ENOMEM;
-       }
+       ret = 0;
+       spin_lock_irqsave(&usbfs_memory_usage_lock, flags);
+       total_mem = usbfs_memory_usage + amount;
+       if (lim > 0 && total_mem > lim)
+               ret = -ENOMEM;
+       else
+               usbfs_memory_usage = total_mem;
+       spin_unlock_irqrestore(&usbfs_memory_usage_lock, flags);
 
-       return 0;
+       return ret;
 }
 
 /* Memory for a transfer is being deallocated */
 static void usbfs_decrease_memory_usage(u64 amount)
 {
-       atomic64_sub(amount, &usbfs_memory_usage);
+       unsigned long flags;
+
+       spin_lock_irqsave(&usbfs_memory_usage_lock, flags);
+       if (amount > usbfs_memory_usage)
+               usbfs_memory_usage = 0;
+       else
+               usbfs_memory_usage -= amount;
+       spin_unlock_irqrestore(&usbfs_memory_usage_lock, flags);
 }
 
 static int connected(struct usb_dev_state *ps)
index d630ccc..7844661 100644 (file)
@@ -248,7 +248,7 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id,
                                        hcd->rsrc_len, driver->description))
                                break;
                }
-               if (region == PCI_ROM_RESOURCE) {
+               if (region == PCI_STD_NUM_BARS) {
                        dev_dbg(&dev->dev, "no i/o regions available\n");
                        retval = -EBUSY;
                        goto put_hcd;
index 47a1c8b..1460857 100644 (file)
@@ -2983,8 +2983,12 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
                                                status);
                }
 
-               /* Check for disconnect or reset */
-               if (status == 0 || status == -ENOTCONN || status == -ENODEV) {
+               /*
+                * Check for disconnect or reset, and bail out after several
+                * reset attempts to avoid warm reset loop.
+                */
+               if (status == 0 || status == -ENOTCONN || status == -ENODEV ||
+                   (status == -EBUSY && i == PORT_RESET_TRIES - 1)) {
                        usb_clear_port_feature(hub->hdev, port1,
                                        USB_PORT_FEAT_C_RESET);
 
@@ -5005,6 +5009,7 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
                retval = usb_get_bos_descriptor(udev);
                if (!retval) {
                        udev->lpm_capable = usb_device_supports_lpm(udev);
+                       udev->lpm_disable_count = 1;
                        usb_set_lpm_parameters(udev);
                }
        }
@@ -5928,16 +5933,6 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
         */
        usb_disable_usb2_hardware_lpm(udev);
 
-       /* Disable LPM while we reset the device and reinstall the alt settings.
-        * Device-initiated LPM, and system exit latency settings are cleared
-        * when the device is reset, so we have to set them up again.
-        */
-       ret = usb_unlocked_disable_lpm(udev);
-       if (ret) {
-               dev_err(&udev->dev, "%s Failed to disable LPM\n", __func__);
-               goto re_enumerate_no_bos;
-       }
-
        bos = udev->bos;
        udev->bos = NULL;
 
@@ -6042,8 +6037,6 @@ done:
 re_enumerate:
        usb_release_bos_descriptor(udev);
        udev->bos = bos;
-re_enumerate_no_bos:
-       /* LPM state doesn't matter when we're about to destroy the device. */
        hub_port_logical_disconnect(parent_hub, port1);
        return -ENODEV;
 }
index 50b2fc7..bb1da35 100644 (file)
@@ -166,7 +166,7 @@ usb_acpi_get_companion_for_port(struct usb_port *port_dev)
                if (!parent_handle)
                        return NULL;
 
-               acpi_bus_get_device(parent_handle, &adev);
+               adev = acpi_fetch_acpi_dev(parent_handle);
                port1 = port_dev->portnum;
        }
 
index 2ce3667..2f71636 100644 (file)
@@ -688,6 +688,10 @@ EXPORT_SYMBOL_GPL(usb_alloc_dev);
  * Drivers for USB interfaces should normally record such references in
  * their probe() methods, when they bind to an interface, and release
  * them by calling usb_put_dev(), in their disconnect() methods.
+ * However, if a driver does not access the usb_device structure after
+ * its disconnect() method returns then refcounting is not necessary,
+ * because the USB core guarantees that a usb_device will not be
+ * deallocated until after all of its interface drivers have been unbound.
  *
  * Return: A pointer to the device with the incremented reference counter.
  */
@@ -722,6 +726,10 @@ EXPORT_SYMBOL_GPL(usb_put_dev);
  * Drivers for USB interfaces should normally record such references in
  * their probe() methods, when they bind to an interface, and release
  * them by calling usb_put_intf(), in their disconnect() methods.
+ * However, if a driver does not access the usb_interface structure after
+ * its disconnect() method returns then refcounting is not necessary,
+ * because the USB core guarantees that a usb_interface will not be
+ * deallocated until after its driver has been unbound.
  *
  * Return: A pointer to the interface with the incremented reference counter.
  */
index d300ae3..1306f4e 100644 (file)
@@ -82,6 +82,14 @@ static void dwc2_set_s3c6400_params(struct dwc2_hsotg *hsotg)
        p->phy_utmi_width = 8;
 }
 
+static void dwc2_set_socfpga_agilex_params(struct dwc2_hsotg *hsotg)
+{
+       struct dwc2_core_params *p = &hsotg->params;
+
+       p->power_down = DWC2_POWER_DOWN_PARAM_NONE;
+       p->no_clock_gating = true;
+}
+
 static void dwc2_set_rk_params(struct dwc2_hsotg *hsotg)
 {
        struct dwc2_core_params *p = &hsotg->params;
@@ -239,6 +247,8 @@ const struct of_device_id dwc2_of_match_table[] = {
          .data = dwc2_set_stm32mp15_fsotg_params },
        { .compatible = "st,stm32mp15-hsotg",
          .data = dwc2_set_stm32mp15_hsotg_params },
+       { .compatible = "intel,socfpga-agilex-hsotg",
+         .data = dwc2_set_socfpga_agilex_params },
        {},
 };
 MODULE_DEVICE_TABLE(of, dwc2_of_match_table);
index f4c0995..18adddf 100644 (file)
@@ -347,17 +347,64 @@ static void dwc3_frame_length_adjustment(struct dwc3 *dwc)
  */
 static void dwc3_ref_clk_period(struct dwc3 *dwc)
 {
+       unsigned long period;
+       unsigned long fladj;
+       unsigned long decr;
+       unsigned long rate;
        u32 reg;
 
-       if (dwc->ref_clk_per == 0)
+       if (dwc->ref_clk) {
+               rate = clk_get_rate(dwc->ref_clk);
+               if (!rate)
+                       return;
+               period = NSEC_PER_SEC / rate;
+       } else if (dwc->ref_clk_per) {
+               period = dwc->ref_clk_per;
+               rate = NSEC_PER_SEC / period;
+       } else {
                return;
+       }
 
        reg = dwc3_readl(dwc->regs, DWC3_GUCTL);
        reg &= ~DWC3_GUCTL_REFCLKPER_MASK;
-       reg |=  FIELD_PREP(DWC3_GUCTL_REFCLKPER_MASK, dwc->ref_clk_per);
+       reg |=  FIELD_PREP(DWC3_GUCTL_REFCLKPER_MASK, period);
        dwc3_writel(dwc->regs, DWC3_GUCTL, reg);
-}
 
+       if (DWC3_VER_IS_PRIOR(DWC3, 250A))
+               return;
+
+       /*
+        * The calculation below is
+        *
+        * 125000 * (NSEC_PER_SEC / (rate * period) - 1)
+        *
+        * but rearranged for fixed-point arithmetic. The division must be
+        * 64-bit because 125000 * NSEC_PER_SEC doesn't fit in 32 bits (and
+        * neither does rate * period).
+        *
+        * Note that rate * period ~= NSEC_PER_SECOND, minus the number of
+        * nanoseconds of error caused by the truncation which happened during
+        * the division when calculating rate or period (whichever one was
+        * derived from the other). We first calculate the relative error, then
+        * scale it to units of 8 ppm.
+        */
+       fladj = div64_u64(125000ULL * NSEC_PER_SEC, (u64)rate * period);
+       fladj -= 125000;
+
+       /*
+        * The documented 240MHz constant is scaled by 2 to get PLS1 as well.
+        */
+       decr = 480000000 / rate;
+
+       reg = dwc3_readl(dwc->regs, DWC3_GFLADJ);
+       reg &= ~DWC3_GFLADJ_REFCLK_FLADJ_MASK
+           &  ~DWC3_GFLADJ_240MHZDECR
+           &  ~DWC3_GFLADJ_240MHZDECR_PLS1;
+       reg |= FIELD_PREP(DWC3_GFLADJ_REFCLK_FLADJ_MASK, fladj)
+           |  FIELD_PREP(DWC3_GFLADJ_240MHZDECR, decr >> 1)
+           |  FIELD_PREP(DWC3_GFLADJ_240MHZDECR_PLS1, decr & 1);
+       dwc3_writel(dwc->regs, DWC3_GFLADJ, reg);
+}
 
 /**
  * dwc3_free_one_event_buffer - Frees one event buffer
@@ -745,6 +792,38 @@ static int dwc3_phy_setup(struct dwc3 *dwc)
        return 0;
 }
 
+static int dwc3_clk_enable(struct dwc3 *dwc)
+{
+       int ret;
+
+       ret = clk_prepare_enable(dwc->bus_clk);
+       if (ret)
+               return ret;
+
+       ret = clk_prepare_enable(dwc->ref_clk);
+       if (ret)
+               goto disable_bus_clk;
+
+       ret = clk_prepare_enable(dwc->susp_clk);
+       if (ret)
+               goto disable_ref_clk;
+
+       return 0;
+
+disable_ref_clk:
+       clk_disable_unprepare(dwc->ref_clk);
+disable_bus_clk:
+       clk_disable_unprepare(dwc->bus_clk);
+       return ret;
+}
+
+static void dwc3_clk_disable(struct dwc3 *dwc)
+{
+       clk_disable_unprepare(dwc->susp_clk);
+       clk_disable_unprepare(dwc->ref_clk);
+       clk_disable_unprepare(dwc->bus_clk);
+}
+
 static void dwc3_core_exit(struct dwc3 *dwc)
 {
        dwc3_event_buffers_cleanup(dwc);
@@ -758,7 +837,7 @@ static void dwc3_core_exit(struct dwc3 *dwc)
        usb_phy_set_suspend(dwc->usb3_phy, 1);
        phy_power_off(dwc->usb2_generic_phy);
        phy_power_off(dwc->usb3_generic_phy);
-       clk_bulk_disable_unprepare(dwc->num_clks, dwc->clks);
+       dwc3_clk_disable(dwc);
        reset_control_assert(dwc->reset);
 }
 
@@ -1605,25 +1684,31 @@ static int dwc3_probe(struct platform_device *pdev)
                return PTR_ERR(dwc->reset);
 
        if (dev->of_node) {
-               ret = devm_clk_bulk_get_all(dev, &dwc->clks);
-               if (ret == -EPROBE_DEFER)
-                       return ret;
                /*
                 * Clocks are optional, but new DT platforms should support all
                 * clocks as required by the DT-binding.
                 */
-               if (ret < 0)
-                       dwc->num_clks = 0;
-               else
-                       dwc->num_clks = ret;
-
+               dwc->bus_clk = devm_clk_get_optional(dev, "bus_early");
+               if (IS_ERR(dwc->bus_clk))
+                       return dev_err_probe(dev, PTR_ERR(dwc->bus_clk),
+                                            "could not get bus clock\n");
+
+               dwc->ref_clk = devm_clk_get_optional(dev, "ref");
+               if (IS_ERR(dwc->ref_clk))
+                       return dev_err_probe(dev, PTR_ERR(dwc->ref_clk),
+                                            "could not get ref clock\n");
+
+               dwc->susp_clk = devm_clk_get_optional(dev, "suspend");
+               if (IS_ERR(dwc->susp_clk))
+                       return dev_err_probe(dev, PTR_ERR(dwc->susp_clk),
+                                            "could not get suspend clock\n");
        }
 
        ret = reset_control_deassert(dwc->reset);
        if (ret)
                return ret;
 
-       ret = clk_bulk_prepare_enable(dwc->num_clks, dwc->clks);
+       ret = dwc3_clk_enable(dwc);
        if (ret)
                goto assert_reset;
 
@@ -1711,7 +1796,7 @@ err1:
        pm_runtime_disable(&pdev->dev);
 
 disable_clks:
-       clk_bulk_disable_unprepare(dwc->num_clks, dwc->clks);
+       dwc3_clk_disable(dwc);
 assert_reset:
        reset_control_assert(dwc->reset);
 
@@ -1755,7 +1840,7 @@ static int dwc3_core_init_for_resume(struct dwc3 *dwc)
        if (ret)
                return ret;
 
-       ret = clk_bulk_prepare_enable(dwc->num_clks, dwc->clks);
+       ret = dwc3_clk_enable(dwc);
        if (ret)
                goto assert_reset;
 
@@ -1766,7 +1851,7 @@ static int dwc3_core_init_for_resume(struct dwc3 *dwc)
        return 0;
 
 disable_clks:
-       clk_bulk_disable_unprepare(dwc->num_clks, dwc->clks);
+       dwc3_clk_disable(dwc);
 assert_reset:
        reset_control_assert(dwc->reset);
 
index e1cc3f7..eb9c1ef 100644 (file)
 /* Global Frame Length Adjustment Register */
 #define DWC3_GFLADJ_30MHZ_SDBND_SEL            BIT(7)
 #define DWC3_GFLADJ_30MHZ_MASK                 0x3f
+#define DWC3_GFLADJ_REFCLK_FLADJ_MASK          GENMASK(21, 8)
+#define DWC3_GFLADJ_240MHZDECR                 GENMASK(30, 24)
+#define DWC3_GFLADJ_240MHZDECR_PLS1            BIT(31)
 
 /* Global User Control Register*/
 #define DWC3_GUCTL_REFCLKPER_MASK              0xffc00000
@@ -978,8 +981,9 @@ struct dwc3_scratchpad_array {
  * @eps: endpoint array
  * @gadget: device side representation of the peripheral controller
  * @gadget_driver: pointer to the gadget driver
- * @clks: array of clocks
- * @num_clks: number of clocks
+ * @bus_clk: clock for accessing the registers
+ * @ref_clk: reference clock
+ * @susp_clk: clock used when the SS phy is in low power (S3) state
  * @reset: reset control
  * @regs: base address for our registers
  * @regs_size: address space size
@@ -1134,8 +1138,9 @@ struct dwc3 {
        struct usb_gadget       *gadget;
        struct usb_gadget_driver *gadget_driver;
 
-       struct clk_bulk_data    *clks;
-       int                     num_clks;
+       struct clk              *bus_clk;
+       struct clk              *ref_clk;
+       struct clk              *susp_clk;
 
        struct reset_control    *reset;
 
index d7f7683..b60b5f7 100644 (file)
@@ -9,6 +9,7 @@
 
 #include <linux/extcon.h>
 #include <linux/of_graph.h>
+#include <linux/of_platform.h>
 #include <linux/platform_device.h>
 #include <linux/property.h>
 
@@ -559,6 +560,18 @@ static int dwc3_setup_role_switch(struct dwc3 *dwc)
        if (IS_ERR(dwc->role_sw))
                return PTR_ERR(dwc->role_sw);
 
+       if (dwc->dev->of_node) {
+               /* populate connector entry */
+               int ret = devm_of_platform_populate(dwc->dev);
+
+               if (ret) {
+                       usb_role_switch_unregister(dwc->role_sw);
+                       dwc->role_sw = NULL;
+                       dev_err(dwc->dev, "DWC3 platform devices creation failed: %i\n", ret);
+                       return ret;
+               }
+       }
+
        dwc3_set_mode(dwc, mode);
        return 0;
 }
index 06d0e88..2e02199 100644 (file)
@@ -120,6 +120,13 @@ static const struct property_entry dwc3_pci_intel_properties[] = {
        {}
 };
 
+static const struct property_entry dwc3_pci_intel_phy_charger_detect_properties[] = {
+       PROPERTY_ENTRY_STRING("dr_mode", "peripheral"),
+       PROPERTY_ENTRY_BOOL("snps,dis_u2_susphy_quirk"),
+       PROPERTY_ENTRY_BOOL("linux,phy_charger_detect"),
+       {}
+};
+
 static const struct property_entry dwc3_pci_intel_byt_properties[] = {
        PROPERTY_ENTRY_STRING("dr_mode", "peripheral"),
        PROPERTY_ENTRY_BOOL("snps,dis_u2_susphy_quirk"),
@@ -169,6 +176,10 @@ static const struct software_node dwc3_pci_intel_swnode = {
        .properties = dwc3_pci_intel_properties,
 };
 
+static const struct software_node dwc3_pci_intel_phy_charger_detect_swnode = {
+       .properties = dwc3_pci_intel_phy_charger_detect_properties,
+};
+
 static const struct software_node dwc3_pci_intel_byt_swnode = {
        .properties = dwc3_pci_intel_byt_properties,
 };
@@ -185,7 +196,8 @@ static const struct software_node dwc3_pci_amd_mr_swnode = {
        .properties = dwc3_pci_mr_properties,
 };
 
-static int dwc3_pci_quirks(struct dwc3_pci *dwc)
+static int dwc3_pci_quirks(struct dwc3_pci *dwc,
+                          const struct software_node *swnode)
 {
        struct pci_dev                  *pdev = dwc->pci;
 
@@ -239,10 +251,30 @@ static int dwc3_pci_quirks(struct dwc3_pci *dwc)
                                gpiod_put(gpio);
                                usleep_range(10000, 11000);
                        }
+
+                       /*
+                        * Make the pdev name predictable (only 1 DWC3 on BYT)
+                        * and patch the phy dev-name into the lookup table so
+                        * that the phy-driver can get the GPIOs.
+                        */
+                       dwc->dwc3->id = PLATFORM_DEVID_NONE;
+                       platform_bytcr_gpios.dev_id = "dwc3.ulpi";
+
+                       /*
+                        * Some Android tablets with a Crystal Cove PMIC
+                        * (INT33FD), rely on the TUSB1211 phy for charger
+                        * detection. These can be identified by them _not_
+                        * using the standard ACPI battery and ac drivers.
+                        */
+                       if (acpi_dev_present("INT33FD", "1", 2) &&
+                           acpi_quirk_skip_acpi_ac_and_battery()) {
+                               dev_info(&pdev->dev, "Using TUSB1211 phy for charger detection\n");
+                               swnode = &dwc3_pci_intel_phy_charger_detect_swnode;
+                       }
                }
        }
 
-       return 0;
+       return device_add_software_node(&dwc->dwc3->dev, swnode);
 }
 
 #ifdef CONFIG_PM
@@ -307,11 +339,7 @@ static int dwc3_pci_probe(struct pci_dev *pci, const struct pci_device_id *id)
        dwc->dwc3->dev.parent = dev;
        ACPI_COMPANION_SET(&dwc->dwc3->dev, ACPI_COMPANION(dev));
 
-       ret = device_add_software_node(&dwc->dwc3->dev, (void *)id->driver_data);
-       if (ret < 0)
-               goto err;
-
-       ret = dwc3_pci_quirks(dwc);
+       ret = dwc3_pci_quirks(dwc, (void *)id->driver_data);
        if (ret)
                goto err;
 
index 1922fd0..4585ee3 100644 (file)
@@ -919,12 +919,12 @@ static ssize_t __ffs_epfile_read_data(struct ffs_epfile *epfile,
                data_len, ret);
 
        data_len -= ret;
-       buf = kmalloc(sizeof(*buf) + data_len, GFP_KERNEL);
+       buf = kmalloc(struct_size(buf, storage, data_len), GFP_KERNEL);
        if (!buf)
                return -ENOMEM;
        buf->length = data_len;
        buf->data = buf->storage;
-       memcpy(buf->storage, data + ret, data_len);
+       memcpy(buf->storage, data + ret, flex_array_size(buf, storage, data_len));
 
        /*
         * At this point read_buffer is NULL or READ_BUFFER_DROP (if
index 46dd11d..ba899ca 100644 (file)
@@ -1188,6 +1188,8 @@ static int do_read_toc(struct fsg_common *common, struct fsg_buffhd *bh)
        int             msf = common->cmnd[1] & 0x02;
        int             start_track = common->cmnd[6];
        u8              *buf = (u8 *)bh->buf;
+       u8              format;
+       int             i, len;
 
        if ((common->cmnd[1] & ~0x02) != 0 ||   /* Mask away MSF */
                        start_track > 1) {
@@ -1195,18 +1197,62 @@ static int do_read_toc(struct fsg_common *common, struct fsg_buffhd *bh)
                return -EINVAL;
        }
 
-       memset(buf, 0, 20);
-       buf[1] = (20-2);                /* TOC data length */
-       buf[2] = 1;                     /* First track number */
-       buf[3] = 1;                     /* Last track number */
-       buf[5] = 0x16;                  /* Data track, copying allowed */
-       buf[6] = 0x01;                  /* Only track is number 1 */
-       store_cdrom_address(&buf[8], msf, 0);
+       format = common->cmnd[2] & 0xf;
+       /*
+        * Check if CDB is old style SFF-8020i
+        * i.e. format is in 2 MSBs of byte 9
+        * Mac OS-X host sends us this.
+        */
+       if (format == 0)
+               format = (common->cmnd[9] >> 6) & 0x3;
+
+       switch (format) {
+       case 0:
+               /* Formatted TOC */
+               len = 4 + 2*8;          /* 4 byte header + 2 descriptors */
+               memset(buf, 0, len);
+               buf[1] = len - 2;       /* TOC Length excludes length field */
+               buf[2] = 1;             /* First track number */
+               buf[3] = 1;             /* Last track number */
+               buf[5] = 0x16;          /* Data track, copying allowed */
+               buf[6] = 0x01;          /* Only track is number 1 */
+               store_cdrom_address(&buf[8], msf, 0);
+
+               buf[13] = 0x16;         /* Lead-out track is data */
+               buf[14] = 0xAA;         /* Lead-out track number */
+               store_cdrom_address(&buf[16], msf, curlun->num_sectors);
+               return len;
+
+       case 2:
+               /* Raw TOC */
+               len = 4 + 3*11;         /* 4 byte header + 3 descriptors */
+               memset(buf, 0, len);    /* Header + A0, A1 & A2 descriptors */
+               buf[1] = len - 2;       /* TOC Length excludes length field */
+               buf[2] = 1;             /* First complete session */
+               buf[3] = 1;             /* Last complete session */
+
+               buf += 4;
+               /* fill in A0, A1 and A2 points */
+               for (i = 0; i < 3; i++) {
+                       buf[0] = 1;     /* Session number */
+                       buf[1] = 0x16;  /* Data track, copying allowed */
+                       /* 2 - Track number 0 ->  TOC */
+                       buf[3] = 0xA0 + i; /* A0, A1, A2 point */
+                       /* 4, 5, 6 - Min, sec, frame is zero */
+                       buf[8] = 1;     /* Pmin: last track number */
+                       buf += 11;      /* go to next track descriptor */
+               }
+               buf -= 11;              /* go back to A2 descriptor */
 
-       buf[13] = 0x16;                 /* Lead-out track is data */
-       buf[14] = 0xAA;                 /* Lead-out track number */
-       store_cdrom_address(&buf[16], msf, curlun->num_sectors);
-       return 20;
+               /* For A2, 7, 8, 9, 10 - zero, Pmin, Psec, Pframe of Lead out */
+               store_cdrom_address(&buf[7], msf, curlun->num_sectors);
+               return len;
+
+       default:
+               /* Multi-session, PMA, ATIP, CD-TEXT not supported/required */
+               curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+               return -EINVAL;
+       }
 }
 
 static int do_mode_sense(struct fsg_common *common, struct fsg_buffhd *bh)
@@ -1944,7 +1990,7 @@ static int do_scsi_command(struct fsg_common *common)
                common->data_size_from_cmnd =
                        get_unaligned_be16(&common->cmnd[7]);
                reply = check_command(common, 10, DATA_DIR_TO_HOST,
-                                     (7<<6) | (1<<1), 1,
+                                     (0xf<<6) | (3<<1), 1,
                                      "READ TOC");
                if (reply == 0)
                        reply = do_read_toc(common, bh);
index 068ed84..0bebbdf 100644 (file)
@@ -668,10 +668,8 @@ static struct usb_function *phonet_alloc(struct usb_function_instance *fi)
 {
        struct f_phonet *fp;
        struct f_phonet_opts *opts;
-       int size;
 
-       size = sizeof(*fp) + (phonet_rxq_size * sizeof(struct usb_request *));
-       fp = kzalloc(size, GFP_KERNEL);
+       fp = kzalloc(struct_size(fp, out_reqv, phonet_rxq_size), GFP_KERNEL);
        if (!fp)
                return ERR_PTR(-ENOMEM);
 
index 1ed8ff0..a9480b9 100644 (file)
@@ -345,6 +345,10 @@ static void gser_free(struct usb_function *f)
 
 static void gser_unbind(struct usb_configuration *c, struct usb_function *f)
 {
+       struct f_gser   *gser = func_to_gser(f);
+
+       /* Ensure port is disconnected before unbinding */
+       gserial_disconnect(&gser->port);
        usb_free_all_descriptors(f);
 }
 
index 03f5064..6f0e1d8 100644 (file)
@@ -3,6 +3,7 @@
  * f_uac1.c -- USB Audio Class 1.0 Function (using u_audio API)
  *
  * Copyright (C) 2016 Ruslan Bilovol <ruslan.bilovol@gmail.com>
+ * Copyright (C) 2021 Julian Scheel <julian@jusst.de>
  *
  * This driver doesn't expect any real Audio codec to be present
  * on the device - the audio streams are simply sinked to and
@@ -42,6 +43,9 @@ struct f_uac1 {
        /* Interrupt IN endpoint of AC interface */
        struct usb_ep   *int_ep;
        atomic_t        int_count;
+       int ctl_id;             /* EP id */
+       int c_srate;    /* current capture srate */
+       int p_srate;    /* current playback prate */
 };
 
 static inline struct f_uac1 *func_to_uac1(struct usb_function *f)
@@ -188,16 +192,18 @@ static struct uac1_as_header_descriptor as_in_header_desc = {
        .wFormatTag =           cpu_to_le16(UAC_FORMAT_TYPE_I_PCM),
 };
 
-DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1);
+DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(UAC_MAX_RATES);
+#define uac_format_type_i_discrete_descriptor                  \
+       uac_format_type_i_discrete_descriptor_##UAC_MAX_RATES
 
-static struct uac_format_type_i_discrete_descriptor_1 as_out_type_i_desc = {
-       .bLength =              UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1),
+static struct uac_format_type_i_discrete_descriptor as_out_type_i_desc = {
+       .bLength =              0, /* filled on rate setup */
        .bDescriptorType =      USB_DT_CS_INTERFACE,
        .bDescriptorSubtype =   UAC_FORMAT_TYPE,
        .bFormatType =          UAC_FORMAT_TYPE_I,
        .bSubframeSize =        2,
        .bBitResolution =       16,
-       .bSamFreqType =         1,
+       .bSamFreqType =         0, /* filled on rate setup */
 };
 
 /* Standard ISO OUT Endpoint Descriptor */
@@ -221,14 +227,14 @@ static struct uac_iso_endpoint_descriptor as_iso_out_desc = {
        .wLockDelay =           cpu_to_le16(1),
 };
 
-static struct uac_format_type_i_discrete_descriptor_1 as_in_type_i_desc = {
-       .bLength =              UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1),
+static struct uac_format_type_i_discrete_descriptor as_in_type_i_desc = {
+       .bLength =              0, /* filled on rate setup */
        .bDescriptorType =      USB_DT_CS_INTERFACE,
        .bDescriptorSubtype =   UAC_FORMAT_TYPE,
        .bFormatType =          UAC_FORMAT_TYPE_I,
        .bSubframeSize =        2,
        .bBitResolution =       16,
-       .bSamFreqType =         1,
+       .bSamFreqType =         0, /* filled on rate setup */
 };
 
 /* Standard ISO OUT Endpoint Descriptor */
@@ -303,7 +309,7 @@ enum {
 };
 
 static struct usb_string strings_uac1[] = {
-       [STR_AC_IF].s = "AC Interface",
+       /* [STR_AC_IF].s = DYNAMIC, */
        [STR_USB_OUT_IT].s = "Playback Input terminal",
        [STR_USB_OUT_IT_CH_NAMES].s = "Playback Channels",
        [STR_IO_OUT_OT].s = "Playback Output terminal",
@@ -333,6 +339,30 @@ static struct usb_gadget_strings *uac1_strings[] = {
  * This function is an ALSA sound card following USB Audio Class Spec 1.0.
  */
 
+static void uac_cs_attr_sample_rate(struct usb_ep *ep, struct usb_request *req)
+{
+       struct usb_function *fn = ep->driver_data;
+       struct usb_composite_dev *cdev = fn->config->cdev;
+       struct g_audio *agdev = func_to_g_audio(fn);
+       struct f_uac1 *uac1 = func_to_uac1(fn);
+       u8 *buf = (u8 *)req->buf;
+       u32 val = 0;
+
+       if (req->actual != 3) {
+               WARN(cdev, "Invalid data size for UAC_EP_CS_ATTR_SAMPLE_RATE.\n");
+               return;
+       }
+
+       val = buf[0] | (buf[1] << 8) | (buf[2] << 16);
+       if (uac1->ctl_id == (USB_DIR_IN | 2)) {
+               uac1->p_srate = val;
+               u_audio_set_playback_srate(agdev, uac1->p_srate);
+       } else if (uac1->ctl_id == (USB_DIR_OUT | 1)) {
+               uac1->c_srate = val;
+               u_audio_set_capture_srate(agdev, uac1->c_srate);
+       }
+}
+
 static void audio_notify_complete(struct usb_ep *_ep, struct usb_request *req)
 {
        struct g_audio *audio = req->context;
@@ -707,18 +737,27 @@ static int audio_set_endpoint_req(struct usb_function *f,
                const struct usb_ctrlrequest *ctrl)
 {
        struct usb_composite_dev *cdev = f->config->cdev;
+       struct usb_request      *req = f->config->cdev->req;
+       struct f_uac1           *uac1 = func_to_uac1(f);
        int                     value = -EOPNOTSUPP;
        u16                     ep = le16_to_cpu(ctrl->wIndex);
        u16                     len = le16_to_cpu(ctrl->wLength);
        u16                     w_value = le16_to_cpu(ctrl->wValue);
+       u8                      cs = w_value >> 8;
 
        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_SET_CUR:
+       case UAC_SET_CUR: {
+               if (cs == UAC_EP_CS_ATTR_SAMPLE_RATE) {
+                       cdev->gadget->ep0->driver_data = f;
+                       uac1->ctl_id = ep;
+                       req->complete = uac_cs_attr_sample_rate;
+               }
                value = len;
                break;
+       }
 
        case UAC_SET_MIN:
                break;
@@ -743,16 +782,33 @@ static int audio_get_endpoint_req(struct usb_function *f,
                const struct usb_ctrlrequest *ctrl)
 {
        struct usb_composite_dev *cdev = f->config->cdev;
+       struct usb_request *req = f->config->cdev->req;
+       struct f_uac1 *uac1 = func_to_uac1(f);
+       u8 *buf = (u8 *)req->buf;
        int value = -EOPNOTSUPP;
-       u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF);
+       u8 ep = le16_to_cpu(ctrl->wIndex);
        u16 len = le16_to_cpu(ctrl->wLength);
        u16 w_value = le16_to_cpu(ctrl->wValue);
+       u8 cs = w_value >> 8;
+       u32 val = 0;
 
        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:
+       case UAC_GET_CUR: {
+               if (cs == UAC_EP_CS_ATTR_SAMPLE_RATE) {
+                       if (ep == (USB_DIR_IN | 2))
+                               val = uac1->p_srate;
+                       else if (ep == (USB_DIR_OUT | 1))
+                               val = uac1->c_srate;
+                       buf[2] = (val >> 16) & 0xff;
+                       buf[1] = (val >> 8) & 0xff;
+                       buf[0] = val & 0xff;
+               }
+               value = len;
+               break;
+       }
        case UAC_GET_MIN:
        case UAC_GET_MAX:
        case UAC_GET_RES:
@@ -905,6 +961,14 @@ static void f_audio_disable(struct usb_function *f)
                usb_ep_disable(uac1->int_ep);
 }
 
+static void
+f_audio_suspend(struct usb_function *f)
+{
+       struct f_uac1 *uac1 = func_to_uac1(f);
+
+       u_audio_suspend(&uac1->g_audio);
+}
+
 /*-------------------------------------------------------------------------*/
 static struct uac_feature_unit_descriptor *build_fu_desc(int chmask)
 {
@@ -1074,10 +1138,10 @@ static int f_audio_validate_opts(struct g_audio *audio, struct device *dev)
        } else if ((opts->c_ssize < 1) || (opts->c_ssize > 4)) {
                dev_err(dev, "Error: incorrect capture sample size\n");
                return -EINVAL;
-       } else if (!opts->p_srate) {
+       } else if (!opts->p_srates[0]) {
                dev_err(dev, "Error: incorrect playback sampling rate\n");
                return -EINVAL;
-       } else if (!opts->c_srate) {
+       } else if (!opts->c_srates[0]) {
                dev_err(dev, "Error: incorrect capture sampling rate\n");
                return -EINVAL;
        }
@@ -1118,10 +1182,9 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
        struct f_uac1_opts              *audio_opts;
        struct usb_ep                   *ep = NULL;
        struct usb_string               *us;
-       u8                              *sam_freq;
-       int                             rate;
        int                             ba_iface_id;
        int                             status;
+       int                             idx, i;
 
        status = f_audio_validate_opts(audio, dev);
        if (status)
@@ -1129,6 +1192,8 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
 
        audio_opts = container_of(f->fi, struct f_uac1_opts, func_inst);
 
+       strings_uac1[STR_AC_IF].s = audio_opts->function_name;
+
        us = usb_gstrings_attach(cdev, uac1_strings, ARRAY_SIZE(strings_uac1));
        if (IS_ERR(us))
                return PTR_ERR(us);
@@ -1213,12 +1278,25 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
        }
 
        /* Set sample rates */
-       rate = audio_opts->c_srate;
-       sam_freq = as_out_type_i_desc.tSamFreq[0];
-       memcpy(sam_freq, &rate, 3);
-       rate = audio_opts->p_srate;
-       sam_freq = as_in_type_i_desc.tSamFreq[0];
-       memcpy(sam_freq, &rate, 3);
+       for (i = 0, idx = 0; i < UAC_MAX_RATES; i++) {
+               if (audio_opts->c_srates[i] == 0)
+                       break;
+               memcpy(as_out_type_i_desc.tSamFreq[idx++],
+                               &audio_opts->c_srates[i], 3);
+       }
+       as_out_type_i_desc.bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(idx);
+       as_out_type_i_desc.bSamFreqType = idx;
+
+       for (i = 0, idx = 0; i < UAC_MAX_RATES; i++) {
+               if (audio_opts->p_srates[i] == 0)
+                       break;
+               memcpy(as_in_type_i_desc.tSamFreq[idx++],
+                               &audio_opts->p_srates[i], 3);
+       }
+       as_in_type_i_desc.bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(idx);
+       as_in_type_i_desc.bSamFreqType = idx;
+       uac1->p_srate = audio_opts->p_srates[0];
+       uac1->c_srate = audio_opts->c_srates[0];
 
        /* allocate instance-specific interface IDs, and patch descriptors */
        status = usb_interface_id(c, f);
@@ -1297,7 +1375,8 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
        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;
+       memcpy(audio->params.c_srates, audio_opts->c_srates,
+                       sizeof(audio->params.c_srates));
        audio->params.c_ssize = audio_opts->c_ssize;
        if (FUIN_EN(audio_opts)) {
                audio->params.p_fu.id = USB_IN_FU_ID;
@@ -1309,7 +1388,8 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
                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;
+       memcpy(audio->params.p_srates, audio_opts->p_srates,
+                       sizeof(audio->params.p_srates));
        audio->params.p_ssize = audio_opts->p_ssize;
        if (FUOUT_EN(audio_opts)) {
                audio->params.c_fu.id = USB_OUT_FU_ID;
@@ -1414,11 +1494,106 @@ end:                                                                   \
                                                                        \
 CONFIGFS_ATTR(f_uac1_opts_, name)
 
+#define UAC1_RATE_ATTRIBUTE(name)                                      \
+static ssize_t f_uac1_opts_##name##_show(struct config_item *item,     \
+                                        char *page)                    \
+{                                                                      \
+       struct f_uac1_opts *opts = to_f_uac1_opts(item);                \
+       int result = 0;                                                 \
+       int i;                                                          \
+                                                                       \
+       mutex_lock(&opts->lock);                                        \
+       page[0] = '\0';                                                 \
+       for (i = 0; i < UAC_MAX_RATES; i++) {                           \
+               if (opts->name##s[i] == 0)                              \
+                       break;                                          \
+               result += sprintf(page + strlen(page), "%u,",           \
+                               opts->name##s[i]);                      \
+       }                                                               \
+       if (strlen(page) > 0)                                           \
+               page[strlen(page) - 1] = '\n';                          \
+       mutex_unlock(&opts->lock);                                      \
+                                                                       \
+       return result;                                                  \
+}                                                                      \
+                                                                       \
+static ssize_t f_uac1_opts_##name##_store(struct config_item *item,    \
+                                         const char *page, size_t len) \
+{                                                                      \
+       struct f_uac1_opts *opts = to_f_uac1_opts(item);                \
+       char *split_page = NULL;                                        \
+       int ret = -EINVAL;                                              \
+       char *token;                                                    \
+       u32 num;                                                        \
+       int i;                                                          \
+                                                                       \
+       mutex_lock(&opts->lock);                                        \
+       if (opts->refcnt) {                                             \
+               ret = -EBUSY;                                           \
+               goto end;                                               \
+       }                                                               \
+                                                                       \
+       i = 0;                                                          \
+       memset(opts->name##s, 0x00, sizeof(opts->name##s));             \
+       split_page = kstrdup(page, GFP_KERNEL);                         \
+       while ((token = strsep(&split_page, ",")) != NULL) {            \
+               ret = kstrtou32(token, 0, &num);                        \
+               if (ret)                                                \
+                       goto end;                                       \
+                                                                       \
+               opts->name##s[i++] = num;                               \
+               ret = len;                                              \
+       };                                                              \
+                                                                       \
+end:                                                                   \
+       kfree(split_page);                                              \
+       mutex_unlock(&opts->lock);                                      \
+       return ret;                                                     \
+}                                                                      \
+                                                                       \
+CONFIGFS_ATTR(f_uac1_opts_, name)
+
+#define UAC1_ATTRIBUTE_STRING(name)                                    \
+static ssize_t f_uac1_opts_##name##_show(struct config_item *item,     \
+                                        char *page)                    \
+{                                                                      \
+       struct f_uac1_opts *opts = to_f_uac1_opts(item);                \
+       int result;                                                     \
+                                                                       \
+       mutex_lock(&opts->lock);                                        \
+       result = snprintf(page, sizeof(opts->name), "%s", opts->name);  \
+       mutex_unlock(&opts->lock);                                      \
+                                                                       \
+       return result;                                                  \
+}                                                                      \
+                                                                       \
+static ssize_t f_uac1_opts_##name##_store(struct config_item *item,    \
+                                         const char *page, size_t len) \
+{                                                                      \
+       struct f_uac1_opts *opts = to_f_uac1_opts(item);                \
+       int ret = 0;                                                    \
+                                                                       \
+       mutex_lock(&opts->lock);                                        \
+       if (opts->refcnt) {                                             \
+               ret = -EBUSY;                                           \
+               goto end;                                               \
+       }                                                               \
+                                                                       \
+       ret = snprintf(opts->name, min(sizeof(opts->name), len),        \
+                       "%s", page);                                    \
+                                                                       \
+end:                                                                   \
+       mutex_unlock(&opts->lock);                                      \
+       return ret;                                                     \
+}                                                                      \
+                                                                       \
+CONFIGFS_ATTR(f_uac1_opts_, name)
+
 UAC1_ATTRIBUTE(u32, c_chmask);
-UAC1_ATTRIBUTE(u32, c_srate);
+UAC1_RATE_ATTRIBUTE(c_srate);
 UAC1_ATTRIBUTE(u32, c_ssize);
 UAC1_ATTRIBUTE(u32, p_chmask);
-UAC1_ATTRIBUTE(u32, p_srate);
+UAC1_RATE_ATTRIBUTE(p_srate);
 UAC1_ATTRIBUTE(u32, p_ssize);
 UAC1_ATTRIBUTE(u32, req_number);
 
@@ -1433,6 +1608,7 @@ UAC1_ATTRIBUTE(bool, c_volume_present);
 UAC1_ATTRIBUTE(s16, c_volume_min);
 UAC1_ATTRIBUTE(s16, c_volume_max);
 UAC1_ATTRIBUTE(s16, c_volume_res);
+UAC1_ATTRIBUTE_STRING(function_name);
 
 static struct configfs_attribute *f_uac1_attrs[] = {
        &f_uac1_opts_attr_c_chmask,
@@ -1455,6 +1631,8 @@ static struct configfs_attribute *f_uac1_attrs[] = {
        &f_uac1_opts_attr_c_volume_max,
        &f_uac1_opts_attr_c_volume_res,
 
+       &f_uac1_opts_attr_function_name,
+
        NULL,
 };
 
@@ -1487,10 +1665,10 @@ static struct usb_function_instance *f_audio_alloc_inst(void)
                                    &f_uac1_func_type);
 
        opts->c_chmask = UAC1_DEF_CCHMASK;
-       opts->c_srate = UAC1_DEF_CSRATE;
+       opts->c_srates[0] = UAC1_DEF_CSRATE;
        opts->c_ssize = UAC1_DEF_CSSIZE;
        opts->p_chmask = UAC1_DEF_PCHMASK;
-       opts->p_srate = UAC1_DEF_PSRATE;
+       opts->p_srates[0] = UAC1_DEF_PSRATE;
        opts->p_ssize = UAC1_DEF_PSSIZE;
 
        opts->p_mute_present = UAC1_DEF_MUTE_PRESENT;
@@ -1506,6 +1684,9 @@ static struct usb_function_instance *f_audio_alloc_inst(void)
        opts->c_volume_res = UAC1_DEF_RES_DB;
 
        opts->req_number = UAC1_DEF_REQ_NUM;
+
+       snprintf(opts->function_name, sizeof(opts->function_name), "AC Interface");
+
        return &opts->func_inst;
 }
 
@@ -1562,6 +1743,7 @@ static struct usb_function *f_audio_alloc(struct usb_function_instance *fi)
        uac1->g_audio.func.get_alt = f_audio_get_alt;
        uac1->g_audio.func.setup = f_audio_setup;
        uac1->g_audio.func.disable = f_audio_disable;
+       uac1->g_audio.func.suspend = f_audio_suspend;
        uac1->g_audio.func.free_func = f_audio_free;
 
        return &uac1->g_audio.func;
index 097a709..1905a8d 100644 (file)
@@ -70,6 +70,8 @@ struct f_uac2 {
        /* Interrupt IN endpoint of AC interface */
        struct usb_ep   *int_ep;
        atomic_t        int_count;
+       /* transient state, only valid during handling of a single control request */
+       int clock_id;
 };
 
 static inline struct f_uac2 *func_to_uac2(struct usb_function *f)
@@ -104,14 +106,11 @@ enum {
        STR_AS_IN_ALT1,
 };
 
-static char clksrc_in[8];
-static char clksrc_out[8];
-
 static struct usb_string strings_fn[] = {
-       [STR_ASSOC].s = "Source/Sink",
+       /* [STR_ASSOC].s = DYNAMIC, */
        [STR_IF_CTRL].s = "Topology Control",
-       [STR_CLKSRC_IN].s = clksrc_in,
-       [STR_CLKSRC_OUT].s = clksrc_out,
+       [STR_CLKSRC_IN].s = "Input Clock",
+       [STR_CLKSRC_OUT].s = "Output Clock",
        [STR_USB_IT].s = "USBH Out",
        [STR_IO_IT].s = "USBD Out",
        [STR_USB_OT].s = "USBH In",
@@ -125,6 +124,16 @@ static struct usb_string strings_fn[] = {
        { },
 };
 
+static const char *const speed_names[] = {
+       [USB_SPEED_UNKNOWN] = "UNKNOWN",
+       [USB_SPEED_LOW] = "LS",
+       [USB_SPEED_FULL] = "FS",
+       [USB_SPEED_HIGH] = "HS",
+       [USB_SPEED_WIRELESS] = "W",
+       [USB_SPEED_SUPER] = "SS",
+       [USB_SPEED_SUPER_PLUS] = "SS+",
+};
+
 static struct usb_gadget_strings str_fn = {
        .language = 0x0409,     /* en-us */
        .strings = strings_fn,
@@ -166,7 +175,7 @@ static struct uac_clock_source_descriptor in_clk_src_desc = {
        .bDescriptorSubtype = UAC2_CLOCK_SOURCE,
        /* .bClockID = DYNAMIC */
        .bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED,
-       .bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL),
+       .bmControls = (CONTROL_RDWR << CLK_FREQ_CTRL),
        .bAssocTerminal = 0,
 };
 
@@ -178,7 +187,7 @@ static struct uac_clock_source_descriptor out_clk_src_desc = {
        .bDescriptorSubtype = UAC2_CLOCK_SOURCE,
        /* .bClockID = DYNAMIC */
        .bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED,
-       .bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL),
+       .bmControls = (CONTROL_RDWR << CLK_FREQ_CTRL),
        .bAssocTerminal = 0,
 };
 
@@ -344,7 +353,7 @@ static struct usb_endpoint_descriptor hs_epout_desc = {
 
        /* .bmAttributes = DYNAMIC */
        /* .wMaxPacketSize = DYNAMIC */
-       .bInterval = 4,
+       /* .bInterval = DYNAMIC */
 };
 
 static struct usb_endpoint_descriptor ss_epout_desc = {
@@ -354,7 +363,7 @@ static struct usb_endpoint_descriptor ss_epout_desc = {
        .bEndpointAddress = USB_DIR_OUT,
        /* .bmAttributes = DYNAMIC */
        /* .wMaxPacketSize = DYNAMIC */
-       .bInterval = 4,
+       /* .bInterval = DYNAMIC */
 };
 
 static struct usb_ss_ep_comp_descriptor ss_epout_desc_comp = {
@@ -478,7 +487,7 @@ static struct usb_endpoint_descriptor hs_epin_desc = {
 
        .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
        /* .wMaxPacketSize = DYNAMIC */
-       .bInterval = 4,
+       /* .bInterval = DYNAMIC */
 };
 
 static struct usb_endpoint_descriptor ss_epin_desc = {
@@ -488,7 +497,7 @@ static struct usb_endpoint_descriptor ss_epin_desc = {
        .bEndpointAddress = USB_DIR_IN,
        .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
        /* .wMaxPacketSize = DYNAMIC */
-       .bInterval = 4,
+       /* .bInterval = DYNAMIC */
 };
 
 static struct usb_ss_ep_comp_descriptor ss_epin_desc_comp = {
@@ -634,44 +643,50 @@ struct cntrl_cur_lay3 {
        __le32  dCUR;
 };
 
-struct cntrl_range_lay3 {
-       __le16  wNumSubRanges;
+struct cntrl_subrange_lay3 {
        __le32  dMIN;
        __le32  dMAX;
        __le32  dRES;
 } __packed;
 
-static int set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts,
-       struct usb_endpoint_descriptor *ep_desc,
-       enum usb_device_speed speed, bool is_playback)
-{
-       int chmask, srate, ssize;
-       u16 max_size_bw, max_size_ep;
-       unsigned int factor;
+#define ranges_lay3_size(c) (sizeof(c.wNumSubRanges)   \
+               + le16_to_cpu(c.wNumSubRanges)          \
+               * sizeof(struct cntrl_subrange_lay3))
 
-       switch (speed) {
-       case USB_SPEED_FULL:
-               max_size_ep = 1023;
-               factor = 1000;
-               break;
+#define DECLARE_UAC2_CNTRL_RANGES_LAY3(k, n)           \
+       struct cntrl_ranges_lay3_##k {                  \
+       __le16  wNumSubRanges;                          \
+       struct cntrl_subrange_lay3 r[n];                \
+} __packed
 
-       case USB_SPEED_HIGH:
-       case USB_SPEED_SUPER:
-               max_size_ep = 1024;
-               factor = 8000;
-               break;
+DECLARE_UAC2_CNTRL_RANGES_LAY3(srates, UAC_MAX_RATES);
 
-       default:
-               return -EINVAL;
+static int get_max_srate(const int *srates)
+{
+       int i, max_srate = 0;
+
+       for (i = 0; i < UAC_MAX_RATES; i++) {
+               if (srates[i] == 0)
+                       break;
+               if (srates[i] > max_srate)
+                       max_srate = srates[i];
        }
+       return max_srate;
+}
+
+static int get_max_bw_for_bint(const struct f_uac2_opts *uac2_opts,
+       u8 bint, unsigned int factor, bool is_playback)
+{
+       int chmask, srate, ssize;
+       u16 max_size_bw;
 
        if (is_playback) {
                chmask = uac2_opts->p_chmask;
-               srate = uac2_opts->p_srate;
+               srate = get_max_srate(uac2_opts->p_srates);
                ssize = uac2_opts->p_ssize;
        } else {
                chmask = uac2_opts->c_chmask;
-               srate = uac2_opts->c_srate;
+               srate = get_max_srate(uac2_opts->c_srates);
                ssize = uac2_opts->c_ssize;
        }
 
@@ -681,14 +696,76 @@ static int set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts,
                srate = srate * (1000 + uac2_opts->fb_max) / 1000;
                // updated srate is always bigger, therefore DIV_ROUND_UP always yields +1
                max_size_bw = num_channels(chmask) * ssize *
-                       (DIV_ROUND_UP(srate, factor / (1 << (ep_desc->bInterval - 1))));
+                       (DIV_ROUND_UP(srate, factor / (1 << (bint - 1))));
        } else {
                // adding 1 frame provision for Win10
                max_size_bw = num_channels(chmask) * ssize *
-                       (DIV_ROUND_UP(srate, factor / (1 << (ep_desc->bInterval - 1))) + 1);
+                       (DIV_ROUND_UP(srate, factor / (1 << (bint - 1))) + 1);
        }
-       ep_desc->wMaxPacketSize = cpu_to_le16(min_t(u16, max_size_bw,
-                                                   max_size_ep));
+       return max_size_bw;
+}
+
+static int set_ep_max_packet_size_bint(struct device *dev, const struct f_uac2_opts *uac2_opts,
+       struct usb_endpoint_descriptor *ep_desc,
+       enum usb_device_speed speed, bool is_playback)
+{
+       u16 max_size_bw, max_size_ep;
+       u8 bint, opts_bint;
+       char *dir;
+
+       switch (speed) {
+       case USB_SPEED_FULL:
+               max_size_ep = 1023;
+               // fixed
+               bint = ep_desc->bInterval;
+               max_size_bw = get_max_bw_for_bint(uac2_opts, bint, 1000, is_playback);
+               break;
+
+       case USB_SPEED_HIGH:
+       case USB_SPEED_SUPER:
+               max_size_ep = 1024;
+               if (is_playback)
+                       opts_bint = uac2_opts->p_hs_bint;
+               else
+                       opts_bint = uac2_opts->c_hs_bint;
+
+               if (opts_bint > 0) {
+                       /* fixed bint */
+                       bint = opts_bint;
+                       max_size_bw = get_max_bw_for_bint(uac2_opts, bint, 8000, is_playback);
+               } else {
+                       /* checking bInterval from 4 to 1 whether the required bandwidth fits */
+                       for (bint = 4; bint > 0; --bint) {
+                               max_size_bw = get_max_bw_for_bint(
+                                       uac2_opts, bint, 8000, is_playback);
+                               if (max_size_bw <= max_size_ep)
+                                       break;
+                       }
+               }
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       if (is_playback)
+               dir = "Playback";
+       else
+               dir = "Capture";
+
+       if (max_size_bw <= max_size_ep)
+               dev_dbg(dev,
+                       "%s %s: Would use wMaxPacketSize %d and bInterval %d\n",
+                       speed_names[speed], dir, max_size_bw, bint);
+       else {
+               dev_warn(dev,
+                       "%s %s: Req. wMaxPacketSize %d at bInterval %d > max ISOC %d, may drop data!\n",
+                       speed_names[speed], dir, max_size_bw, bint, max_size_ep);
+               max_size_bw = max_size_ep;
+       }
+
+       ep_desc->wMaxPacketSize = cpu_to_le16(max_size_bw);
+       ep_desc->bInterval = bint;
 
        return 0;
 }
@@ -896,50 +973,45 @@ static void setup_descriptor(struct f_uac2_opts *opts)
 static int afunc_validate_opts(struct g_audio *agdev, struct device *dev)
 {
        struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev);
-
-       if (!opts->p_chmask && !opts->c_chmask) {
-               dev_err(dev, "Error: no playback and capture channels\n");
-               return -EINVAL;
-       } else if (opts->p_chmask & ~UAC2_CHANNEL_MASK) {
-               dev_err(dev, "Error: unsupported playback channels mask\n");
-               return -EINVAL;
-       } else if (opts->c_chmask & ~UAC2_CHANNEL_MASK) {
-               dev_err(dev, "Error: unsupported capture channels mask\n");
-               return -EINVAL;
-       } else if ((opts->p_ssize < 1) || (opts->p_ssize > 4)) {
-               dev_err(dev, "Error: incorrect playback sample size\n");
-               return -EINVAL;
-       } else if ((opts->c_ssize < 1) || (opts->c_ssize > 4)) {
-               dev_err(dev, "Error: incorrect capture sample size\n");
-               return -EINVAL;
-       } else if (!opts->p_srate) {
-               dev_err(dev, "Error: incorrect playback sampling rate\n");
+       const char *msg = NULL;
+
+       if (!opts->p_chmask && !opts->c_chmask)
+               msg = "no playback and capture channels";
+       else if (opts->p_chmask & ~UAC2_CHANNEL_MASK)
+               msg = "unsupported playback channels mask";
+       else if (opts->c_chmask & ~UAC2_CHANNEL_MASK)
+               msg = "unsupported capture channels mask";
+       else if ((opts->p_ssize < 1) || (opts->p_ssize > 4))
+               msg = "incorrect playback sample size";
+       else if ((opts->c_ssize < 1) || (opts->c_ssize > 4))
+               msg = "incorrect capture sample size";
+       else if (!opts->p_srates[0])
+               msg = "incorrect playback sampling rate";
+       else if (!opts->c_srates[0])
+               msg = "incorrect capture sampling rate";
+
+       else if (opts->p_volume_max <= opts->p_volume_min)
+               msg = "incorrect playback volume max/min";
+       else if (opts->c_volume_max <= opts->c_volume_min)
+               msg = "incorrect capture volume max/min";
+       else if (opts->p_volume_res <= 0)
+               msg = "negative/zero playback volume resolution";
+       else if (opts->c_volume_res <= 0)
+               msg = "negative/zero capture volume resolution";
+
+       else if ((opts->p_volume_max - opts->p_volume_min) % opts->p_volume_res)
+               msg = "incorrect playback volume resolution";
+       else if ((opts->c_volume_max - opts->c_volume_min) % opts->c_volume_res)
+               msg = "incorrect capture volume resolution";
+
+       else if ((opts->p_hs_bint < 0) || (opts->p_hs_bint > 4))
+               msg = "incorrect playback HS/SS bInterval (1-4: fixed, 0: auto)";
+       else if ((opts->c_hs_bint < 0) || (opts->c_hs_bint > 4))
+               msg = "incorrect capture HS/SS bInterval (1-4: fixed, 0: auto)";
+
+       if (msg) {
+               dev_err(dev, "Error: %s\n", msg);
                return -EINVAL;
-       } else if (!opts->c_srate) {
-               dev_err(dev, "Error: incorrect capture sampling rate\n");
-               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;
@@ -961,6 +1033,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
        if (ret)
                return ret;
 
+       strings_fn[STR_ASSOC].s = uac2_opts->function_name;
+
        us = usb_gstrings_attach(cdev, fn_strings, ARRAY_SIZE(strings_fn));
        if (IS_ERR(us))
                return PTR_ERR(us);
@@ -1037,9 +1111,6 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
                *bma = cpu_to_le32(control);
        }
 
-       snprintf(clksrc_in, sizeof(clksrc_in), "%uHz", uac2_opts->p_srate);
-       snprintf(clksrc_out, sizeof(clksrc_out), "%uHz", uac2_opts->c_srate);
-
        ret = usb_interface_id(cfg, fn);
        if (ret < 0) {
                dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
@@ -1103,44 +1174,49 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
                std_ac_if_desc.bNumEndpoints = 1;
        }
 
+       hs_epin_desc.bInterval = uac2_opts->p_hs_bint;
+       ss_epin_desc.bInterval = uac2_opts->p_hs_bint;
+       hs_epout_desc.bInterval = uac2_opts->c_hs_bint;
+       ss_epout_desc.bInterval = uac2_opts->c_hs_bint;
+
        /* Calculate wMaxPacketSize according to audio bandwidth */
-       ret = set_ep_max_packet_size(uac2_opts, &fs_epin_desc, USB_SPEED_FULL,
-                                    true);
+       ret = set_ep_max_packet_size_bint(dev, uac2_opts, &fs_epin_desc,
+                                       USB_SPEED_FULL, true);
        if (ret < 0) {
                dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
                return ret;
        }
 
-       ret = set_ep_max_packet_size(uac2_opts, &fs_epout_desc, USB_SPEED_FULL,
-                                    false);
+       ret = set_ep_max_packet_size_bint(dev, uac2_opts, &fs_epout_desc,
+                                       USB_SPEED_FULL, false);
        if (ret < 0) {
                dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
                return ret;
        }
 
-       ret = set_ep_max_packet_size(uac2_opts, &hs_epin_desc, USB_SPEED_HIGH,
-                                    true);
+       ret = set_ep_max_packet_size_bint(dev, uac2_opts, &hs_epin_desc,
+                                       USB_SPEED_HIGH, true);
        if (ret < 0) {
                dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
                return ret;
        }
 
-       ret = set_ep_max_packet_size(uac2_opts, &hs_epout_desc, USB_SPEED_HIGH,
-                                    false);
+       ret = set_ep_max_packet_size_bint(dev, uac2_opts, &hs_epout_desc,
+                                       USB_SPEED_HIGH, false);
        if (ret < 0) {
                dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
                return ret;
        }
 
-       ret = set_ep_max_packet_size(uac2_opts, &ss_epin_desc, USB_SPEED_SUPER,
-                                    true);
+       ret = set_ep_max_packet_size_bint(dev, uac2_opts, &ss_epin_desc,
+                                       USB_SPEED_SUPER, true);
        if (ret < 0) {
                dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
                return ret;
        }
 
-       ret = set_ep_max_packet_size(uac2_opts, &ss_epout_desc, USB_SPEED_SUPER,
-                                    false);
+       ret = set_ep_max_packet_size_bint(dev, uac2_opts, &ss_epout_desc,
+                                       USB_SPEED_SUPER, false);
        if (ret < 0) {
                dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
                return ret;
@@ -1209,7 +1285,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
        agdev->gadget = gadget;
 
        agdev->params.p_chmask = uac2_opts->p_chmask;
-       agdev->params.p_srate = uac2_opts->p_srate;
+       memcpy(agdev->params.p_srates, uac2_opts->p_srates,
+                       sizeof(agdev->params.p_srates));
        agdev->params.p_ssize = uac2_opts->p_ssize;
        if (FUIN_EN(uac2_opts)) {
                agdev->params.p_fu.id = USB_IN_FU_ID;
@@ -1220,7 +1297,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
                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;
+       memcpy(agdev->params.c_srates, uac2_opts->c_srates,
+                       sizeof(agdev->params.c_srates));
        agdev->params.c_ssize = uac2_opts->c_ssize;
        if (FUOUT_EN(uac2_opts)) {
                agdev->params.c_fu.id = USB_OUT_FU_ID;
@@ -1411,6 +1489,14 @@ afunc_disable(struct usb_function *fn)
                usb_ep_disable(uac2->int_ep);
 }
 
+static void
+afunc_suspend(struct usb_function *fn)
+{
+       struct f_uac2 *uac2 = func_to_uac2(fn);
+
+       u_audio_suspend(&uac2->g_audio);
+}
+
 static int
 in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
 {
@@ -1423,10 +1509,10 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
        u8 entity_id = (w_index >> 8) & 0xff;
        u8 control_selector = w_value >> 8;
        int value = -EOPNOTSUPP;
-       int p_srate, c_srate;
+       u32 p_srate, c_srate;
 
-       p_srate = opts->p_srate;
-       c_srate = opts->c_srate;
+       u_audio_get_playback_srate(agdev, &p_srate);
+       u_audio_get_capture_srate(agdev, &c_srate);
 
        if ((entity_id == USB_IN_CLK_ID) || (entity_id == USB_OUT_CLK_ID)) {
                if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
@@ -1500,28 +1586,39 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr)
        u8 entity_id = (w_index >> 8) & 0xff;
        u8 control_selector = w_value >> 8;
        int value = -EOPNOTSUPP;
-       int p_srate, c_srate;
-
-       p_srate = opts->p_srate;
-       c_srate = opts->c_srate;
 
        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;
+                       struct cntrl_ranges_lay3_srates rs;
+                       int i;
+                       int wNumSubRanges = 0;
+                       int srate;
+                       int *srates;
 
                        if (entity_id == USB_IN_CLK_ID)
-                               r.dMIN = cpu_to_le32(p_srate);
+                               srates = opts->p_srates;
                        else if (entity_id == USB_OUT_CLK_ID)
-                               r.dMIN = cpu_to_le32(c_srate);
+                               srates = opts->c_srates;
                        else
                                return -EOPNOTSUPP;
-
-                       r.dMAX = r.dMIN;
-                       r.dRES = 0;
-                       r.wNumSubRanges = cpu_to_le16(1);
-
-                       value = min_t(unsigned int, w_length, sizeof(r));
-                       memcpy(req->buf, &r, value);
+                       for (i = 0; i < UAC_MAX_RATES; i++) {
+                               srate = srates[i];
+                               if (srate == 0)
+                                       break;
+
+                               rs.r[wNumSubRanges].dMIN = cpu_to_le32(srate);
+                               rs.r[wNumSubRanges].dMAX = cpu_to_le32(srate);
+                               rs.r[wNumSubRanges].dRES = 0;
+                               wNumSubRanges++;
+                               dev_dbg(&agdev->gadget->dev,
+                                       "%s(): clk %d: rate ID %d: %d\n",
+                                       __func__, entity_id, wNumSubRanges, srate);
+                       }
+                       rs.wNumSubRanges = cpu_to_le16(wNumSubRanges);
+                       value = min_t(unsigned int, w_length, ranges_lay3_size(rs));
+                       dev_dbg(&agdev->gadget->dev, "%s(): sending %d rates, size %d\n",
+                               __func__, rs.wNumSubRanges, value);
+                       memcpy(req->buf, &rs, value);
                } else {
                        dev_err(&agdev->gadget->dev,
                                "%s:%d control_selector=%d TODO!\n",
@@ -1580,6 +1677,25 @@ ac_rq_in(struct usb_function *fn, const struct usb_ctrlrequest *cr)
                return -EOPNOTSUPP;
 }
 
+static void uac2_cs_control_sam_freq(struct usb_ep *ep, struct usb_request *req)
+{
+       struct usb_function *fn = ep->driver_data;
+       struct g_audio *agdev = func_to_g_audio(fn);
+       struct f_uac2 *uac2 = func_to_uac2(fn);
+       u32 val;
+
+       if (req->actual != 4)
+               return;
+
+       val = le32_to_cpu(*((__le32 *)req->buf));
+       dev_dbg(&agdev->gadget->dev, "%s val: %d.\n", __func__, val);
+       if (uac2->clock_id == USB_IN_CLK_ID) {
+               u_audio_set_playback_srate(agdev, val);
+       } else if (uac2->clock_id == USB_OUT_CLK_ID) {
+               u_audio_set_capture_srate(agdev, val);
+       }
+}
+
 static void
 out_rq_cur_complete(struct usb_ep *ep, struct usb_request *req)
 {
@@ -1631,6 +1747,7 @@ out_rq_cur_complete(struct usb_ep *ep, struct usb_request *req)
 static int
 out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
 {
+       struct usb_composite_dev *cdev = fn->config->cdev;
        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);
@@ -1640,10 +1757,17 @@ out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
        u16 w_value = le16_to_cpu(cr->wValue);
        u8 entity_id = (w_index >> 8) & 0xff;
        u8 control_selector = w_value >> 8;
+       u8 clock_id = w_index >> 8;
 
        if ((entity_id == USB_IN_CLK_ID) || (entity_id == USB_OUT_CLK_ID)) {
-               if (control_selector == UAC2_CS_CONTROL_SAM_FREQ)
+               if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
+                       dev_dbg(&agdev->gadget->dev,
+                               "control_selector UAC2_CS_CONTROL_SAM_FREQ, clock: %d\n", clock_id);
+                       cdev->gadget->ep0->driver_data = fn;
+                       uac2->clock_id = clock_id;
+                       req->complete = 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));
@@ -1731,10 +1855,12 @@ static struct configfs_item_operations f_uac2_item_ops = {
        .release        = f_uac2_attr_release,
 };
 
+#define uac2_kstrtou8 kstrtou8
 #define uac2_kstrtou32 kstrtou32
 #define uac2_kstrtos16 kstrtos16
 #define uac2_kstrtobool(s, base, res) kstrtobool((s), (res))
 
+static const char *u8_fmt = "%u\n";
 static const char *u32_fmt = "%u\n";
 static const char *s16_fmt = "%hd\n";
 static const char *bool_fmt = "%u\n";
@@ -1836,13 +1962,110 @@ end:                                                                   \
                                                                        \
 CONFIGFS_ATTR(f_uac2_opts_, name)
 
+#define UAC2_RATE_ATTRIBUTE(name)                                      \
+static ssize_t f_uac2_opts_##name##_show(struct config_item *item,     \
+                                        char *page)                    \
+{                                                                      \
+       struct f_uac2_opts *opts = to_f_uac2_opts(item);                \
+       int result = 0;                                                 \
+       int i;                                                          \
+                                                                       \
+       mutex_lock(&opts->lock);                                        \
+       page[0] = '\0';                                                 \
+       for (i = 0; i < UAC_MAX_RATES; i++) {                           \
+               if (opts->name##s[i] == 0)                              \
+                       break;                                          \
+               result += sprintf(page + strlen(page), "%u,",           \
+                               opts->name##s[i]);                      \
+       }                                                               \
+       if (strlen(page) > 0)                                           \
+               page[strlen(page) - 1] = '\n';                          \
+       mutex_unlock(&opts->lock);                                      \
+                                                                       \
+       return result;                                                  \
+}                                                                      \
+                                                                       \
+static ssize_t f_uac2_opts_##name##_store(struct config_item *item,    \
+                                         const char *page, size_t len) \
+{                                                                      \
+       struct f_uac2_opts *opts = to_f_uac2_opts(item);                \
+       char *split_page = NULL;                                        \
+       int ret = -EINVAL;                                              \
+       char *token;                                                    \
+       u32 num;                                                        \
+       int i;                                                          \
+                                                                       \
+       mutex_lock(&opts->lock);                                        \
+       if (opts->refcnt) {                                             \
+               ret = -EBUSY;                                           \
+               goto end;                                               \
+       }                                                               \
+                                                                       \
+       i = 0;                                                          \
+       memset(opts->name##s, 0x00, sizeof(opts->name##s));             \
+       split_page = kstrdup(page, GFP_KERNEL);                         \
+       while ((token = strsep(&split_page, ",")) != NULL) {            \
+               ret = kstrtou32(token, 0, &num);                        \
+               if (ret)                                                \
+                       goto end;                                       \
+                                                                       \
+               opts->name##s[i++] = num;                               \
+               ret = len;                                              \
+       };                                                              \
+                                                                       \
+end:                                                                   \
+       kfree(split_page);                                              \
+       mutex_unlock(&opts->lock);                                      \
+       return ret;                                                     \
+}                                                                      \
+                                                                       \
+CONFIGFS_ATTR(f_uac2_opts_, name)
+
+#define UAC2_ATTRIBUTE_STRING(name)                                    \
+static ssize_t f_uac2_opts_##name##_show(struct config_item *item,     \
+                                        char *page)                    \
+{                                                                      \
+       struct f_uac2_opts *opts = to_f_uac2_opts(item);                \
+       int result;                                                     \
+                                                                       \
+       mutex_lock(&opts->lock);                                        \
+       result = snprintf(page, sizeof(opts->name), "%s", opts->name);  \
+       mutex_unlock(&opts->lock);                                      \
+                                                                       \
+       return result;                                                  \
+}                                                                      \
+                                                                       \
+static ssize_t f_uac2_opts_##name##_store(struct config_item *item,    \
+                                         const char *page, size_t len) \
+{                                                                      \
+       struct f_uac2_opts *opts = to_f_uac2_opts(item);                \
+       int ret = 0;                                                    \
+                                                                       \
+       mutex_lock(&opts->lock);                                        \
+       if (opts->refcnt) {                                             \
+               ret = -EBUSY;                                           \
+               goto end;                                               \
+       }                                                               \
+                                                                       \
+       ret = snprintf(opts->name, min(sizeof(opts->name), len),        \
+                       "%s", page);                                    \
+                                                                       \
+end:                                                                   \
+       mutex_unlock(&opts->lock);                                      \
+       return ret;                                                     \
+}                                                                      \
+                                                                       \
+CONFIGFS_ATTR(f_uac2_opts_, name)
+
 UAC2_ATTRIBUTE(u32, p_chmask);
-UAC2_ATTRIBUTE(u32, p_srate);
+UAC2_RATE_ATTRIBUTE(p_srate);
 UAC2_ATTRIBUTE(u32, p_ssize);
+UAC2_ATTRIBUTE(u8, p_hs_bint);
 UAC2_ATTRIBUTE(u32, c_chmask);
-UAC2_ATTRIBUTE(u32, c_srate);
+UAC2_RATE_ATTRIBUTE(c_srate);
 UAC2_ATTRIBUTE_SYNC(c_sync);
 UAC2_ATTRIBUTE(u32, c_ssize);
+UAC2_ATTRIBUTE(u8, c_hs_bint);
 UAC2_ATTRIBUTE(u32, req_number);
 
 UAC2_ATTRIBUTE(bool, p_mute_present);
@@ -1857,14 +2080,17 @@ UAC2_ATTRIBUTE(s16, c_volume_min);
 UAC2_ATTRIBUTE(s16, c_volume_max);
 UAC2_ATTRIBUTE(s16, c_volume_res);
 UAC2_ATTRIBUTE(u32, fb_max);
+UAC2_ATTRIBUTE_STRING(function_name);
 
 static struct configfs_attribute *f_uac2_attrs[] = {
        &f_uac2_opts_attr_p_chmask,
        &f_uac2_opts_attr_p_srate,
        &f_uac2_opts_attr_p_ssize,
+       &f_uac2_opts_attr_p_hs_bint,
        &f_uac2_opts_attr_c_chmask,
        &f_uac2_opts_attr_c_srate,
        &f_uac2_opts_attr_c_ssize,
+       &f_uac2_opts_attr_c_hs_bint,
        &f_uac2_opts_attr_c_sync,
        &f_uac2_opts_attr_req_number,
        &f_uac2_opts_attr_fb_max,
@@ -1881,6 +2107,8 @@ static struct configfs_attribute *f_uac2_attrs[] = {
        &f_uac2_opts_attr_c_volume_max,
        &f_uac2_opts_attr_c_volume_res,
 
+       &f_uac2_opts_attr_function_name,
+
        NULL,
 };
 
@@ -1913,11 +2141,13 @@ static struct usb_function_instance *afunc_alloc_inst(void)
                                    &f_uac2_func_type);
 
        opts->p_chmask = UAC2_DEF_PCHMASK;
-       opts->p_srate = UAC2_DEF_PSRATE;
+       opts->p_srates[0] = UAC2_DEF_PSRATE;
        opts->p_ssize = UAC2_DEF_PSSIZE;
+       opts->p_hs_bint = UAC2_DEF_PHSBINT;
        opts->c_chmask = UAC2_DEF_CCHMASK;
-       opts->c_srate = UAC2_DEF_CSRATE;
+       opts->c_srates[0] = UAC2_DEF_CSRATE;
        opts->c_ssize = UAC2_DEF_CSSIZE;
+       opts->c_hs_bint = UAC2_DEF_CHSBINT;
        opts->c_sync = UAC2_DEF_CSYNC;
 
        opts->p_mute_present = UAC2_DEF_MUTE_PRESENT;
@@ -1934,6 +2164,9 @@ static struct usb_function_instance *afunc_alloc_inst(void)
 
        opts->req_number = UAC2_DEF_REQ_NUM;
        opts->fb_max = FBACK_FAST_MAX;
+
+       snprintf(opts->function_name, sizeof(opts->function_name), "Source/Sink");
+
        return &opts->func_inst;
 }
 
@@ -1985,6 +2218,7 @@ static struct usb_function *afunc_alloc(struct usb_function_instance *fi)
        uac2->g_audio.func.set_alt = afunc_set_alt;
        uac2->g_audio.func.get_alt = afunc_get_alt;
        uac2->g_audio.func.disable = afunc_disable;
+       uac2->g_audio.func.suspend = afunc_suspend;
        uac2->g_audio.func.setup = afunc_setup;
        uac2->g_audio.func.free_func = afunc_free;
 
index 4561d7a..2bb5698 100644 (file)
@@ -32,6 +32,7 @@ enum {
        UAC_P_PITCH_CTRL,
        UAC_MUTE_CTRL,
        UAC_VOLUME_CTRL,
+       UAC_RATE_CTRL,
 };
 
 /* Runtime data params for one stream */
@@ -62,6 +63,10 @@ struct uac_rtd_params {
   s16 volume;
   int mute;
 
+       struct snd_kcontrol *snd_kctl_rate; /* read-only current rate */
+       int srate; /* selected samplerate */
+       int active; /* playback/capture running */
+
   spinlock_t lock; /* lock for control transfers */
 
 };
@@ -150,8 +155,6 @@ static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req)
        struct snd_pcm_runtime *runtime;
        struct uac_rtd_params *prm = req->context;
        struct snd_uac_chip *uac = prm->uac;
-       struct g_audio *audio_dev = uac->audio_dev;
-       struct uac_params *params = &audio_dev->params;
        unsigned int frames, p_pktsize;
        unsigned long long pitched_rate_mil, p_pktsize_residue_mil,
                        residue_frames_mil, div_result;
@@ -196,15 +199,14 @@ static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req)
                 */
                unsigned long long p_interval_mil = uac->p_interval * 1000000ULL;
 
-               pitched_rate_mil = (unsigned long long)
-                               params->p_srate * prm->pitch;
+               pitched_rate_mil = (unsigned long long) prm->srate * prm->pitch;
                div_result = pitched_rate_mil;
                do_div(div_result, uac->p_interval);
                do_div(div_result, 1000000);
                frames = (unsigned int) div_result;
 
                pr_debug("p_srate %d, pitch %d, interval_mil %llu, frames %d\n",
-                               params->p_srate, prm->pitch, p_interval_mil, frames);
+                               prm->srate, prm->pitch, p_interval_mil, frames);
 
                p_pktsize = min_t(unsigned int,
                                        uac->p_framesize * frames,
@@ -281,7 +283,6 @@ static void u_audio_iso_fback_complete(struct usb_ep *ep,
        struct uac_rtd_params *prm = req->context;
        struct snd_uac_chip *uac = prm->uac;
        struct g_audio *audio_dev = uac->audio_dev;
-       struct uac_params *params = &audio_dev->params;
        int status = req->status;
 
        /* i/f shutting down */
@@ -303,7 +304,7 @@ static void u_audio_iso_fback_complete(struct usb_ep *ep,
                        __func__, status, req->actual, req->length);
 
        u_audio_set_fback_frequency(audio_dev->gadget->speed, audio_dev->out_ep,
-                                   params->c_srate, prm->pitch,
+                                   prm->srate, prm->pitch,
                                    req->buf);
 
        if (usb_ep_queue(ep, req, GFP_ATOMIC))
@@ -387,16 +388,14 @@ static int uac_pcm_open(struct snd_pcm_substream *substream)
        struct snd_pcm_runtime *runtime = substream->runtime;
        struct g_audio *audio_dev;
        struct uac_params *params;
+       struct uac_rtd_params *prm;
        int p_ssize, c_ssize;
-       int p_srate, c_srate;
        int p_chmask, c_chmask;
 
        audio_dev = uac->audio_dev;
        params = &audio_dev->params;
        p_ssize = params->p_ssize;
        c_ssize = params->c_ssize;
-       p_srate = params->p_srate;
-       c_srate = params->c_srate;
        p_chmask = params->p_chmask;
        c_chmask = params->c_chmask;
        uac->p_residue_mil = 0;
@@ -404,19 +403,18 @@ static int uac_pcm_open(struct snd_pcm_substream *substream)
        runtime->hw = uac_pcm_hardware;
 
        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
-               runtime->hw.rate_min = p_srate;
                runtime->hw.formats = uac_ssize_to_fmt(p_ssize);
                runtime->hw.channels_min = num_channels(p_chmask);
-               runtime->hw.period_bytes_min = 2 * uac->p_prm.max_psize
-                                               / runtime->hw.periods_min;
+               prm = &uac->p_prm;
        } else {
-               runtime->hw.rate_min = c_srate;
                runtime->hw.formats = uac_ssize_to_fmt(c_ssize);
                runtime->hw.channels_min = num_channels(c_chmask);
-               runtime->hw.period_bytes_min = 2 * uac->c_prm.max_psize
-                                               / runtime->hw.periods_min;
+               prm = &uac->c_prm;
        }
 
+       runtime->hw.period_bytes_min = 2 * prm->max_psize
+                                       / runtime->hw.periods_min;
+       runtime->hw.rate_min = prm->srate;
        runtime->hw.rate_max = runtime->hw.rate_min;
        runtime->hw.channels_max = runtime->hw.channels_min;
 
@@ -493,6 +491,99 @@ static inline void free_ep_fback(struct uac_rtd_params *prm, struct usb_ep *ep)
                dev_err(uac->card->dev, "%s:%d Error!\n", __func__, __LINE__);
 }
 
+static void set_active(struct uac_rtd_params *prm, bool active)
+{
+       // notifying through the Rate ctrl
+       struct snd_kcontrol *kctl = prm->snd_kctl_rate;
+       unsigned long flags;
+
+       spin_lock_irqsave(&prm->lock, flags);
+       if (prm->active != active) {
+               prm->active = active;
+               snd_ctl_notify(prm->uac->card, SNDRV_CTL_EVENT_MASK_VALUE,
+                               &kctl->id);
+       }
+       spin_unlock_irqrestore(&prm->lock, flags);
+}
+
+int u_audio_set_capture_srate(struct g_audio *audio_dev, int srate)
+{
+       struct uac_params *params = &audio_dev->params;
+       struct snd_uac_chip *uac = audio_dev->uac;
+       struct uac_rtd_params *prm;
+       int i;
+       unsigned long flags;
+
+       dev_dbg(&audio_dev->gadget->dev, "%s: srate %d\n", __func__, srate);
+       prm = &uac->c_prm;
+       for (i = 0; i < UAC_MAX_RATES; i++) {
+               if (params->c_srates[i] == srate) {
+                       spin_lock_irqsave(&prm->lock, flags);
+                       prm->srate = srate;
+                       spin_unlock_irqrestore(&prm->lock, flags);
+                       return 0;
+               }
+               if (params->c_srates[i] == 0)
+                       break;
+       }
+
+       return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(u_audio_set_capture_srate);
+
+int u_audio_get_capture_srate(struct g_audio *audio_dev, u32 *val)
+{
+       struct snd_uac_chip *uac = audio_dev->uac;
+       struct uac_rtd_params *prm;
+       unsigned long flags;
+
+       prm = &uac->c_prm;
+       spin_lock_irqsave(&prm->lock, flags);
+       *val = prm->srate;
+       spin_unlock_irqrestore(&prm->lock, flags);
+       return 0;
+}
+EXPORT_SYMBOL_GPL(u_audio_get_capture_srate);
+
+int u_audio_set_playback_srate(struct g_audio *audio_dev, int srate)
+{
+       struct uac_params *params = &audio_dev->params;
+       struct snd_uac_chip *uac = audio_dev->uac;
+       struct uac_rtd_params *prm;
+       int i;
+       unsigned long flags;
+
+       dev_dbg(&audio_dev->gadget->dev, "%s: srate %d\n", __func__, srate);
+       prm = &uac->p_prm;
+       for (i = 0; i < UAC_MAX_RATES; i++) {
+               if (params->p_srates[i] == srate) {
+                       spin_lock_irqsave(&prm->lock, flags);
+                       prm->srate = srate;
+                       spin_unlock_irqrestore(&prm->lock, flags);
+                       return 0;
+               }
+               if (params->p_srates[i] == 0)
+                       break;
+       }
+
+       return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(u_audio_set_playback_srate);
+
+int u_audio_get_playback_srate(struct g_audio *audio_dev, u32 *val)
+{
+       struct snd_uac_chip *uac = audio_dev->uac;
+       struct uac_rtd_params *prm;
+       unsigned long flags;
+
+       prm = &uac->p_prm;
+       spin_lock_irqsave(&prm->lock, flags);
+       *val = prm->srate;
+       spin_unlock_irqrestore(&prm->lock, flags);
+       return 0;
+}
+EXPORT_SYMBOL_GPL(u_audio_get_playback_srate);
+
 int u_audio_start_capture(struct g_audio *audio_dev)
 {
        struct snd_uac_chip *uac = audio_dev->uac;
@@ -504,8 +595,9 @@ int u_audio_start_capture(struct g_audio *audio_dev)
        struct uac_params *params = &audio_dev->params;
        int req_len, i;
 
-       ep = audio_dev->out_ep;
        prm = &uac->c_prm;
+       dev_dbg(dev, "start capture with rate %d\n", prm->srate);
+       ep = audio_dev->out_ep;
        config_ep_by_speed(gadget, &audio_dev->func, ep);
        req_len = ep->maxpacket;
 
@@ -531,6 +623,8 @@ int u_audio_start_capture(struct g_audio *audio_dev)
                        dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
        }
 
+       set_active(&uac->c_prm, true);
+
        ep_fback = audio_dev->in_ep_fback;
        if (!ep_fback)
                return 0;
@@ -562,7 +656,7 @@ int u_audio_start_capture(struct g_audio *audio_dev)
         */
        prm->pitch = 1000000;
        u_audio_set_fback_frequency(audio_dev->gadget->speed, ep,
-                                   params->c_srate, prm->pitch,
+                                   prm->srate, prm->pitch,
                                    req_fback->buf);
 
        if (usb_ep_queue(ep_fback, req_fback, GFP_ATOMIC))
@@ -576,6 +670,7 @@ void u_audio_stop_capture(struct g_audio *audio_dev)
 {
        struct snd_uac_chip *uac = audio_dev->uac;
 
+       set_active(&uac->c_prm, false);
        if (audio_dev->in_ep_fback)
                free_ep_fback(&uac->c_prm, audio_dev->in_ep_fback);
        free_ep(&uac->c_prm, audio_dev->out_ep);
@@ -596,8 +691,9 @@ int u_audio_start_playback(struct g_audio *audio_dev)
        int req_len, i;
        unsigned int p_pktsize;
 
-       ep = audio_dev->in_ep;
        prm = &uac->p_prm;
+       dev_dbg(dev, "start playback with rate %d\n", prm->srate);
+       ep = audio_dev->in_ep;
        config_ep_by_speed(gadget, &audio_dev->func, ep);
 
        ep_desc = ep->desc;
@@ -618,7 +714,7 @@ int u_audio_start_playback(struct g_audio *audio_dev)
        uac->p_interval = factor / (1 << (ep_desc->bInterval - 1));
        p_pktsize = min_t(unsigned int,
                                uac->p_framesize *
-                                       (params->p_srate / uac->p_interval),
+                                       (prm->srate / uac->p_interval),
                                ep->maxpacket);
 
        req_len = p_pktsize;
@@ -646,6 +742,8 @@ int u_audio_start_playback(struct g_audio *audio_dev)
                        dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
        }
 
+       set_active(&uac->p_prm, true);
+
        return 0;
 }
 EXPORT_SYMBOL_GPL(u_audio_start_playback);
@@ -654,10 +752,20 @@ void u_audio_stop_playback(struct g_audio *audio_dev)
 {
        struct snd_uac_chip *uac = audio_dev->uac;
 
+       set_active(&uac->p_prm, false);
        free_ep(&uac->p_prm, audio_dev->in_ep);
 }
 EXPORT_SYMBOL_GPL(u_audio_stop_playback);
 
+void u_audio_suspend(struct g_audio *audio_dev)
+{
+       struct snd_uac_chip *uac = audio_dev->uac;
+
+       set_active(&uac->p_prm, false);
+       set_active(&uac->c_prm, false);
+}
+EXPORT_SYMBOL_GPL(u_audio_suspend);
+
 int u_audio_get_volume(struct g_audio *audio_dev, int playback, s16 *val)
 {
        struct snd_uac_chip *uac = audio_dev->uac;
@@ -943,6 +1051,68 @@ static int u_audio_volume_put(struct snd_kcontrol *kcontrol,
        return change;
 }
 
+static int get_max_srate(const int *srates)
+{
+       int i, max_srate = 0;
+
+       for (i = 0; i < UAC_MAX_RATES; i++) {
+               if (srates[i] == 0)
+                       break;
+               if (srates[i] > max_srate)
+                       max_srate = srates[i];
+       }
+       return max_srate;
+}
+
+static int get_min_srate(const int *srates)
+{
+       int i, min_srate = INT_MAX;
+
+       for (i = 0; i < UAC_MAX_RATES; i++) {
+               if (srates[i] == 0)
+                       break;
+               if (srates[i] < min_srate)
+                       min_srate = srates[i];
+       }
+       return min_srate;
+}
+
+static int u_audio_rate_info(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_info *uinfo)
+{
+       const int *srates;
+       struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol);
+       struct snd_uac_chip *uac = prm->uac;
+       struct g_audio *audio_dev = uac->audio_dev;
+       struct uac_params *params = &audio_dev->params;
+
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = 1;
+
+       if (prm == &uac->c_prm)
+               srates = params->c_srates;
+       else
+               srates = params->p_srates;
+       uinfo->value.integer.min = get_min_srate(srates);
+       uinfo->value.integer.max = get_max_srate(srates);
+       return 0;
+}
+
+static int u_audio_rate_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);
+       if (prm->active)
+               ucontrol->value.integer.value[0] = prm->srate;
+       else
+               /* not active: reporting zero rate */
+               ucontrol->value.integer.value[0] = 0;
+       spin_unlock_irqrestore(&prm->lock, flags);
+       return 0;
+}
 
 static struct snd_kcontrol_new u_audio_controls[]  = {
   [UAC_FBACK_CTRL] {
@@ -973,6 +1143,13 @@ static struct snd_kcontrol_new u_audio_controls[]  = {
                .get =          u_audio_volume_get,
                .put =          u_audio_volume_put,
        },
+       [UAC_RATE_CTRL] {
+               .iface =        SNDRV_CTL_ELEM_IFACE_PCM,
+               .name =         "", /* will be filled later */
+               .access =       SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+               .info =         u_audio_rate_info,
+               .get =          u_audio_rate_get,
+       },
 };
 
 int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
@@ -1005,6 +1182,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
     spin_lock_init(&prm->lock);
     uac->c_prm.uac = uac;
                prm->max_psize = g_audio->out_ep_maxpsize;
+               prm->srate = params->c_srates[0];
 
                prm->reqs = kcalloc(params->req_number,
                                    sizeof(struct usb_request *),
@@ -1029,6 +1207,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
                spin_lock_init(&prm->lock);
                uac->p_prm.uac = uac;
                prm->max_psize = g_audio->in_ep_maxpsize;
+               prm->srate = params->p_srates[0];
 
                prm->reqs = kcalloc(params->req_number,
                                    sizeof(struct usb_request *),
@@ -1186,6 +1365,25 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
                        prm->volume_min = fu->volume_min;
                        prm->volume_res = fu->volume_res;
                }
+
+               /* Add rate control */
+               snprintf(ctrl_name, sizeof(ctrl_name),
+                               "%s Rate", direction);
+               u_audio_controls[UAC_RATE_CTRL].name = ctrl_name;
+
+               kctl = snd_ctl_new1(&u_audio_controls[UAC_RATE_CTRL], prm);
+               if (!kctl) {
+                       err = -ENOMEM;
+                       goto snd_fail;
+               }
+
+               kctl->id.device = pcm->device;
+               kctl->id.subdevice = 0;
+
+               err = snd_ctl_add(card, kctl);
+               if (err < 0)
+                       goto snd_fail;
+               prm->snd_kctl_rate = kctl;
        }
 
        strscpy(card->driver, card_name, sizeof(card->driver));
index 8dfdae1..9512b8f 100644 (file)
@@ -10,6 +10,7 @@
 #define __U_AUDIO_H
 
 #include <linux/usb/composite.h>
+#include "uac_common.h"
 
 /*
  * Same maximum frequency deviation on the slower side as in
@@ -40,16 +41,18 @@ struct uac_fu_params {
 struct uac_params {
        /* playback */
        int p_chmask;   /* channel mask */
-       int p_srate;    /* rate in Hz */
+       int p_srates[UAC_MAX_RATES];    /* available rates in Hz (0 terminated list) */
        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_srates[UAC_MAX_RATES];    /* available rates in Hz (0 terminated list) */
        int c_ssize;    /* sample size */
        struct uac_fu_params c_fu;      /* Feature Unit parameters */
 
+       /* rates are dynamic, in uac_rtd_params */
+
        int req_number; /* number of preallocated requests */
        int fb_max;     /* upper frequency drift feedback limit per-mil */
 };
@@ -117,9 +120,16 @@ 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_capture_srate(struct g_audio *audio_dev, u32 *val);
+int u_audio_set_capture_srate(struct g_audio *audio_dev, int srate);
+int u_audio_get_playback_srate(struct g_audio *audio_dev, u32 *val);
+int u_audio_set_playback_srate(struct g_audio *audio_dev, int srate);
+
 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);
 
+void u_audio_suspend(struct g_audio *g_audio);
+
 #endif /* __U_AUDIO_H */
index 589fae8..f7a6167 100644 (file)
@@ -9,6 +9,7 @@
 #define __U_UAC1_H
 
 #include <linux/usb/composite.h>
+#include "uac_common.h"
 
 #define UAC1_OUT_EP_MAX_PACKET_SIZE    200
 #define UAC1_DEF_CCHMASK       0x3
 struct f_uac1_opts {
        struct usb_function_instance    func_inst;
        int                             c_chmask;
-       int                             c_srate;
+       int                             c_srates[UAC_MAX_RATES];
        int                             c_ssize;
        int                             p_chmask;
-       int                             p_srate;
+       int                             p_srates[UAC_MAX_RATES];
        int                             p_ssize;
 
        bool                    p_mute_present;
@@ -51,6 +52,8 @@ struct f_uac1_opts {
        int                             req_number;
        unsigned                        bound:1;
 
+       char                    function_name[32];
+
        struct mutex                    lock;
        int                             refcnt;
 };
index e0c8e35..0510c9b 100644 (file)
 #define U_UAC2_H
 
 #include <linux/usb/composite.h>
+#include "uac_common.h"
 
 #define UAC2_DEF_PCHMASK 0x3
 #define UAC2_DEF_PSRATE 48000
 #define UAC2_DEF_PSSIZE 2
+#define UAC2_DEF_PHSBINT 0
 #define UAC2_DEF_CCHMASK 0x3
 #define UAC2_DEF_CSRATE 64000
 #define UAC2_DEF_CSSIZE 2
+#define UAC2_DEF_CHSBINT 0
 #define UAC2_DEF_CSYNC         USB_ENDPOINT_SYNC_ASYNC
 
 #define UAC2_DEF_MUTE_PRESENT  1
 struct f_uac2_opts {
        struct usb_function_instance    func_inst;
        int                             p_chmask;
-       int                             p_srate;
+       int                             p_srates[UAC_MAX_RATES];
        int                             p_ssize;
+       u8                              p_hs_bint;
        int                             c_chmask;
-       int                             c_srate;
+       int                             c_srates[UAC_MAX_RATES];
        int                             c_ssize;
        int                             c_sync;
+       u8                              c_hs_bint;
 
        bool                    p_mute_present;
        bool                    p_volume_present;
@@ -58,6 +63,8 @@ struct f_uac2_opts {
        int                             fb_max;
        bool                    bound;
 
+       char                    function_name[32];
+
        struct mutex                    lock;
        int                             refcnt;
 };
diff --git a/drivers/usb/gadget/function/uac_common.h b/drivers/usb/gadget/function/uac_common.h
new file mode 100644 (file)
index 0000000..3ecf89d
--- /dev/null
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ */
+
+#ifndef UAC_COMMON_H
+#define UAC_COMMON_H
+
+#define UAC_MAX_RATES 10 /* maximum number of rates configurable by f_uac1/2 */
+#endif
index a748ed0..76ea6de 100644 (file)
@@ -22,91 +22,108 @@ USB_GADGET_COMPOSITE_OPTIONS();
 
 /* Playback(USB-IN) Default Stereo - Fl/Fr */
 static int p_chmask = UAC2_DEF_PCHMASK;
-module_param(p_chmask, uint, S_IRUGO);
+module_param(p_chmask, uint, 0444);
 MODULE_PARM_DESC(p_chmask, "Playback Channel Mask");
 
 /* Playback Default 48 KHz */
-static int p_srate = UAC2_DEF_PSRATE;
-module_param(p_srate, uint, S_IRUGO);
-MODULE_PARM_DESC(p_srate, "Playback Sampling Rate");
+static int p_srates[UAC_MAX_RATES] = {UAC2_DEF_PSRATE};
+static int p_srates_cnt = 1;
+module_param_array_named(p_srate, p_srates, uint, &p_srates_cnt, 0444);
+MODULE_PARM_DESC(p_srate, "Playback Sampling Rates (array)");
 
 /* Playback Default 16bits/sample */
 static int p_ssize = UAC2_DEF_PSSIZE;
-module_param(p_ssize, uint, S_IRUGO);
+module_param(p_ssize, uint, 0444);
 MODULE_PARM_DESC(p_ssize, "Playback Sample Size(bytes)");
 
+/* Playback bInterval for HS/SS (1-4: fixed, 0: auto) */
+static u8 p_hs_bint = UAC2_DEF_PHSBINT;
+module_param(p_hs_bint, byte, 0444);
+MODULE_PARM_DESC(p_hs_bint,
+               "Playback bInterval for HS/SS (1-4: fixed, 0: auto)");
+
 /* Capture(USB-OUT) Default Stereo - Fl/Fr */
 static int c_chmask = UAC2_DEF_CCHMASK;
-module_param(c_chmask, uint, S_IRUGO);
+module_param(c_chmask, uint, 0444);
 MODULE_PARM_DESC(c_chmask, "Capture Channel Mask");
 
 /* Capture Default 64 KHz */
-static int c_srate = UAC2_DEF_CSRATE;
-module_param(c_srate, uint, S_IRUGO);
-MODULE_PARM_DESC(c_srate, "Capture Sampling Rate");
+static int c_srates[UAC_MAX_RATES] = {UAC2_DEF_CSRATE};
+static int c_srates_cnt = 1;
+module_param_array_named(c_srate, c_srates, uint, &c_srates_cnt, 0444);
+MODULE_PARM_DESC(c_srate, "Capture Sampling Rates (array)");
 
 /* Capture Default 16bits/sample */
 static int c_ssize = UAC2_DEF_CSSIZE;
-module_param(c_ssize, uint, S_IRUGO);
+module_param(c_ssize, uint, 0444);
 MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)");
+
+/* capture bInterval for HS/SS (1-4: fixed, 0: auto) */
+static u8 c_hs_bint = UAC2_DEF_CHSBINT;
+module_param(c_hs_bint, byte, 0444);
+MODULE_PARM_DESC(c_hs_bint,
+               "Capture bInterval for HS/SS (1-4: fixed, 0: auto)");
+
 #else
 #ifndef CONFIG_GADGET_UAC1_LEGACY
 #include "u_uac1.h"
 
 /* Playback(USB-IN) Default Stereo - Fl/Fr */
 static int p_chmask = UAC1_DEF_PCHMASK;
-module_param(p_chmask, uint, S_IRUGO);
+module_param(p_chmask, uint, 0444);
 MODULE_PARM_DESC(p_chmask, "Playback Channel Mask");
 
 /* Playback Default 48 KHz */
-static int p_srate = UAC1_DEF_PSRATE;
-module_param(p_srate, uint, S_IRUGO);
-MODULE_PARM_DESC(p_srate, "Playback Sampling Rate");
+static int p_srates[UAC_MAX_RATES] = {UAC1_DEF_PSRATE};
+static int p_srates_cnt = 1;
+module_param_array_named(p_srate, p_srates, uint, &p_srates_cnt, 0444);
+MODULE_PARM_DESC(p_srate, "Playback Sampling Rates (array)");
 
 /* Playback Default 16bits/sample */
 static int p_ssize = UAC1_DEF_PSSIZE;
-module_param(p_ssize, uint, S_IRUGO);
+module_param(p_ssize, uint, 0444);
 MODULE_PARM_DESC(p_ssize, "Playback Sample Size(bytes)");
 
 /* Capture(USB-OUT) Default Stereo - Fl/Fr */
 static int c_chmask = UAC1_DEF_CCHMASK;
-module_param(c_chmask, uint, S_IRUGO);
+module_param(c_chmask, uint, 0444);
 MODULE_PARM_DESC(c_chmask, "Capture Channel Mask");
 
 /* Capture Default 48 KHz */
-static int c_srate = UAC1_DEF_CSRATE;
-module_param(c_srate, uint, S_IRUGO);
-MODULE_PARM_DESC(c_srate, "Capture Sampling Rate");
+static int c_srates[UAC_MAX_RATES] = {UAC1_DEF_CSRATE};
+static int c_srates_cnt = 1;
+module_param_array_named(c_srate, c_srates, uint, &c_srates_cnt, 0444);
+MODULE_PARM_DESC(c_srate, "Capture Sampling Rates (array)");
 
 /* Capture Default 16bits/sample */
 static int c_ssize = UAC1_DEF_CSSIZE;
-module_param(c_ssize, uint, S_IRUGO);
+module_param(c_ssize, uint, 0444);
 MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)");
 #else /* CONFIG_GADGET_UAC1_LEGACY */
 #include "u_uac1_legacy.h"
 
 static char *fn_play = FILE_PCM_PLAYBACK;
-module_param(fn_play, charp, S_IRUGO);
+module_param(fn_play, charp, 0444);
 MODULE_PARM_DESC(fn_play, "Playback PCM device file name");
 
 static char *fn_cap = FILE_PCM_CAPTURE;
-module_param(fn_cap, charp, S_IRUGO);
+module_param(fn_cap, charp, 0444);
 MODULE_PARM_DESC(fn_cap, "Capture PCM device file name");
 
 static char *fn_cntl = FILE_CONTROL;
-module_param(fn_cntl, charp, S_IRUGO);
+module_param(fn_cntl, charp, 0444);
 MODULE_PARM_DESC(fn_cntl, "Control device file name");
 
 static int req_buf_size = UAC1_OUT_EP_MAX_PACKET_SIZE;
-module_param(req_buf_size, int, S_IRUGO);
+module_param(req_buf_size, int, 0444);
 MODULE_PARM_DESC(req_buf_size, "ISO OUT endpoint request buffer size");
 
 static int req_count = UAC1_REQ_COUNT;
-module_param(req_count, int, S_IRUGO);
+module_param(req_count, int, 0444);
 MODULE_PARM_DESC(req_count, "ISO OUT endpoint request count");
 
 static int audio_buf_size = UAC1_AUDIO_BUF_SIZE;
-module_param(audio_buf_size, int, S_IRUGO);
+module_param(audio_buf_size, int, 0444);
 MODULE_PARM_DESC(audio_buf_size, "Audio buffer size");
 #endif /* CONFIG_GADGET_UAC1_LEGACY */
 #endif
@@ -237,9 +254,11 @@ static int audio_bind(struct usb_composite_dev *cdev)
 {
 #ifndef CONFIG_GADGET_UAC1
        struct f_uac2_opts      *uac2_opts;
+       int i;
 #else
 #ifndef CONFIG_GADGET_UAC1_LEGACY
        struct f_uac1_opts      *uac1_opts;
+       int i;
 #else
        struct f_uac1_legacy_opts       *uac1_opts;
 #endif
@@ -263,20 +282,34 @@ static int audio_bind(struct usb_composite_dev *cdev)
 #ifndef CONFIG_GADGET_UAC1
        uac2_opts = container_of(fi_uac2, struct f_uac2_opts, func_inst);
        uac2_opts->p_chmask = p_chmask;
-       uac2_opts->p_srate = p_srate;
+
+       for (i = 0; i < p_srates_cnt; ++i)
+               uac2_opts->p_srates[i] = p_srates[i];
+
        uac2_opts->p_ssize = p_ssize;
+       uac2_opts->p_hs_bint = p_hs_bint;
        uac2_opts->c_chmask = c_chmask;
-       uac2_opts->c_srate = c_srate;
+
+       for (i = 0; i < c_srates_cnt; ++i)
+               uac2_opts->c_srates[i] = c_srates[i];
+
        uac2_opts->c_ssize = c_ssize;
+       uac2_opts->c_hs_bint = c_hs_bint;
        uac2_opts->req_number = UAC2_DEF_REQ_NUM;
 #else
 #ifndef CONFIG_GADGET_UAC1_LEGACY
        uac1_opts = container_of(fi_uac1, struct f_uac1_opts, func_inst);
        uac1_opts->p_chmask = p_chmask;
-       uac1_opts->p_srate = p_srate;
+
+       for (i = 0; i < p_srates_cnt; ++i)
+               uac1_opts->p_srates[i] = p_srates[i];
+
        uac1_opts->p_ssize = p_ssize;
        uac1_opts->c_chmask = c_chmask;
-       uac1_opts->c_srate = c_srate;
+
+       for (i = 0; i < c_srates_cnt; ++i)
+               uac1_opts->c_srates[i] = c_srates[i];
+
        uac1_opts->c_ssize = c_ssize;
        uac1_opts->req_number = UAC1_DEF_REQ_NUM;
 #else /* CONFIG_GADGET_UAC1_LEGACY */
index 43f1b0d..be76f89 100644 (file)
@@ -32,9 +32,6 @@
 #include <linux/workqueue.h>
 
 /* XUSB_DEV registers */
-#define SPARAM 0x000
-#define  SPARAM_ERSTMAX_MASK GENMASK(20, 16)
-#define  SPARAM_ERSTMAX(x) (((x) << 16) & SPARAM_ERSTMAX_MASK)
 #define DB 0x004
 #define  DB_TARGET_MASK GENMASK(15, 8)
 #define  DB_TARGET(x) (((x) << 8) & DB_TARGET_MASK)
@@ -275,8 +272,10 @@ BUILD_EP_CONTEXT_RW(deq_hi, deq_hi, 0, 0xffffffff)
 BUILD_EP_CONTEXT_RW(avg_trb_len, tx_info, 0, 0xffff)
 BUILD_EP_CONTEXT_RW(max_esit_payload, tx_info, 16, 0xffff)
 BUILD_EP_CONTEXT_RW(edtla, rsvd[0], 0, 0xffffff)
-BUILD_EP_CONTEXT_RW(seq_num, rsvd[0], 24, 0xff)
+BUILD_EP_CONTEXT_RW(rsvd, rsvd[0], 24, 0x1)
 BUILD_EP_CONTEXT_RW(partial_td, rsvd[0], 25, 0x1)
+BUILD_EP_CONTEXT_RW(splitxstate, rsvd[0], 26, 0x1)
+BUILD_EP_CONTEXT_RW(seq_num, rsvd[0], 27, 0x1f)
 BUILD_EP_CONTEXT_RW(cerrcnt, rsvd[1], 18, 0x3)
 BUILD_EP_CONTEXT_RW(data_offset, rsvd[2], 0, 0x1ffff)
 BUILD_EP_CONTEXT_RW(numtrbs, rsvd[2], 22, 0x1f)
@@ -1557,6 +1556,9 @@ static int __tegra_xudc_ep_set_halt(struct tegra_xudc_ep *ep, bool halt)
                ep_reload(xudc, ep->index);
 
                ep_ctx_write_state(ep->context, EP_STATE_RUNNING);
+               ep_ctx_write_rsvd(ep->context, 0);
+               ep_ctx_write_partial_td(ep->context, 0);
+               ep_ctx_write_splitxstate(ep->context, 0);
                ep_ctx_write_seq_num(ep->context, 0);
 
                ep_reload(xudc, ep->index);
@@ -2812,7 +2814,10 @@ static void tegra_xudc_reset(struct tegra_xudc *xudc)
        xudc->setup_seq_num = 0;
        xudc->queued_setup_packet = false;
 
-       ep_ctx_write_seq_num(ep0->context, xudc->setup_seq_num);
+       ep_ctx_write_rsvd(ep0->context, 0);
+       ep_ctx_write_partial_td(ep0->context, 0);
+       ep_ctx_write_splitxstate(ep0->context, 0);
+       ep_ctx_write_seq_num(ep0->context, 0);
 
        deq_ptr = trb_virt_to_phys(ep0, &ep0->transfer_ring[ep0->deq_ptr]);
 
@@ -3295,11 +3300,6 @@ static void tegra_xudc_init_event_ring(struct tegra_xudc *xudc)
        unsigned int i;
        u32 val;
 
-       val = xudc_readl(xudc, SPARAM);
-       val &= ~(SPARAM_ERSTMAX_MASK);
-       val |= SPARAM_ERSTMAX(XUDC_NR_EVENT_RINGS);
-       xudc_writel(xudc, val, SPARAM);
-
        for (i = 0; i < ARRAY_SIZE(xudc->event_ring); i++) {
                memset(xudc->event_ring[i], 0, XUDC_EVENT_RING_SIZE *
                       sizeof(*xudc->event_ring[i]));
index 0b7f1ed..c063fb0 100644 (file)
@@ -931,7 +931,7 @@ static struct debug_buffer *alloc_buffer(struct usb_bus *bus,
 
 static int fill_buffer(struct debug_buffer *buf)
 {
-       int ret = 0;
+       int ret;
 
        if (!buf->output_buf)
                buf->output_buf = vmalloc(buf->alloc_size);
@@ -956,7 +956,7 @@ static ssize_t debug_output(struct file *file, char __user *user_buf,
                size_t len, loff_t *offset)
 {
        struct debug_buffer *buf = file->private_data;
-       int ret = 0;
+       int ret;
 
        mutex_lock(&buf->mutex);
        if (buf->count == 0) {
index e87cf3a..638f03b 100644 (file)
@@ -21,6 +21,9 @@ static const char hcd_name[] = "ehci-pci";
 /* defined here to avoid adding to pci_ids.h for single instance use */
 #define PCI_DEVICE_ID_INTEL_CE4100_USB 0x2e70
 
+#define PCI_VENDOR_ID_ASPEED           0x1a03
+#define PCI_DEVICE_ID_ASPEED_EHCI      0x2603
+
 /*-------------------------------------------------------------------------*/
 #define PCI_DEVICE_ID_INTEL_QUARK_X1000_SOC            0x0939
 static inline bool is_intel_quark_x1000(struct pci_dev *pdev)
@@ -222,6 +225,12 @@ static int ehci_pci_setup(struct usb_hcd *hcd)
                        ehci->has_synopsys_hc_bug = 1;
                }
                break;
+       case PCI_VENDOR_ID_ASPEED:
+               if (pdev->device == PCI_DEVICE_ID_ASPEED_EHCI) {
+                       ehci_info(ehci, "applying Aspeed HC workaround\n");
+                       ehci->is_aspeed = 1;
+               }
+               break;
        }
 
        /* optional debug port, normally in the first BAR */
index c3dc906..1115431 100644 (file)
@@ -43,7 +43,6 @@
 #define hcd_to_ehci_priv(h) ((struct ehci_platform_priv *)hcd_to_ehci(h)->priv)
 
 #define BCM_USB_FIFO_THRESHOLD 0x00800040
-#define bcm_iproc_insnreg01    hostpc[0]
 
 struct ehci_platform_priv {
        struct clk *clks[EHCI_MAX_CLKS];
@@ -81,7 +80,7 @@ static int ehci_platform_reset(struct usb_hcd *hcd)
 
        if (of_device_is_compatible(pdev->dev.of_node, "brcm,xgs-iproc-ehci"))
                ehci_writel(ehci, BCM_USB_FIFO_THRESHOLD,
-                           &ehci->regs->bcm_iproc_insnreg01);
+                           &ehci->regs->brcm_insnreg[1]);
 
        return 0;
 }
index 2cbf4f8..a2a5c29 100644 (file)
 
 /* fill a qtd, returning how much of the buffer we were able to queue up */
 
-static int
+static unsigned int
 qtd_fill(struct ehci_hcd *ehci, struct ehci_qtd *qtd, dma_addr_t buf,
                  size_t len, int token, int maxpacket)
 {
-       int     i, count;
+       unsigned int count;
        u64     addr = buf;
+       int     i;
 
        /* one buffer entry per 4K ... first might be short or unaligned */
        qtd->hw_buf[0] = cpu_to_hc32(ehci, (u32)addr);
@@ -652,7 +653,7 @@ qh_urb_transaction (
         * and may serve as a control status ack
         */
        for (;;) {
-               int this_qtd_len;
+               unsigned int this_qtd_len;
 
                this_qtd_len = qtd_fill(ehci, qtd, buf, this_sg_len, token,
                                maxpacket);
index 0f85aa9..bd542b6 100644 (file)
@@ -1165,10 +1165,8 @@ static struct ehci_iso_sched *
 iso_sched_alloc(unsigned packets, gfp_t mem_flags)
 {
        struct ehci_iso_sched   *iso_sched;
-       int                     size = sizeof(*iso_sched);
 
-       size += packets * sizeof(struct ehci_iso_packet);
-       iso_sched = kzalloc(size, mem_flags);
+       iso_sched = kzalloc(struct_size(iso_sched, packet, packets), mem_flags);
        if (likely(iso_sched != NULL))
                INIT_LIST_HEAD(&iso_sched->td_list);
 
index 7af17c8..c3fd375 100644 (file)
@@ -4014,10 +4014,8 @@ static struct fotg210_iso_sched *iso_sched_alloc(unsigned packets,
                gfp_t mem_flags)
 {
        struct fotg210_iso_sched *iso_sched;
-       int size = sizeof(*iso_sched);
 
-       size += packets * sizeof(struct fotg210_iso_packet);
-       iso_sched = kzalloc(size, mem_flags);
+       iso_sched = kzalloc(struct_size(iso_sched, packet, packets), mem_flags);
        if (likely(iso_sched != NULL))
                INIT_LIST_HEAD(&iso_sched->td_list);
 
index 4f267dc..76bc8d5 100644 (file)
@@ -680,7 +680,7 @@ static struct debug_buffer *alloc_buffer(struct ohci_hcd *ohci,
 
 static int fill_buffer(struct debug_buffer *buf)
 {
-       int ret = 0;
+       int ret;
 
        if (!buf->page)
                buf->page = (char *)get_zeroed_page(GFP_KERNEL);
@@ -705,7 +705,7 @@ static ssize_t debug_output(struct file *file, char __user *user_buf,
                        size_t len, loff_t *offset)
 {
        struct debug_buffer *buf = file->private_data;
-       int ret = 0;
+       int ret;
 
        mutex_lock(&buf->mutex);
        if (buf->count == 0) {
index ccb0156..e61155f 100644 (file)
@@ -914,59 +914,6 @@ static void xhci_dbc_handle_events(struct work_struct *work)
        mod_delayed_work(system_wq, &dbc->event_work, 1);
 }
 
-static void xhci_do_dbc_exit(struct xhci_hcd *xhci)
-{
-       unsigned long           flags;
-
-       spin_lock_irqsave(&xhci->lock, flags);
-       kfree(xhci->dbc);
-       xhci->dbc = NULL;
-       spin_unlock_irqrestore(&xhci->lock, flags);
-}
-
-static int xhci_do_dbc_init(struct xhci_hcd *xhci)
-{
-       u32                     reg;
-       struct xhci_dbc         *dbc;
-       unsigned long           flags;
-       void __iomem            *base;
-       int                     dbc_cap_offs;
-
-       base = &xhci->cap_regs->hc_capbase;
-       dbc_cap_offs = xhci_find_next_ext_cap(base, 0, XHCI_EXT_CAPS_DEBUG);
-       if (!dbc_cap_offs)
-               return -ENODEV;
-
-       dbc = kzalloc(sizeof(*dbc), GFP_KERNEL);
-       if (!dbc)
-               return -ENOMEM;
-
-       dbc->regs = base + dbc_cap_offs;
-
-       /* We will avoid using DbC in xhci driver if it's in use. */
-       reg = readl(&dbc->regs->control);
-       if (reg & DBC_CTRL_DBC_ENABLE) {
-               kfree(dbc);
-               return -EBUSY;
-       }
-
-       spin_lock_irqsave(&xhci->lock, flags);
-       if (xhci->dbc) {
-               spin_unlock_irqrestore(&xhci->lock, flags);
-               kfree(dbc);
-               return -EBUSY;
-       }
-       xhci->dbc = dbc;
-       spin_unlock_irqrestore(&xhci->lock, flags);
-
-       dbc->xhci = xhci;
-       dbc->dev = xhci_to_hcd(xhci)->self.sysdev;
-       INIT_DELAYED_WORK(&dbc->event_work, xhci_dbc_handle_events);
-       spin_lock_init(&dbc->lock);
-
-       return 0;
-}
-
 static ssize_t dbc_show(struct device *dev,
                        struct device_attribute *attr,
                        char *buf)
@@ -1026,44 +973,86 @@ static ssize_t dbc_store(struct device *dev,
 
 static DEVICE_ATTR_RW(dbc);
 
-int xhci_dbc_init(struct xhci_hcd *xhci)
+struct xhci_dbc *
+xhci_alloc_dbc(struct device *dev, void __iomem *base, const struct dbc_driver *driver)
 {
+       struct xhci_dbc         *dbc;
        int                     ret;
-       struct device           *dev = xhci_to_hcd(xhci)->self.controller;
 
-       ret = xhci_do_dbc_init(xhci);
-       if (ret)
-               goto init_err3;
+       dbc = kzalloc(sizeof(*dbc), GFP_KERNEL);
+       if (!dbc)
+               return NULL;
 
-       ret = xhci_dbc_tty_probe(xhci);
-       if (ret)
-               goto init_err2;
+       dbc->regs = base;
+       dbc->dev = dev;
+       dbc->driver = driver;
+
+       if (readl(&dbc->regs->control) & DBC_CTRL_DBC_ENABLE)
+               return NULL;
+
+       INIT_DELAYED_WORK(&dbc->event_work, xhci_dbc_handle_events);
+       spin_lock_init(&dbc->lock);
 
        ret = device_create_file(dev, &dev_attr_dbc);
        if (ret)
-               goto init_err1;
+               goto err;
 
-       return 0;
+       return dbc;
+err:
+       kfree(dbc);
+       return NULL;
+}
+
+/* undo what xhci_alloc_dbc() did */
+void xhci_dbc_remove(struct xhci_dbc *dbc)
+{
+       if (!dbc)
+               return;
+       /* stop hw, stop wq and call dbc->ops->stop() */
+       xhci_dbc_stop(dbc);
+
+       /* remove sysfs files */
+       device_remove_file(dbc->dev, &dev_attr_dbc);
+
+       kfree(dbc);
+}
+
+
+int xhci_create_dbc_dev(struct xhci_hcd *xhci)
+{
+       struct device           *dev;
+       void __iomem            *base;
+       int                     ret;
+       int                     dbc_cap_offs;
+
+       /* create all parameters needed resembling a dbc device */
+       dev = xhci_to_hcd(xhci)->self.controller;
+       base = &xhci->cap_regs->hc_capbase;
+
+       dbc_cap_offs = xhci_find_next_ext_cap(base, 0, XHCI_EXT_CAPS_DEBUG);
+       if (!dbc_cap_offs)
+               return -ENODEV;
+
+       /* already allocated and in use */
+       if (xhci->dbc)
+               return -EBUSY;
+
+       ret = xhci_dbc_tty_probe(dev, base + dbc_cap_offs, xhci);
 
-init_err1:
-       xhci_dbc_tty_remove(xhci->dbc);
-init_err2:
-       xhci_do_dbc_exit(xhci);
-init_err3:
        return ret;
 }
 
-void xhci_dbc_exit(struct xhci_hcd *xhci)
+void xhci_remove_dbc_dev(struct xhci_hcd *xhci)
 {
-       struct device           *dev = xhci_to_hcd(xhci)->self.controller;
+       unsigned long           flags;
 
        if (!xhci->dbc)
                return;
 
-       device_remove_file(dev, &dev_attr_dbc);
        xhci_dbc_tty_remove(xhci->dbc);
-       xhci_dbc_stop(xhci->dbc);
-       xhci_do_dbc_exit(xhci);
+       spin_lock_irqsave(&xhci->lock, flags);
+       xhci->dbc = NULL;
+       spin_unlock_irqrestore(&xhci->lock, flags);
 }
 
 #ifdef CONFIG_PM
@@ -1098,3 +1087,13 @@ int xhci_dbc_resume(struct xhci_hcd *xhci)
        return ret;
 }
 #endif /* CONFIG_PM */
+
+int xhci_dbc_init(void)
+{
+       return dbc_tty_init();
+}
+
+void xhci_dbc_exit(void)
+{
+       dbc_tty_exit();
+}
index c70b78d..ca04192 100644 (file)
@@ -100,6 +100,7 @@ struct dbc_ep {
 struct dbc_port {
        struct tty_port                 port;
        spinlock_t                      port_lock;      /* port access */
+       int                             minor;
 
        struct list_head                read_pool;
        struct list_head                read_queue;
@@ -194,10 +195,17 @@ static inline struct dbc_ep *get_out_ep(struct xhci_dbc *dbc)
 }
 
 #ifdef CONFIG_USB_XHCI_DBGCAP
-int xhci_dbc_init(struct xhci_hcd *xhci);
-void xhci_dbc_exit(struct xhci_hcd *xhci);
-int xhci_dbc_tty_probe(struct xhci_hcd *xhci);
+int xhci_create_dbc_dev(struct xhci_hcd *xhci);
+void xhci_remove_dbc_dev(struct xhci_hcd *xhci);
+int xhci_dbc_init(void);
+void xhci_dbc_exit(void);
+int dbc_tty_init(void);
+void dbc_tty_exit(void);
+int xhci_dbc_tty_probe(struct device *dev, void __iomem *res, struct xhci_hcd *xhci);
 void xhci_dbc_tty_remove(struct xhci_dbc *dbc);
+struct xhci_dbc *xhci_alloc_dbc(struct device *dev, void __iomem *res,
+                                const struct dbc_driver *driver);
+void xhci_dbc_remove(struct xhci_dbc *dbc);
 struct dbc_request *dbc_alloc_request(struct xhci_dbc *dbc,
                                      unsigned int direction,
                                      gfp_t flags);
@@ -208,15 +216,21 @@ int xhci_dbc_suspend(struct xhci_hcd *xhci);
 int xhci_dbc_resume(struct xhci_hcd *xhci);
 #endif /* CONFIG_PM */
 #else
-static inline int xhci_dbc_init(struct xhci_hcd *xhci)
+static inline int xhci_create_dbc_dev(struct xhci_hcd *xhci)
 {
        return 0;
 }
 
-static inline void xhci_dbc_exit(struct xhci_hcd *xhci)
+static inline void xhci_remove_dbc_dev(struct xhci_hcd *xhci)
+{
+}
+static inline int xhci_dbc_init(void)
+{
+       return 0;
+}
+static inline void xhci_dbc_exit(void)
 {
 }
-
 static inline int xhci_dbc_suspend(struct xhci_hcd *xhci)
 {
        return 0;
index eb46e64..d3acc08 100644 (file)
 #include <linux/slab.h>
 #include <linux/tty.h>
 #include <linux/tty_flip.h>
+#include <linux/idr.h>
 
 #include "xhci.h"
 #include "xhci-dbgcap.h"
 
-static int dbc_tty_init(void);
-static void dbc_tty_exit(void);
-
 static struct tty_driver *dbc_tty_driver;
+static struct idr dbc_tty_minors;
+static DEFINE_MUTEX(dbc_tty_minors_lock);
 
 static inline struct dbc_port *dbc_to_port(struct xhci_dbc *dbc)
 {
@@ -180,7 +180,14 @@ xhci_dbc_free_requests(struct list_head *head)
 
 static int dbc_tty_install(struct tty_driver *driver, struct tty_struct *tty)
 {
-       struct dbc_port         *port = driver->driver_state;
+       struct dbc_port         *port;
+
+       mutex_lock(&dbc_tty_minors_lock);
+       port = idr_find(&dbc_tty_minors, tty->index);
+       mutex_unlock(&dbc_tty_minors_lock);
+
+       if (!port)
+               return -ENXIO;
 
        tty->driver_data = port;
 
@@ -409,6 +416,15 @@ static int xhci_dbc_tty_register_device(struct xhci_dbc *dbc)
 
        xhci_dbc_tty_init_port(dbc, port);
 
+       mutex_lock(&dbc_tty_minors_lock);
+       port->minor = idr_alloc(&dbc_tty_minors, port, 0, 64, GFP_KERNEL);
+       mutex_unlock(&dbc_tty_minors_lock);
+
+       if (port->minor < 0) {
+               ret = port->minor;
+               goto err_idr;
+       }
+
        ret = kfifo_alloc(&port->write_fifo, DBC_WRITE_BUF_SIZE, GFP_KERNEL);
        if (ret)
                goto err_exit_port;
@@ -424,7 +440,7 @@ static int xhci_dbc_tty_register_device(struct xhci_dbc *dbc)
                goto err_free_requests;
 
        tty_dev = tty_port_register_device(&port->port,
-                                          dbc_tty_driver, 0, NULL);
+                                          dbc_tty_driver, port->minor, NULL);
        if (IS_ERR(tty_dev)) {
                ret = PTR_ERR(tty_dev);
                goto err_free_requests;
@@ -440,6 +456,8 @@ err_free_requests:
 err_free_fifo:
        kfifo_free(&port->write_fifo);
 err_exit_port:
+       idr_remove(&dbc_tty_minors, port->minor);
+err_idr:
        xhci_dbc_tty_exit_port(port);
 
        dev_err(dbc->dev, "can't register tty port, err %d\n", ret);
@@ -453,10 +471,14 @@ static void xhci_dbc_tty_unregister_device(struct xhci_dbc *dbc)
 
        if (!port->registered)
                return;
-       tty_unregister_device(dbc_tty_driver, 0);
+       tty_unregister_device(dbc_tty_driver, port->minor);
        xhci_dbc_tty_exit_port(port);
        port->registered = false;
 
+       mutex_lock(&dbc_tty_minors_lock);
+       idr_remove(&dbc_tty_minors, port->minor);
+       mutex_unlock(&dbc_tty_minors_lock);
+
        kfifo_free(&port->write_fifo);
        xhci_dbc_free_requests(&port->read_pool);
        xhci_dbc_free_requests(&port->read_queue);
@@ -468,33 +490,35 @@ static const struct dbc_driver dbc_driver = {
        .disconnect             = xhci_dbc_tty_unregister_device,
 };
 
-int xhci_dbc_tty_probe(struct xhci_hcd *xhci)
+int xhci_dbc_tty_probe(struct device *dev, void __iomem *base, struct xhci_hcd *xhci)
 {
-       struct xhci_dbc         *dbc = xhci->dbc;
+       struct xhci_dbc         *dbc;
        struct dbc_port         *port;
        int                     status;
 
-       /* dbc_tty_init will be called by module init() in the future */
-       status = dbc_tty_init();
-       if (status)
-               return status;
+       if (!dbc_tty_driver)
+               return -ENODEV;
 
        port = kzalloc(sizeof(*port), GFP_KERNEL);
-       if (!port) {
+       if (!port)
+               return -ENOMEM;
+
+       dbc = xhci_alloc_dbc(dev, base, &dbc_driver);
+
+       if (!dbc) {
                status = -ENOMEM;
-               goto out;
+               goto out2;
        }
 
-       dbc->driver = &dbc_driver;
        dbc->priv = port;
 
-
-       dbc_tty_driver->driver_state = port;
+       /* get rid of xhci once this is a real driver binding to a device */
+       xhci->dbc = dbc;
 
        return 0;
-out:
-       /* dbc_tty_exit will be called by module_exit() in the future */
-       dbc_tty_exit();
+out2:
+       kfree(port);
+
        return status;
 }
 
@@ -506,22 +530,22 @@ void xhci_dbc_tty_remove(struct xhci_dbc *dbc)
 {
        struct dbc_port         *port = dbc_to_port(dbc);
 
-       dbc->driver = NULL;
-       dbc->priv = NULL;
+       xhci_dbc_remove(dbc);
        kfree(port);
-
-       /* dbc_tty_exit will be called by  module_exit() in the future */
-       dbc_tty_exit();
 }
 
-static int dbc_tty_init(void)
+int dbc_tty_init(void)
 {
        int             ret;
 
-       dbc_tty_driver = tty_alloc_driver(1, TTY_DRIVER_REAL_RAW |
+       idr_init(&dbc_tty_minors);
+
+       dbc_tty_driver = tty_alloc_driver(64, TTY_DRIVER_REAL_RAW |
                                          TTY_DRIVER_DYNAMIC_DEV);
-       if (IS_ERR(dbc_tty_driver))
+       if (IS_ERR(dbc_tty_driver)) {
+               idr_destroy(&dbc_tty_minors);
                return PTR_ERR(dbc_tty_driver);
+       }
 
        dbc_tty_driver->driver_name = "dbc_serial";
        dbc_tty_driver->name = "ttyDBC";
@@ -540,15 +564,19 @@ static int dbc_tty_init(void)
        if (ret) {
                pr_err("Can't register dbc tty driver\n");
                tty_driver_kref_put(dbc_tty_driver);
+               idr_destroy(&dbc_tty_minors);
        }
+
        return ret;
 }
 
-static void dbc_tty_exit(void)
+void dbc_tty_exit(void)
 {
        if (dbc_tty_driver) {
                tty_unregister_driver(dbc_tty_driver);
                tty_driver_kref_put(dbc_tty_driver);
                dbc_tty_driver = NULL;
        }
+
+       idr_destroy(&dbc_tty_minors);
 }
index 0e31206..cb70d0b 100644 (file)
@@ -57,7 +57,7 @@ static struct xhci_segment *xhci_segment_alloc(struct xhci_hcd *xhci,
        /* If the cycle state is 0, set the cycle bit to 1 for all the TRBs */
        if (cycle_state == 0) {
                for (i = 0; i < TRBS_PER_SEGMENT; i++)
-                       seg->trbs[i].link.control |= cpu_to_le32(TRB_CYCLE);
+                       seg->trbs[i].link.control = cpu_to_le32(TRB_CYCLE);
        }
        seg->dma = dma;
        seg->next = NULL;
@@ -433,8 +433,7 @@ int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring,
                                (TRBS_PER_SEGMENT - 1);
 
        /* Allocate number of segments we needed, or double the ring size */
-       num_segs = ring->num_segs > num_segs_needed ?
-                       ring->num_segs : num_segs_needed;
+       num_segs = max(ring->num_segs, num_segs_needed);
 
        ret = xhci_alloc_segments_for_ring(xhci, &first, &last,
                        num_segs, ring->cycle_state, ring->type,
@@ -1846,9 +1845,6 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci)
        xhci->event_ring = NULL;
        xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed event ring");
 
-       if (xhci->lpm_command)
-               xhci_free_command(xhci, xhci->lpm_command);
-       xhci->lpm_command = NULL;
        if (xhci->cmd_ring)
                xhci_ring_free(xhci, xhci->cmd_ring);
        xhci->cmd_ring = NULL;
@@ -2488,10 +2484,6 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
                        "// Setting command ring address to 0x%016llx", val_64);
        xhci_write_64(xhci, val_64, &xhci->op_regs->cmd_ring);
 
-       xhci->lpm_command = xhci_alloc_command_with_ctx(xhci, true, flags);
-       if (!xhci->lpm_command)
-               goto fail;
-
        /* Reserve one command ring TRB for disabling LPM.
         * Since the USB core grabs the shared usb_bus bandwidth mutex before
         * disabling LPM, we only need to reserve one TRB for all devices.
index edbfa82..f3139ce 100644 (file)
@@ -248,7 +248,6 @@ create_sch_ep(struct xhci_hcd_mtk *mtk, struct usb_device *udev,
        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)
@@ -262,9 +261,9 @@ create_sch_ep(struct xhci_hcd_mtk *mtk, struct usb_device *udev,
        else
                len_bw_budget_table = 1;
 
-       mem_size = sizeof(struct mu3h_sch_ep_info) +
-                       len_bw_budget_table * sizeof(u32);
-       sch_ep = kzalloc(mem_size, GFP_KERNEL);
+       sch_ep = kzalloc(struct_size(sch_ep, bw_budget_table,
+                                    len_bw_budget_table),
+                        GFP_KERNEL);
        if (!sch_ep)
                return ERR_PTR(-ENOMEM);
 
index 91738af..b1045f5 100644 (file)
 #define WC0_SSUSB0_CDEN                BIT(6)
 #define WC0_IS_SPM_EN          BIT(1)
 
+/* mt8195 */
+#define PERI_WK_CTRL0_8195     0x04
+#define WC0_IS_P_95            BIT(30) /* polarity */
+#define WC0_IS_C_95(x)         ((u32)(((x) & 0x7) << 27))
+#define WC0_IS_EN_P3_95                BIT(26)
+#define WC0_IS_EN_P2_95                BIT(25)
+#define WC0_IS_EN_P1_95                BIT(24)
+
+#define PERI_WK_CTRL1_8195     0x20
+#define WC1_IS_C_95(x)         ((u32)(((x) & 0xf) << 28))
+#define WC1_IS_P_95            BIT(12)
+#define WC1_IS_EN_P0_95                BIT(6)
+
 /* mt2712 etc */
 #define PERI_SSUSB_SPM_CTRL    0x0
 #define SSC_IP_SLEEP_EN        BIT(4)
@@ -105,6 +118,10 @@ enum ssusb_uwk_vers {
        SSUSB_UWK_V2,
        SSUSB_UWK_V1_1 = 101,   /* specific revision 1.01 */
        SSUSB_UWK_V1_2,         /* specific revision 1.2 */
+       SSUSB_UWK_V1_3,         /* mt8195 IP0 */
+       SSUSB_UWK_V1_4,         /* mt8195 IP1 */
+       SSUSB_UWK_V1_5,         /* mt8195 IP2 */
+       SSUSB_UWK_V1_6,         /* mt8195 IP3 */
 };
 
 /*
@@ -308,6 +325,26 @@ static void usb_wakeup_ip_sleep_set(struct xhci_hcd_mtk *mtk, bool enable)
                msk = WC0_SSUSB0_CDEN | WC0_IS_SPM_EN;
                val = enable ? msk : 0;
                break;
+       case SSUSB_UWK_V1_3:
+               reg = mtk->uwk_reg_base + PERI_WK_CTRL1_8195;
+               msk = WC1_IS_EN_P0_95 | WC1_IS_C_95(0xf) | WC1_IS_P_95;
+               val = enable ? (WC1_IS_EN_P0_95 | WC1_IS_C_95(0x1)) : 0;
+               break;
+       case SSUSB_UWK_V1_4:
+               reg = mtk->uwk_reg_base + PERI_WK_CTRL0_8195;
+               msk = WC0_IS_EN_P1_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95;
+               val = enable ? (WC0_IS_EN_P1_95 | WC0_IS_C_95(0x1)) : 0;
+               break;
+       case SSUSB_UWK_V1_5:
+               reg = mtk->uwk_reg_base + PERI_WK_CTRL0_8195;
+               msk = WC0_IS_EN_P2_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95;
+               val = enable ? (WC0_IS_EN_P2_95 | WC0_IS_C_95(0x1)) : 0;
+               break;
+       case SSUSB_UWK_V1_6:
+               reg = mtk->uwk_reg_base + PERI_WK_CTRL0_8195;
+               msk = WC0_IS_EN_P3_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95;
+               val = enable ? (WC0_IS_EN_P3_95 | WC0_IS_C_95(0x1)) : 0;
+               break;
        case SSUSB_UWK_V2:
                reg = mtk->uwk_reg_base + PERI_SSUSB_SPM_CTRL;
                msk = SSC_IP_SLEEP_EN | SSC_SPM_INT_EN;
@@ -364,29 +401,14 @@ static int xhci_mtk_clks_get(struct xhci_hcd_mtk *mtk)
        return devm_clk_bulk_get_optional(mtk->dev, BULK_CLKS_NUM, clks);
 }
 
-static int xhci_mtk_ldos_enable(struct xhci_hcd_mtk *mtk)
+static int xhci_mtk_vregs_get(struct xhci_hcd_mtk *mtk)
 {
-       int ret;
+       struct regulator_bulk_data *supplies = mtk->supplies;
 
-       ret = regulator_enable(mtk->vbus);
-       if (ret) {
-               dev_err(mtk->dev, "failed to enable vbus\n");
-               return ret;
-       }
+       supplies[0].supply = "vbus";
+       supplies[1].supply = "vusb33";
 
-       ret = regulator_enable(mtk->vusb33);
-       if (ret) {
-               dev_err(mtk->dev, "failed to enable vusb33\n");
-               regulator_disable(mtk->vbus);
-               return ret;
-       }
-       return 0;
-}
-
-static void xhci_mtk_ldos_disable(struct xhci_hcd_mtk *mtk)
-{
-       regulator_disable(mtk->vbus);
-       regulator_disable(mtk->vusb33);
+       return devm_regulator_bulk_get(mtk->dev, BULK_VREGS_NUM, supplies);
 }
 
 static void xhci_mtk_quirks(struct device *dev, struct xhci_hcd *xhci)
@@ -476,17 +498,10 @@ static int xhci_mtk_probe(struct platform_device *pdev)
                return -ENOMEM;
 
        mtk->dev = dev;
-       mtk->vbus = devm_regulator_get(dev, "vbus");
-       if (IS_ERR(mtk->vbus)) {
-               dev_err(dev, "fail to get vbus\n");
-               return PTR_ERR(mtk->vbus);
-       }
 
-       mtk->vusb33 = devm_regulator_get(dev, "vusb33");
-       if (IS_ERR(mtk->vusb33)) {
-               dev_err(dev, "fail to get vusb33\n");
-               return PTR_ERR(mtk->vusb33);
-       }
+       ret = xhci_mtk_vregs_get(mtk);
+       if (ret)
+               return dev_err_probe(dev, ret, "Failed to get regulators\n");
 
        ret = xhci_mtk_clks_get(mtk);
        if (ret)
@@ -527,7 +542,7 @@ static int xhci_mtk_probe(struct platform_device *pdev)
        pm_runtime_enable(dev);
        pm_runtime_get_sync(dev);
 
-       ret = xhci_mtk_ldos_enable(mtk);
+       ret = regulator_bulk_enable(BULK_VREGS_NUM, mtk->supplies);
        if (ret)
                goto disable_pm;
 
@@ -636,7 +651,7 @@ disable_clk:
        clk_bulk_disable_unprepare(BULK_CLKS_NUM, mtk->clks);
 
 disable_ldos:
-       xhci_mtk_ldos_disable(mtk);
+       regulator_bulk_disable(BULK_VREGS_NUM, mtk->supplies);
 
 disable_pm:
        pm_runtime_put_noidle(dev);
@@ -664,7 +679,7 @@ static int xhci_mtk_remove(struct platform_device *pdev)
        usb_put_hcd(hcd);
        xhci_mtk_sch_exit(mtk);
        clk_bulk_disable_unprepare(BULK_CLKS_NUM, mtk->clks);
-       xhci_mtk_ldos_disable(mtk);
+       regulator_bulk_disable(BULK_VREGS_NUM, mtk->supplies);
 
        pm_runtime_disable(dev);
        pm_runtime_put_noidle(dev);
index 4b1ea89..ffd4b49 100644 (file)
 
 #include <linux/clk.h>
 #include <linux/hashtable.h>
+#include <linux/regulator/consumer.h>
 
 #include "xhci.h"
 
 #define BULK_CLKS_NUM  5
+#define BULK_VREGS_NUM 2
 
 /* support at most 64 ep, use 32 size hash table */
 #define SCH_EP_HASH_BITS       5
@@ -150,9 +152,8 @@ struct xhci_hcd_mtk {
        int num_u3_ports;
        int u2p_dis_msk;
        int u3p_dis_msk;
-       struct regulator *vusb33;
-       struct regulator *vbus;
        struct clk_bulk_data clks[BULK_CLKS_NUM];
+       struct regulator_bulk_data supplies[BULK_VREGS_NUM];
        unsigned int has_ippc:1;
        unsigned int lpm_support:1;
        unsigned int u2_lpm_disable:1;
index dc570ce..8094da3 100644 (file)
@@ -226,20 +226,13 @@ static int xhci_plat_probe(struct platform_device *pdev)
        if (!sysdev)
                sysdev = &pdev->dev;
 
-       /* Try to set 64-bit DMA first */
        if (WARN_ON(!sysdev->dma_mask))
                /* Platform did not initialize dma_mask */
-               ret = dma_coerce_mask_and_coherent(sysdev,
-                                                  DMA_BIT_MASK(64));
+               ret = dma_coerce_mask_and_coherent(sysdev, DMA_BIT_MASK(64));
        else
                ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(64));
-
-       /* If seting 64-bit DMA mask fails, fall back to 32-bit DMA mask */
-       if (ret) {
-               ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(32));
-               if (ret)
-                       return ret;
-       }
+       if (ret)
+               return ret;
 
        pm_runtime_set_active(&pdev->dev);
        pm_runtime_enable(&pdev->dev);
index 2d37854..a1c781f 100644 (file)
@@ -695,7 +695,7 @@ int xhci_run(struct usb_hcd *hcd)
        xhci_dbg_trace(xhci, trace_xhci_dbg_init,
                        "Finished xhci_run for USB2 roothub");
 
-       xhci_dbc_init(xhci);
+       xhci_create_dbc_dev(xhci);
 
        xhci_debugfs_init(xhci);
 
@@ -725,7 +725,7 @@ static void xhci_stop(struct usb_hcd *hcd)
                return;
        }
 
-       xhci_dbc_exit(xhci);
+       xhci_remove_dbc_dev(xhci);
 
        spin_lock_irq(&xhci->lock);
        xhci->xhc_state |= XHCI_STATE_HALTED;
@@ -3160,8 +3160,6 @@ rescan:
 
        ep_index = xhci_get_endpoint_index(&host_ep->desc);
        ep = &vdev->eps[ep_index];
-       if (!ep)
-               goto done;
 
        /* wait for hub_tt_work to finish clearing hub TT */
        if (ep->ep_state & EP_CLEARING_TT) {
@@ -3219,8 +3217,6 @@ static void xhci_endpoint_reset(struct usb_hcd *hcd,
                return;
        ep_index = xhci_get_endpoint_index(&host_ep->desc);
        ep = &vdev->eps[ep_index];
-       if (!ep)
-               return;
 
        /* Bail out if toggle is already being cleared by a endpoint reset */
        spin_lock_irqsave(&xhci->lock, flags);
@@ -4354,6 +4350,10 @@ static int __maybe_unused xhci_change_max_exit_latency(struct xhci_hcd *xhci,
        unsigned long flags;
        int ret;
 
+       command = xhci_alloc_command_with_ctx(xhci, true, GFP_KERNEL);
+       if (!command)
+               return -ENOMEM;
+
        spin_lock_irqsave(&xhci->lock, flags);
 
        virt_dev = xhci->devs[udev->slot_id];
@@ -4370,10 +4370,10 @@ static int __maybe_unused xhci_change_max_exit_latency(struct xhci_hcd *xhci,
        }
 
        /* Attempt to issue an Evaluate Context command to change the MEL. */
-       command = xhci->lpm_command;
        ctrl_ctx = xhci_get_input_control_ctx(command->in_ctx);
        if (!ctrl_ctx) {
                spin_unlock_irqrestore(&xhci->lock, flags);
+               xhci_free_command(xhci, command);
                xhci_warn(xhci, "%s: Could not get input context, bad type.\n",
                                __func__);
                return -ENOMEM;
@@ -4400,6 +4400,9 @@ static int __maybe_unused xhci_change_max_exit_latency(struct xhci_hcd *xhci,
                virt_dev->current_mel = max_exit_latency;
                spin_unlock_irqrestore(&xhci->lock, flags);
        }
+
+       xhci_free_command(xhci, command);
+
        return ret;
 }
 
@@ -4520,18 +4523,8 @@ static int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd,
                        exit_latency = xhci_besl_encoding[hird];
                        spin_unlock_irqrestore(&xhci->lock, flags);
 
-                       /* USB 3.0 code dedicate one xhci->lpm_command->in_ctx
-                        * input context for link powermanagement evaluate
-                        * context commands. It is protected by hcd->bandwidth
-                        * mutex and is shared by all devices. We need to set
-                        * the max ext latency in USB 2 BESL LPM as well, so
-                        * use the same mutex and xhci_change_max_exit_latency()
-                        */
-                       mutex_lock(hcd->bandwidth_mutex);
                        ret = xhci_change_max_exit_latency(xhci, udev,
                                                           exit_latency);
-                       mutex_unlock(hcd->bandwidth_mutex);
-
                        if (ret < 0)
                                return ret;
                        spin_lock_irqsave(&xhci->lock, flags);
@@ -4559,9 +4552,7 @@ static int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd,
                readl(pm_addr);
                if (udev->usb2_hw_lpm_besl_capable) {
                        spin_unlock_irqrestore(&xhci->lock, flags);
-                       mutex_lock(hcd->bandwidth_mutex);
                        xhci_change_max_exit_latency(xhci, udev, 0);
-                       mutex_unlock(hcd->bandwidth_mutex);
                        readl_poll_timeout(ports[port_num]->addr, pm_val,
                                           (pm_val & PORT_PLS_MASK) == XDEV_U0,
                                           100, 10000);
@@ -5505,6 +5496,7 @@ static int __init xhci_hcd_init(void)
                return -ENODEV;
 
        xhci_debugfs_create_root();
+       xhci_dbc_init();
 
        return 0;
 }
@@ -5516,6 +5508,7 @@ static int __init xhci_hcd_init(void)
 static void __exit xhci_hcd_fini(void)
 {
        xhci_debugfs_remove_root();
+       xhci_dbc_exit();
 }
 
 module_init(xhci_hcd_init);
index 5a75fe5..8a0026e 100644 (file)
@@ -1812,8 +1812,6 @@ struct xhci_hcd {
        /* slot enabling and address device helpers */
        /* these are not thread safe so use mutex */
        struct mutex mutex;
-       /* For USB 3.0 LPM enable/disable. */
-       struct xhci_command             *lpm_command;
        /* Internal mirror of the HW's dcbaa */
        struct xhci_virt_device *devs[MAX_HC_SLOTS];
        /* For keeping track of bandwidth domains per roothub. */
index 8f11443..4c5ddbd 100644 (file)
@@ -137,6 +137,17 @@ config USB_APPLEDISPLAY
          Say Y here if you want to control the backlight of Apple Cinema
          Displays over USB. This driver provides a sysfs interface.
 
+config USB_QCOM_EUD
+       tristate "QCOM Embedded USB Debugger(EUD) Driver"
+       depends on ARCH_QCOM || COMPILE_TEST
+       select USB_ROLE_SWITCH
+       help
+         This module enables support for Qualcomm Technologies, Inc.
+         Embedded USB Debugger (EUD). The EUD is a control peripheral
+         which reports VBUS attach/detach events and has USB-based
+         debug and trace capabilities. On selecting m, the module name
+         that is built is qcom_eud.ko
+
 config APPLE_MFI_FASTCHARGE
        tristate "Fast charge control for iOS devices"
        select POWER_SUPPLY
index 5f4e598..35bdb4b 100644 (file)
@@ -18,6 +18,7 @@ obj-$(CONFIG_USB_ISIGHTFW)            += isight_firmware.o
 obj-$(CONFIG_USB_LCD)                  += usblcd.o
 obj-$(CONFIG_USB_LD)                   += ldusb.o
 obj-$(CONFIG_USB_LEGOTOWER)            += legousbtower.o
+obj-$(CONFIG_USB_QCOM_EUD)             += qcom_eud.o
 obj-$(CONFIG_USB_TEST)                 += usbtest.o
 obj-$(CONFIG_USB_EHSET_TEST_FIXTURE)    += ehset.o
 obj-$(CONFIG_USB_TRANCEVIBRATOR)       += trancevibrator.o
diff --git a/drivers/usb/misc/qcom_eud.c b/drivers/usb/misc/qcom_eud.c
new file mode 100644 (file)
index 0000000..f929bff
--- /dev/null
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/usb/role.h>
+
+#define EUD_REG_INT1_EN_MASK   0x0024
+#define EUD_REG_INT_STATUS_1   0x0044
+#define EUD_REG_CTL_OUT_1      0x0074
+#define EUD_REG_VBUS_INT_CLR   0x0080
+#define EUD_REG_CSR_EUD_EN     0x1014
+#define EUD_REG_SW_ATTACH_DET  0x1018
+#define EUD_REG_EUD_EN2        0x0000
+
+#define EUD_ENABLE             BIT(0)
+#define EUD_INT_PET_EUD        BIT(0)
+#define EUD_INT_VBUS           BIT(2)
+#define EUD_INT_SAFE_MODE      BIT(4)
+#define EUD_INT_ALL            (EUD_INT_VBUS | EUD_INT_SAFE_MODE)
+
+struct eud_chip {
+       struct device                   *dev;
+       struct usb_role_switch          *role_sw;
+       void __iomem                    *base;
+       void __iomem                    *mode_mgr;
+       unsigned int                    int_status;
+       int                             irq;
+       bool                            enabled;
+       bool                            usb_attached;
+};
+
+static int enable_eud(struct eud_chip *priv)
+{
+       writel(EUD_ENABLE, priv->base + EUD_REG_CSR_EUD_EN);
+       writel(EUD_INT_VBUS | EUD_INT_SAFE_MODE,
+                       priv->base + EUD_REG_INT1_EN_MASK);
+       writel(1, priv->mode_mgr + EUD_REG_EUD_EN2);
+
+       return usb_role_switch_set_role(priv->role_sw, USB_ROLE_DEVICE);
+}
+
+static void disable_eud(struct eud_chip *priv)
+{
+       writel(0, priv->base + EUD_REG_CSR_EUD_EN);
+       writel(0, priv->mode_mgr + EUD_REG_EUD_EN2);
+}
+
+static ssize_t enable_show(struct device *dev,
+               struct device_attribute *attr, char *buf)
+{
+       struct eud_chip *chip = dev_get_drvdata(dev);
+
+       return sysfs_emit(buf, "%d\n", chip->enabled);
+}
+
+static ssize_t enable_store(struct device *dev,
+               struct device_attribute *attr,
+               const char *buf, size_t count)
+{
+       struct eud_chip *chip = dev_get_drvdata(dev);
+       bool enable;
+       int ret;
+
+       if (kstrtobool(buf, &enable))
+               return -EINVAL;
+
+       if (enable) {
+               ret = enable_eud(chip);
+               if (!ret)
+                       chip->enabled = enable;
+               else
+                       disable_eud(chip);
+       } else {
+               disable_eud(chip);
+       }
+
+       return count;
+}
+
+static DEVICE_ATTR_RW(enable);
+
+static struct attribute *eud_attrs[] = {
+       &dev_attr_enable.attr,
+       NULL,
+};
+ATTRIBUTE_GROUPS(eud);
+
+static void usb_attach_detach(struct eud_chip *chip)
+{
+       u32 reg;
+
+       /* read ctl_out_1[4] to find USB attach or detach event */
+       reg = readl(chip->base + EUD_REG_CTL_OUT_1);
+       chip->usb_attached = reg & EUD_INT_SAFE_MODE;
+}
+
+static void pet_eud(struct eud_chip *chip)
+{
+       u32 reg;
+       int ret;
+
+       /* When the EUD_INT_PET_EUD in SW_ATTACH_DET is set, the cable has been
+        * disconnected and we need to detach the pet to check if EUD is in safe
+        * mode before attaching again.
+        */
+       reg = readl(chip->base + EUD_REG_SW_ATTACH_DET);
+       if (reg & EUD_INT_PET_EUD) {
+               /* Detach & Attach pet for EUD */
+               writel(0, chip->base + EUD_REG_SW_ATTACH_DET);
+               /* Delay to make sure detach pet is done before attach pet */
+               ret = readl_poll_timeout(chip->base + EUD_REG_SW_ATTACH_DET,
+                                       reg, (reg == 0), 1, 100);
+               if (ret) {
+                       dev_err(chip->dev, "Detach pet failed\n");
+                       return;
+               }
+       }
+       /* Attach pet for EUD */
+       writel(EUD_INT_PET_EUD, chip->base + EUD_REG_SW_ATTACH_DET);
+}
+
+static irqreturn_t handle_eud_irq(int irq, void *data)
+{
+       struct eud_chip *chip = data;
+       u32 reg;
+
+       reg = readl(chip->base + EUD_REG_INT_STATUS_1);
+       switch (reg & EUD_INT_ALL) {
+       case EUD_INT_VBUS:
+               usb_attach_detach(chip);
+               return IRQ_WAKE_THREAD;
+       case EUD_INT_SAFE_MODE:
+               pet_eud(chip);
+               return IRQ_HANDLED;
+       default:
+               return IRQ_NONE;
+       }
+}
+
+static irqreturn_t handle_eud_irq_thread(int irq, void *data)
+{
+       struct eud_chip *chip = data;
+       int ret;
+
+       if (chip->usb_attached)
+               ret = usb_role_switch_set_role(chip->role_sw, USB_ROLE_DEVICE);
+       else
+               ret = usb_role_switch_set_role(chip->role_sw, USB_ROLE_HOST);
+       if (ret)
+               dev_err(chip->dev, "failed to set role switch\n");
+
+       /* set and clear vbus_int_clr[0] to clear interrupt */
+       writel(BIT(0), chip->base + EUD_REG_VBUS_INT_CLR);
+       writel(0, chip->base + EUD_REG_VBUS_INT_CLR);
+
+       return IRQ_HANDLED;
+}
+
+static void eud_role_switch_release(void *data)
+{
+       struct eud_chip *chip = data;
+
+       usb_role_switch_put(chip->role_sw);
+}
+
+static int eud_probe(struct platform_device *pdev)
+{
+       struct eud_chip *chip;
+       int ret;
+
+       chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+       if (!chip)
+               return -ENOMEM;
+
+       chip->dev = &pdev->dev;
+
+       ret = devm_add_action_or_reset(chip->dev, eud_role_switch_release, chip);
+       if (ret)
+               return dev_err_probe(chip->dev, ret,
+                               "failed to add role switch release action\n");
+
+       chip->role_sw = usb_role_switch_get(&pdev->dev);
+       if (IS_ERR(chip->role_sw))
+               return dev_err_probe(chip->dev, PTR_ERR(chip->role_sw),
+                                       "failed to get role switch\n");
+
+       chip->base = devm_platform_ioremap_resource(pdev, 0);
+       if (IS_ERR(chip->base))
+               return PTR_ERR(chip->base);
+
+       chip->mode_mgr = devm_platform_ioremap_resource(pdev, 1);
+       if (IS_ERR(chip->mode_mgr))
+               return PTR_ERR(chip->mode_mgr);
+
+       chip->irq = platform_get_irq(pdev, 0);
+       ret = devm_request_threaded_irq(&pdev->dev, chip->irq, handle_eud_irq,
+                       handle_eud_irq_thread, IRQF_ONESHOT, NULL, chip);
+       if (ret)
+               return dev_err_probe(chip->dev, ret, "failed to allocate irq\n");
+
+       enable_irq_wake(chip->irq);
+
+       platform_set_drvdata(pdev, chip);
+
+       return 0;
+}
+
+static int eud_remove(struct platform_device *pdev)
+{
+       struct eud_chip *chip = platform_get_drvdata(pdev);
+
+       if (chip->enabled)
+               disable_eud(chip);
+
+       device_init_wakeup(&pdev->dev, false);
+       disable_irq_wake(chip->irq);
+
+       return 0;
+}
+
+static const struct of_device_id eud_dt_match[] = {
+       { .compatible = "qcom,sc7280-eud" },
+       { }
+};
+MODULE_DEVICE_TABLE(of, eud_dt_match);
+
+static struct platform_driver eud_driver = {
+       .probe  = eud_probe,
+       .remove = eud_remove,
+       .driver = {
+               .name = "qcom_eud",
+               .dev_groups = eud_groups,
+               .of_match_table = eud_dt_match,
+       },
+};
+module_platform_driver(eud_driver);
+
+MODULE_DESCRIPTION("QTI EUD driver");
+MODULE_LICENSE("GPL v2");
index ab480f3..8f92121 100644 (file)
@@ -52,6 +52,18 @@ source "drivers/usb/typec/ucsi/Kconfig"
 
 source "drivers/usb/typec/tipd/Kconfig"
 
+config TYPEC_RT1719
+       tristate "Richtek RT1719 Sink Only Type-C controller driver"
+       depends on USB_ROLE_SWITCH || !USB_ROLE_SWITCH
+       depends on I2C
+       select REGMAP_I2C
+       help
+         Say Y or M here if your system has Richtek RT1719 sink only
+         Type-C port controller driver.
+
+         If you choose to build this driver as a dynamically linked module, the
+         module will be called rt1719.ko
+
 config TYPEC_HD3SS3220
        tristate "TI HD3SS3220 Type-C DRP Port controller driver"
        depends on I2C
@@ -88,6 +100,16 @@ config TYPEC_QCOM_PMIC
          It will also enable the VBUS output to connected devices when a
          DFP connection is made.
 
+config TYPEC_WUSB3801
+       tristate "Willsemi WUSB3801 Type-C port controller driver"
+       depends on I2C
+       select REGMAP_I2C
+       help
+         Say Y or M here if your system has a WUSB3801 Type-C port controller.
+
+         If you choose to build this driver as a dynamically linked module, the
+         module will be called wusb3801.ko.
+
 source "drivers/usb/typec/mux/Kconfig"
 
 source "drivers/usb/typec/altmodes/Kconfig"
index 57870a2..43626ac 100644 (file)
@@ -9,4 +9,6 @@ obj-$(CONFIG_TYPEC_TPS6598X)    += tipd/
 obj-$(CONFIG_TYPEC_HD3SS3220)  += hd3ss3220.o
 obj-$(CONFIG_TYPEC_QCOM_PMIC)  += qcom-pmic-typec.o
 obj-$(CONFIG_TYPEC_STUSB160X)  += stusb160x.o
+obj-$(CONFIG_TYPEC_RT1719)     += rt1719.o
+obj-$(CONFIG_TYPEC_WUSB3801)   += wusb3801.o
 obj-$(CONFIG_TYPEC)            += mux/
index 45a6f0c..ee0e520 100644 (file)
@@ -1894,6 +1894,49 @@ void *typec_get_drvdata(struct typec_port *port)
 }
 EXPORT_SYMBOL_GPL(typec_get_drvdata);
 
+int typec_get_fw_cap(struct typec_capability *cap,
+                    struct fwnode_handle *fwnode)
+{
+       const char *cap_str;
+       int ret;
+
+       cap->fwnode = fwnode;
+
+       ret = fwnode_property_read_string(fwnode, "power-role", &cap_str);
+       if (ret < 0)
+               return ret;
+
+       ret = typec_find_port_power_role(cap_str);
+       if (ret < 0)
+               return ret;
+       cap->type = ret;
+
+       /* USB data support is optional */
+       ret = fwnode_property_read_string(fwnode, "data-role", &cap_str);
+       if (ret == 0) {
+               ret = typec_find_port_data_role(cap_str);
+               if (ret < 0)
+                       return ret;
+               cap->data = ret;
+       }
+
+       /* Get the preferred power role for a DRP */
+       if (cap->type == TYPEC_PORT_DRP) {
+               cap->prefer_role = TYPEC_NO_PREFERRED_ROLE;
+
+               ret = fwnode_property_read_string(fwnode, "try-power-role", &cap_str);
+               if (ret == 0) {
+                       ret = typec_find_power_role(cap_str);
+                       if (ret < 0)
+                               return ret;
+                       cap->prefer_role = ret;
+               }
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(typec_get_fw_cap);
+
 /**
  * typec_port_register_altmode - Register USB Type-C Port Alternate Mode
  * @port: USB Type-C Port that supports the alternate mode
diff --git a/drivers/usb/typec/rt1719.c b/drivers/usb/typec/rt1719.c
new file mode 100644 (file)
index 0000000..f1b698e
--- /dev/null
@@ -0,0 +1,961 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bitfield.h>
+#include <linux/completion.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/usb/pd.h>
+#include <linux/usb/role.h>
+#include <linux/usb/typec.h>
+
+#define RT1719_REG_TXCTRL1     0x03
+#define RT1719_REG_TXCTRL2     0x04
+#define RT1719_REG_POLICYINFO  0x0E
+#define RT1719_REG_SRCPDO1     0x11
+#define RT1719_REG_MASKS       0x2D
+#define RT1719_REG_EVENTS      0x33
+#define RT1719_REG_STATS       0x37
+#define RT1719_REG_PSELINFO    0x3C
+#define RT1719_REG_USBSETINFO  0x3E
+#define RT1719_REG_VENID       0x82
+
+#define RT1719_UNIQUE_PID      0x1719
+#define RT1719_REQDRSWAP_MASK  BIT(7)
+#define RT1719_EVALMODE_MASK   BIT(4)
+#define RT1719_REQSRCPDO_MASK  GENMASK(2, 0)
+#define RT1719_TXSPDOREQ_MASK  BIT(7)
+#define RT1719_INT_DRSW_ACCEPT BIT(23)
+#define RT1719_INT_RX_SRCCAP   BIT(21)
+#define RT1719_INT_VBUS_DCT    BIT(6)
+#define RT1719_INT_VBUS_PRESENT        BIT(5)
+#define RT1719_INT_PE_SNK_RDY  BIT(2)
+#define RT1719_CC1_STAT                GENMASK(9, 8)
+#define RT1719_CC2_STAT                GENMASK(11, 10)
+#define RT1719_POLARITY_MASK   BIT(23)
+#define RT1719_DATAROLE_MASK   BIT(22)
+#define RT1719_PDSPECREV_MASK  GENMASK(21, 20)
+#define RT1719_SPDOSEL_MASK    GENMASK(18, 16)
+#define RT1719_SPDONUM_MASK    GENMASK(15, 13)
+#define RT1719_ATTACH_VBUS     BIT(12)
+#define RT1719_ATTACH_DBG      BIT(10)
+#define RT1719_ATTACH_SNK      BIT(9)
+#define RT1719_ATTACHDEV_MASK  (RT1719_ATTACH_VBUS | RT1719_ATTACH_DBG | \
+                                RT1719_ATTACH_SNK)
+#define RT1719_PE_EXP_CONTRACT BIT(2)
+#define RT1719_PSEL_SUPPORT    BIT(15)
+#define RT1719_TBLSEL_MASK     BIT(6)
+#define RT1719_LATPSEL_MASK    GENMASK(5, 0)
+#define RT1719_USBINFO_MASK    GENMASK(1, 0)
+#define RT1719_USB_DFPUFP      3
+#define RT1719_MAX_SRCPDO      7
+
+enum {
+       SNK_PWR_OPEN = 0,
+       SNK_PWR_DEF,
+       SNK_PWR_1P5A,
+       SNK_PWR_3A
+};
+
+enum {
+       USBPD_SPECREV_1_0 = 0,
+       USBPD_SPECREV_2_0,
+       USBPD_SPECREV_3_0
+};
+
+enum rt1719_snkcap {
+       RT1719_SNKCAP_5V = 0,
+       RT1719_SNKCAP_9V,
+       RT1719_SNKCAP_12V,
+       RT1719_SNKCAP_15V,
+       RT1719_SNKCAP_20V,
+       RT1719_MAX_SNKCAP
+};
+
+struct rt1719_psel_cap {
+       u8 lomask;
+       u8 himask;
+       u32 milliwatt;
+       u32 milliamp;
+};
+
+struct rt1719_data {
+       struct device *dev;
+       struct regmap *regmap;
+       struct typec_port *port;
+       struct usb_role_switch *role_sw;
+       struct power_supply *psy;
+       struct typec_partner *partner;
+       struct power_supply_desc psy_desc;
+       struct usb_pd_identity partner_ident;
+       struct typec_partner_desc partner_desc;
+       struct completion req_completion;
+       enum power_supply_usb_type usb_type;
+       bool attached;
+       bool pd_capable;
+       bool drswap_support;
+       u32 voltage;
+       u32 req_voltage;
+       u32 max_current;
+       u32 op_current;
+       u32 spdos[RT1719_MAX_SRCPDO];
+       u16 snkcaps[RT1719_MAX_SNKCAP];
+       int spdo_num;
+       int spdo_sel;
+       u32 conn_info;
+       u16 conn_stat;
+};
+
+static const enum power_supply_usb_type rt1719_psy_usb_types[] = {
+       POWER_SUPPLY_USB_TYPE_C,
+       POWER_SUPPLY_USB_TYPE_PD,
+       POWER_SUPPLY_USB_TYPE_PD_PPS
+};
+
+static const enum power_supply_property rt1719_psy_properties[] = {
+       POWER_SUPPLY_PROP_ONLINE,
+       POWER_SUPPLY_PROP_USB_TYPE,
+       POWER_SUPPLY_PROP_VOLTAGE_NOW,
+       POWER_SUPPLY_PROP_CURRENT_MAX,
+       POWER_SUPPLY_PROP_CURRENT_NOW
+};
+
+static int rt1719_read16(struct rt1719_data *data, unsigned int reg, u16 *val)
+{
+       __le16 regval;
+       int ret;
+
+       ret = regmap_raw_read(data->regmap, reg, &regval, sizeof(regval));
+       if (ret)
+               return ret;
+
+       *val = le16_to_cpu(regval);
+       return 0;
+}
+
+static int rt1719_read32(struct rt1719_data *data, unsigned int reg, u32 *val)
+{
+       __le32 regval;
+       int ret;
+
+       ret = regmap_raw_read(data->regmap, reg, &regval, sizeof(regval));
+       if (ret)
+               return ret;
+
+       *val = le32_to_cpu(regval);
+       return 0;
+}
+
+static int rt1719_write32(struct rt1719_data *data, unsigned int reg, u32 val)
+{
+       __le32 regval = cpu_to_le32(val);
+
+       return regmap_raw_write(data->regmap, reg, &regval, sizeof(regval));
+}
+
+static enum typec_pwr_opmode rt1719_get_pwr_opmode(u32 conn, u16 stat)
+{
+       u16 cc1, cc2, cc_stat;
+
+       cc1 = FIELD_GET(RT1719_CC1_STAT, stat);
+       cc2 = FIELD_GET(RT1719_CC2_STAT, stat);
+
+       if (conn & RT1719_ATTACH_SNK) {
+               if (conn & RT1719_POLARITY_MASK)
+                       cc_stat = cc2;
+               else
+                       cc_stat = cc1;
+
+               switch (cc_stat) {
+               case SNK_PWR_3A:
+                       return TYPEC_PWR_MODE_3_0A;
+               case SNK_PWR_1P5A:
+                       return TYPEC_PWR_MODE_1_5A;
+               }
+       } else if (conn & RT1719_ATTACH_DBG) {
+               if ((cc1 == SNK_PWR_1P5A && cc2 == SNK_PWR_DEF) ||
+                   (cc1 == SNK_PWR_DEF && cc2 == SNK_PWR_1P5A))
+                       return TYPEC_PWR_MODE_1_5A;
+               else if ((cc1 == SNK_PWR_3A && cc2 == SNK_PWR_DEF) ||
+                        (cc1 == SNK_PWR_DEF && cc2 == SNK_PWR_3A))
+                       return TYPEC_PWR_MODE_3_0A;
+       }
+
+       return TYPEC_PWR_MODE_USB;
+}
+
+static enum typec_data_role rt1719_get_data_role(u32 conn)
+{
+       if (conn & RT1719_DATAROLE_MASK)
+               return TYPEC_HOST;
+       return TYPEC_DEVICE;
+}
+
+static void rt1719_set_data_role(struct rt1719_data *data,
+                                enum typec_data_role data_role,
+                                bool attached)
+{
+       enum usb_role usb_role = USB_ROLE_NONE;
+
+       if (attached) {
+               if (data_role == TYPEC_HOST)
+                       usb_role = USB_ROLE_HOST;
+               else
+                       usb_role = USB_ROLE_DEVICE;
+       }
+
+       usb_role_switch_set_role(data->role_sw, usb_role);
+       typec_set_data_role(data->port, data_role);
+}
+
+static void rt1719_update_data_role(struct rt1719_data *data)
+{
+       if (!data->attached)
+               return;
+
+       rt1719_set_data_role(data, rt1719_get_data_role(data->conn_info), true);
+}
+
+static void rt1719_register_partner(struct rt1719_data *data)
+{
+       u16 spec_rev = 0;
+
+       if (data->pd_capable) {
+               u32 rev;
+
+               rev = FIELD_GET(RT1719_PDSPECREV_MASK, data->conn_info);
+               switch (rev) {
+               case USBPD_SPECREV_3_0:
+                       spec_rev = 0x0300;
+                       break;
+               case USBPD_SPECREV_2_0:
+                       spec_rev = 0x0200;
+                       break;
+               default:
+                       spec_rev = 0x0100;
+                       break;
+               }
+       }
+
+       /* Just to prevent multiple times attach */
+       if (data->partner)
+               typec_unregister_partner(data->partner);
+
+       memset(&data->partner_ident, 0, sizeof(data->partner_ident));
+       data->partner_desc.usb_pd = data->pd_capable;
+       data->partner_desc.pd_revision = spec_rev;
+
+       if (data->conn_info & RT1719_ATTACH_DBG)
+               data->partner_desc.accessory = TYPEC_ACCESSORY_DEBUG;
+       else
+               data->partner_desc.accessory = TYPEC_ACCESSORY_NONE;
+
+       data->partner = typec_register_partner(data->port, &data->partner_desc);
+}
+
+static void rt1719_attach(struct rt1719_data *data)
+{
+       enum typec_pwr_opmode pwr_opmode;
+       enum typec_data_role data_role;
+       u32 volt = 5000, curr = 500;
+
+       if (!(data->conn_info & RT1719_ATTACHDEV_MASK))
+               return;
+
+       pwr_opmode = rt1719_get_pwr_opmode(data->conn_info, data->conn_stat);
+       data_role = rt1719_get_data_role(data->conn_info);
+
+       typec_set_pwr_opmode(data->port, pwr_opmode);
+       rt1719_set_data_role(data, data_role, true);
+
+       if (data->conn_info & RT1719_ATTACH_SNK)
+               rt1719_register_partner(data);
+
+       if (pwr_opmode == TYPEC_PWR_MODE_3_0A)
+               curr = 3000;
+       else if (pwr_opmode == TYPEC_PWR_MODE_1_5A)
+               curr = 1500;
+
+       data->voltage = volt * 1000;
+       data->max_current = data->op_current = curr * 1000;
+       data->attached = true;
+
+       power_supply_changed(data->psy);
+}
+
+static void rt1719_detach(struct rt1719_data *data)
+{
+       if (!data->attached || (data->conn_info & RT1719_ATTACHDEV_MASK))
+               return;
+
+       typec_unregister_partner(data->partner);
+       data->partner = NULL;
+
+       typec_set_pwr_opmode(data->port, TYPEC_PWR_MODE_USB);
+       rt1719_set_data_role(data, TYPEC_DEVICE, false);
+
+       memset32(data->spdos, 0, RT1719_MAX_SRCPDO);
+       data->spdo_num = 0;
+       data->voltage = data->max_current = data->op_current = 0;
+       data->attached = data->pd_capable = false;
+
+       data->usb_type = POWER_SUPPLY_USB_TYPE_C;
+
+       power_supply_changed(data->psy);
+}
+
+static void rt1719_update_operating_status(struct rt1719_data *data)
+{
+       enum power_supply_usb_type usb_type = POWER_SUPPLY_USB_TYPE_PD;
+       u32 voltage, max_current, op_current;
+       int i, snk_sel;
+
+       for (i = 0; i < data->spdo_num; i++) {
+               u32 pdo = data->spdos[i];
+               enum pd_pdo_type type = pdo_type(pdo);
+
+               if (type == PDO_TYPE_APDO) {
+                       usb_type = POWER_SUPPLY_USB_TYPE_PD_PPS;
+                       break;
+               }
+       }
+
+       data->spdo_sel = FIELD_GET(RT1719_SPDOSEL_MASK, data->conn_info);
+       if (data->spdo_sel <= 0)
+               return;
+
+       data->usb_type = usb_type;
+
+       voltage = pdo_fixed_voltage(data->spdos[data->spdo_sel - 1]);
+       max_current = pdo_max_current(data->spdos[data->spdo_sel - 1]);
+
+       switch (voltage) {
+       case 5000:
+               snk_sel = RT1719_SNKCAP_5V;
+               break;
+       case 9000:
+               snk_sel = RT1719_SNKCAP_9V;
+               break;
+       case 12000:
+               snk_sel = RT1719_SNKCAP_12V;
+               break;
+       case 15000:
+               snk_sel = RT1719_SNKCAP_15V;
+               break;
+       case 20000:
+               snk_sel = RT1719_SNKCAP_20V;
+               break;
+       default:
+               return;
+       }
+
+       op_current = min(max_current, pdo_max_current(data->snkcaps[snk_sel]));
+
+       /* covert mV/mA to uV/uA */
+       data->voltage = voltage * 1000;
+       data->max_current = max_current * 1000;
+       data->op_current = op_current * 1000;
+
+       power_supply_changed(data->psy);
+}
+
+static void rt1719_update_pwr_opmode(struct rt1719_data *data)
+{
+       if (!data->attached)
+               return;
+
+       if (!data->pd_capable) {
+               data->pd_capable = true;
+
+               typec_set_pwr_opmode(data->port, TYPEC_PWR_MODE_PD);
+               rt1719_register_partner(data);
+       }
+
+       rt1719_update_operating_status(data);
+}
+
+static void rt1719_update_source_pdos(struct rt1719_data *data)
+{
+       int spdo_num = FIELD_GET(RT1719_SPDONUM_MASK, data->conn_info);
+       __le32 src_pdos[RT1719_MAX_SRCPDO] = { };
+       int i, ret;
+
+       if (!data->attached)
+               return;
+
+       ret = regmap_raw_read(data->regmap, RT1719_REG_SRCPDO1, src_pdos,
+                             sizeof(__le32) * spdo_num);
+       if (ret)
+               return;
+
+       data->spdo_num = spdo_num;
+       for (i = 0; i < spdo_num; i++)
+               data->spdos[i] = le32_to_cpu(src_pdos[i]);
+}
+
+static int rt1719_dr_set(struct typec_port *port, enum typec_data_role role)
+{
+       struct rt1719_data *data = typec_get_drvdata(port);
+       enum typec_data_role cur_role;
+       int ret;
+
+       if (!data->attached || !data->pd_capable || !data->drswap_support)
+               return -EOPNOTSUPP;
+
+       if (data->spdo_num > 0 && !(data->spdos[0] & PDO_FIXED_DATA_SWAP))
+               return -EINVAL;
+
+       cur_role = rt1719_get_data_role(data->conn_info);
+       if (cur_role == role)
+               return 0;
+
+       ret = regmap_update_bits(data->regmap, RT1719_REG_TXCTRL1,
+                                RT1719_REQDRSWAP_MASK, RT1719_REQDRSWAP_MASK);
+       if (ret)
+               return ret;
+
+       reinit_completion(&data->req_completion);
+       ret = wait_for_completion_timeout(&data->req_completion,
+                                         msecs_to_jiffies(400));
+       if (ret == 0)
+               return -ETIMEDOUT;
+
+       cur_role = rt1719_get_data_role(data->conn_info);
+       if (cur_role != role)
+               return -EAGAIN;
+
+       rt1719_set_data_role(data, role, true);
+       return 0;
+}
+
+static const struct typec_operations rt1719_port_ops = {
+       .dr_set = rt1719_dr_set,
+};
+
+static int rt1719_usbpd_request_voltage(struct rt1719_data *data)
+{
+       u32 src_voltage;
+       int snk_sel, src_sel = -1;
+       int i, ret;
+
+       if (!data->attached || !data->pd_capable || data->spdo_sel <= 0)
+               return -EINVAL;
+
+       src_voltage = pdo_fixed_voltage(data->spdos[data->spdo_sel - 1]);
+       if (src_voltage == data->req_voltage)
+               return 0;
+
+       switch (data->req_voltage) {
+       case 5000:
+               snk_sel = RT1719_SNKCAP_5V;
+               break;
+       case 9000:
+               snk_sel = RT1719_SNKCAP_9V;
+               break;
+       case 12000:
+               snk_sel = RT1719_SNKCAP_12V;
+               break;
+       case 15000:
+               snk_sel = RT1719_SNKCAP_15V;
+               break;
+       case 20000:
+               snk_sel = RT1719_SNKCAP_20V;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if (!(data->snkcaps[snk_sel] & RT1719_PSEL_SUPPORT))
+               return -EINVAL;
+
+       for (i = 0; i < data->spdo_num; i++) {
+               enum pd_pdo_type type = pdo_type(data->spdos[i]);
+
+               if (type != PDO_TYPE_FIXED)
+                       continue;
+
+               src_voltage = pdo_fixed_voltage(data->spdos[i]);
+               if (src_voltage == data->req_voltage) {
+                       src_sel = i;
+                       break;
+               }
+       }
+
+       if (src_sel == -1)
+               return -EOPNOTSUPP;
+
+       ret = regmap_update_bits(data->regmap, RT1719_REG_TXCTRL1,
+                                RT1719_EVALMODE_MASK | RT1719_REQSRCPDO_MASK,
+                                RT1719_EVALMODE_MASK | (src_sel + 1));
+       ret |= regmap_update_bits(data->regmap, RT1719_REG_TXCTRL2,
+                                 RT1719_TXSPDOREQ_MASK, RT1719_TXSPDOREQ_MASK);
+       if (ret)
+               return ret;
+
+       reinit_completion(&data->req_completion);
+       ret = wait_for_completion_timeout(&data->req_completion,
+                                         msecs_to_jiffies(400));
+       if (!ret)
+               return -ETIMEDOUT;
+
+       return 0;
+}
+
+static int rt1719_psy_set_property(struct power_supply *psy,
+                                  enum power_supply_property psp,
+                                  const union power_supply_propval *val)
+{
+       struct rt1719_data *data = power_supply_get_drvdata(psy);
+
+       if (psp == POWER_SUPPLY_PROP_VOLTAGE_NOW) {
+               data->req_voltage = val->intval / 1000;
+               return rt1719_usbpd_request_voltage(data);
+       }
+
+       return -EINVAL;
+}
+
+static int rt1719_psy_get_property(struct power_supply *psy,
+                                  enum power_supply_property psp,
+                                  union power_supply_propval *val)
+{
+       struct rt1719_data *data = power_supply_get_drvdata(psy);
+       int ret = 0;
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_ONLINE:
+               val->intval = data->attached ? 1 : 0;
+               break;
+       case POWER_SUPPLY_PROP_USB_TYPE:
+               val->intval = data->usb_type;
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               val->intval = data->voltage;
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_MAX:
+               val->intval = data->max_current;
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_NOW:
+               val->intval = data->op_current;
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+       return ret;
+}
+
+static int rt1719_psy_property_is_writeable(struct power_supply *psy,
+                                           enum power_supply_property psp)
+{
+       if (psp == POWER_SUPPLY_PROP_VOLTAGE_NOW)
+               return 1;
+       return 0;
+}
+
+static int devm_rt1719_psy_register(struct rt1719_data *data)
+{
+       struct power_supply_config psy_cfg = { };
+       char *psy_name;
+
+       psy_cfg.fwnode = dev_fwnode(data->dev);
+       psy_cfg.drv_data = data;
+
+       psy_name = devm_kasprintf(data->dev, GFP_KERNEL, "rt1719-source-psy-%s",
+                                 dev_name(data->dev));
+       if (!psy_name)
+               return -ENOMEM;
+
+       data->psy_desc.name = psy_name;
+       data->psy_desc.type = POWER_SUPPLY_TYPE_USB;
+       data->psy_desc.usb_types = rt1719_psy_usb_types;
+       data->psy_desc.num_usb_types = ARRAY_SIZE(rt1719_psy_usb_types);
+       data->psy_desc.properties = rt1719_psy_properties;
+       data->psy_desc.num_properties = ARRAY_SIZE(rt1719_psy_properties);
+       data->psy_desc.get_property = rt1719_psy_get_property;
+       data->psy_desc.set_property = rt1719_psy_set_property;
+       data->psy_desc.property_is_writeable = rt1719_psy_property_is_writeable;
+
+       data->usb_type = POWER_SUPPLY_USB_TYPE_C;
+
+       data->psy = devm_power_supply_register(data->dev, &data->psy_desc,
+                                              &psy_cfg);
+
+       return PTR_ERR_OR_ZERO(data->psy);
+}
+
+static irqreturn_t rt1719_irq_handler(int irq, void *priv)
+{
+       struct rt1719_data *data = priv;
+       u32 events, conn_info;
+       u16 conn_stat;
+       int ret;
+
+       ret = rt1719_read32(data, RT1719_REG_EVENTS, &events);
+       ret |= rt1719_read32(data, RT1719_REG_POLICYINFO, &conn_info);
+       ret |= rt1719_read16(data, RT1719_REG_STATS, &conn_stat);
+       if (ret)
+               return IRQ_NONE;
+
+       data->conn_info = conn_info;
+       data->conn_stat = conn_stat;
+
+       events &= (RT1719_INT_DRSW_ACCEPT | RT1719_INT_RX_SRCCAP |
+                  RT1719_INT_VBUS_PRESENT | RT1719_INT_VBUS_DCT |
+                  RT1719_INT_PE_SNK_RDY);
+
+       if (events & RT1719_INT_DRSW_ACCEPT)
+               rt1719_update_data_role(data);
+
+       if (events & RT1719_INT_VBUS_PRESENT)
+               rt1719_attach(data);
+
+       if (events & RT1719_INT_VBUS_DCT)
+               rt1719_detach(data);
+
+       if (events & RT1719_INT_RX_SRCCAP)
+               rt1719_update_source_pdos(data);
+
+       if (events & RT1719_INT_PE_SNK_RDY) {
+               complete(&data->req_completion);
+               rt1719_update_pwr_opmode(data);
+       }
+
+       /* Write 1 to clear already handled events */
+       rt1719_write32(data, RT1719_REG_EVENTS, events);
+
+       return IRQ_HANDLED;
+}
+
+static int rt1719_irq_init(struct rt1719_data *data)
+{
+       struct i2c_client *i2c = to_i2c_client(data->dev);
+       u32 irq_enable;
+       int ret;
+
+       irq_enable = RT1719_INT_DRSW_ACCEPT | RT1719_INT_RX_SRCCAP |
+                    RT1719_INT_VBUS_DCT | RT1719_INT_VBUS_PRESENT |
+                    RT1719_INT_PE_SNK_RDY;
+
+       ret = rt1719_write32(data, RT1719_REG_MASKS, irq_enable);
+       if (ret) {
+               dev_err(&i2c->dev, "Failed to config irq enable\n");
+               return ret;
+       }
+
+       return devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL,
+                                        rt1719_irq_handler, IRQF_ONESHOT,
+                                        dev_name(&i2c->dev), data);
+}
+
+static int rt1719_init_attach_state(struct rt1719_data *data)
+{
+       u32 conn_info, irq_clear;
+       u16 conn_stat;
+       int ret;
+
+       irq_clear = RT1719_INT_DRSW_ACCEPT | RT1719_INT_RX_SRCCAP |
+                   RT1719_INT_VBUS_DCT | RT1719_INT_VBUS_PRESENT |
+                   RT1719_INT_PE_SNK_RDY;
+
+       ret = rt1719_read32(data, RT1719_REG_POLICYINFO, &conn_info);
+       ret |= rt1719_read16(data, RT1719_REG_STATS, &conn_stat);
+       ret |= rt1719_write32(data, RT1719_REG_EVENTS, irq_clear);
+       if (ret)
+               return ret;
+
+       data->conn_info = conn_info;
+       data->conn_stat = conn_stat;
+
+       if (conn_info & RT1719_ATTACHDEV_MASK)
+               rt1719_attach(data);
+
+       if (conn_info & RT1719_PE_EXP_CONTRACT) {
+               rt1719_update_source_pdos(data);
+               rt1719_update_pwr_opmode(data);
+       }
+
+       return 0;
+}
+
+#define RT1719_PSEL_CAPINFO(_lomask, _milliwatt, _himask, _milliamp) { \
+       .lomask         = _lomask, \
+       .milliwatt      = _milliwatt, \
+       .himask         = _himask, \
+       .milliamp       = _milliamp, \
+}
+
+static const struct rt1719_psel_cap rt1719_psel_caps[] = {
+       RT1719_PSEL_CAPINFO(0x18, 75000, 0x10, 5000),
+       RT1719_PSEL_CAPINFO(0x18, 60000, 0x10, 4500),
+       RT1719_PSEL_CAPINFO(0x18, 45000, 0x10, 4000),
+       RT1719_PSEL_CAPINFO(0x18, 30000, 0x10, 3500),
+       RT1719_PSEL_CAPINFO(0x18, 25000, 0x10, 3000),
+       RT1719_PSEL_CAPINFO(0x18, 20000, 0x10, 2500),
+       RT1719_PSEL_CAPINFO(0x18, 15000, 0x10, 2000),
+       RT1719_PSEL_CAPINFO(0x18, 10000, 0x10, 1000),
+       RT1719_PSEL_CAPINFO(0x1C, 60000, 0x1F, 5000),
+       RT1719_PSEL_CAPINFO(0x1C, 45000, 0x1F, 4500),
+       RT1719_PSEL_CAPINFO(0x1C, 30000, 0x1F, 4000),
+       RT1719_PSEL_CAPINFO(0x1C, 24000, 0x1F, 3500),
+       RT1719_PSEL_CAPINFO(0x1C, 15000, 0x1F, 3000),
+       RT1719_PSEL_CAPINFO(0x1C, 10000, 0x1F, 2500),
+       RT1719_PSEL_CAPINFO(0x0C, 60000, 0x1F, 2000),
+       RT1719_PSEL_CAPINFO(0x0C, 45000, 0x1F, 1000),
+       RT1719_PSEL_CAPINFO(0x0C, 36000, 0x08, 5000),
+       RT1719_PSEL_CAPINFO(0x0C, 30000, 0x08, 4500),
+       RT1719_PSEL_CAPINFO(0x0C, 24000, 0x08, 4000),
+       RT1719_PSEL_CAPINFO(0x0C, 15000, 0x08, 3500),
+       RT1719_PSEL_CAPINFO(0x0C, 10000, 0x08, 3000),
+       RT1719_PSEL_CAPINFO(0x1E, 45000, 0x08, 2500),
+       RT1719_PSEL_CAPINFO(0x1E, 36000, 0x08, 2000),
+       RT1719_PSEL_CAPINFO(0x1E, 27000, 0x08, 1500),
+       RT1719_PSEL_CAPINFO(0x1E, 20000, 0x08, 1000),
+       RT1719_PSEL_CAPINFO(0x1E, 15000, 0x0F, 5000),
+       RT1719_PSEL_CAPINFO(0x1E, 9000, 0x0F, 4500),
+       RT1719_PSEL_CAPINFO(0x0E, 45000, 0x0F, 4000),
+       RT1719_PSEL_CAPINFO(0x0E, 36000, 0x0F, 3500),
+       RT1719_PSEL_CAPINFO(0x0E, 27000, 0x0F, 3000),
+       RT1719_PSEL_CAPINFO(0x0E, 20000, 0x0F, 2500),
+       RT1719_PSEL_CAPINFO(0x0E, 15000, 0x0F, 2000),
+       RT1719_PSEL_CAPINFO(0x0E, 9000, 0x0F, 1500),
+       RT1719_PSEL_CAPINFO(0x06, 45000, 0x0F, 1000),
+       RT1719_PSEL_CAPINFO(0x06, 36000, 0x0F, 500),
+       RT1719_PSEL_CAPINFO(0x06, 27000, 0x04, 5000),
+       RT1719_PSEL_CAPINFO(0x06, 24000, 0x04, 4500),
+       RT1719_PSEL_CAPINFO(0x06, 18000, 0x04, 4000),
+       RT1719_PSEL_CAPINFO(0x06, 12000, 0x04, 3500),
+       RT1719_PSEL_CAPINFO(0x06, 9000, 0x04, 3000),
+       RT1719_PSEL_CAPINFO(0x1F, 25000, 0x04, 2500),
+       RT1719_PSEL_CAPINFO(0x1F, 20000, 0x04, 2000),
+       RT1719_PSEL_CAPINFO(0x1F, 15000, 0x04, 1500),
+       RT1719_PSEL_CAPINFO(0x1F, 10000, 0x04, 1000),
+       RT1719_PSEL_CAPINFO(0x1F, 7500, 0x07, 5000),
+       RT1719_PSEL_CAPINFO(0x0F, 25000, 0x07, 4500),
+       RT1719_PSEL_CAPINFO(0x0F, 20000, 0x07, 4000),
+       RT1719_PSEL_CAPINFO(0x0F, 15000, 0x07, 3500),
+       RT1719_PSEL_CAPINFO(0x0F, 10000, 0x07, 3000),
+       RT1719_PSEL_CAPINFO(0x0F, 7500, 0x07, 2500),
+       RT1719_PSEL_CAPINFO(0x07, 25000, 0x07, 2000),
+       RT1719_PSEL_CAPINFO(0x07, 20000, 0x07, 1500),
+       RT1719_PSEL_CAPINFO(0x07, 15000, 0x07, 1000),
+       RT1719_PSEL_CAPINFO(0x07, 10000, 0x07, 500),
+       RT1719_PSEL_CAPINFO(0x07, 7500, 0x03, 5000),
+       RT1719_PSEL_CAPINFO(0x03, 25000, 0x03, 4500),
+       RT1719_PSEL_CAPINFO(0x03, 20000, 0x03, 4000),
+       RT1719_PSEL_CAPINFO(0x03, 15000, 0x03, 3500),
+       RT1719_PSEL_CAPINFO(0x03, 10000, 0x03, 3000),
+       RT1719_PSEL_CAPINFO(0x03, 7500, 0x03, 2500),
+       RT1719_PSEL_CAPINFO(0x01, 15000, 0x03, 2000),
+       RT1719_PSEL_CAPINFO(0x01, 10000, 0x03, 1500),
+       RT1719_PSEL_CAPINFO(0x01, 7500, 0x03, 1000),
+       RT1719_PSEL_CAPINFO(0x01, 2500, 0x03, 500)
+};
+
+static u16 rt1719_gen_snkcap_by_current(const struct rt1719_psel_cap *psel_cap,
+                                       enum rt1719_snkcap capsel)
+{
+       u16 cap = RT1719_PSEL_SUPPORT;
+
+       if (!(psel_cap->himask & BIT(capsel)))
+               return 0;
+
+       cap |= psel_cap->milliamp / 10;
+       return cap;
+}
+
+static u16 rt1719_gen_snkcap_by_watt(const struct rt1719_psel_cap *psel_cap,
+                                    enum rt1719_snkcap capsel)
+{
+       u32 volt_div[RT1719_MAX_SNKCAP] = { 5, 9, 12, 15, 20 };
+       u16 cap = RT1719_PSEL_SUPPORT;
+
+       if (!(psel_cap->lomask & BIT(capsel)))
+               return 0;
+
+       cap |= min(psel_cap->milliwatt / volt_div[capsel], (u32)5000) / 10;
+       return cap;
+}
+
+static u16 rt1719_gen_snkcap(unsigned int pselinfo, enum rt1719_snkcap capsel)
+{
+       int psel = FIELD_GET(RT1719_LATPSEL_MASK, pselinfo);
+       const struct rt1719_psel_cap *psel_cap;
+       bool by_current = false;
+
+       if (pselinfo & RT1719_TBLSEL_MASK)
+               by_current = true;
+
+       psel_cap = rt1719_psel_caps + psel;
+       if (by_current)
+               return rt1719_gen_snkcap_by_current(psel_cap, capsel);
+
+       return rt1719_gen_snkcap_by_watt(psel_cap, capsel);
+}
+
+static int rt1719_get_caps(struct rt1719_data *data)
+{
+       unsigned int pselinfo, usbinfo;
+       int i, ret;
+
+       ret = regmap_read(data->regmap, RT1719_REG_PSELINFO, &pselinfo);
+       ret |= regmap_read(data->regmap, RT1719_REG_USBSETINFO, &usbinfo);
+       if (ret)
+               return ret;
+
+       for (i = 0; i < RT1719_MAX_SNKCAP; i++)
+               data->snkcaps[i] = rt1719_gen_snkcap(pselinfo, i);
+
+       usbinfo = FIELD_GET(RT1719_USBINFO_MASK, usbinfo);
+       if (usbinfo == RT1719_USB_DFPUFP)
+               data->drswap_support = true;
+
+       return 0;
+}
+
+static int rt1719_check_exist(struct rt1719_data *data)
+{
+       u16 pid;
+       int ret;
+
+       ret = rt1719_read16(data, RT1719_REG_VENID, &pid);
+       if (ret)
+               return ret;
+
+       if (pid != RT1719_UNIQUE_PID) {
+               dev_err(data->dev, "Incorrect PID 0x%04x\n", pid);
+               return -ENODEV;
+       }
+
+       return 0;
+}
+
+static const struct regmap_config rt1719_regmap_config = {
+       .reg_bits = 8,
+       .val_bits = 8,
+       .max_register = 0xff,
+};
+
+static int rt1719_probe(struct i2c_client *i2c)
+{
+       struct rt1719_data *data;
+       struct fwnode_handle *fwnode;
+       struct typec_capability typec_cap = { };
+       int ret;
+
+       data = devm_kzalloc(&i2c->dev, sizeof(*data), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
+       data->dev = &i2c->dev;
+       init_completion(&data->req_completion);
+
+       data->regmap = devm_regmap_init_i2c(i2c, &rt1719_regmap_config);
+       if (IS_ERR(data->regmap)) {
+               ret = PTR_ERR(data->regmap);
+               dev_err(&i2c->dev, "Failed to init regmap (%d)\n", ret);
+               return ret;
+       }
+
+       ret = rt1719_check_exist(data);
+       if (ret)
+               return ret;
+
+       ret = rt1719_get_caps(data);
+       if (ret)
+               return ret;
+
+       fwnode = device_get_named_child_node(&i2c->dev, "connector");
+       if (!fwnode)
+               return -ENODEV;
+
+       data->role_sw = fwnode_usb_role_switch_get(fwnode);
+       if (IS_ERR(data->role_sw)) {
+               ret = PTR_ERR(data->role_sw);
+               dev_err(&i2c->dev, "Failed to get usb role switch (%d)\n", ret);
+               goto err_fwnode_put;
+       }
+
+       ret = devm_rt1719_psy_register(data);
+       if (ret) {
+               dev_err(&i2c->dev, "Failed to register psy (%d)\n", ret);
+               goto err_role_put;
+       }
+
+       typec_cap.revision = USB_TYPEC_REV_1_2;
+       typec_cap.pd_revision = 0x300;  /* USB-PD spec release 3.0 */
+       typec_cap.type = TYPEC_PORT_SNK;
+       typec_cap.data = TYPEC_PORT_DRD;
+       typec_cap.ops = &rt1719_port_ops;
+       typec_cap.fwnode = fwnode;
+       typec_cap.driver_data = data;
+       typec_cap.accessory[0] = TYPEC_ACCESSORY_DEBUG;
+
+       data->partner_desc.identity = &data->partner_ident;
+
+       data->port = typec_register_port(&i2c->dev, &typec_cap);
+       if (IS_ERR(data->port)) {
+               ret = PTR_ERR(data->port);
+               dev_err(&i2c->dev, "Failed to register typec port (%d)\n", ret);
+               goto err_role_put;
+       }
+
+       ret = rt1719_init_attach_state(data);
+       if (ret) {
+               dev_err(&i2c->dev, "Failed to init attach state (%d)\n", ret);
+               goto err_role_put;
+       }
+
+       ret = rt1719_irq_init(data);
+       if (ret) {
+               dev_err(&i2c->dev, "Failed to init irq\n");
+               goto err_role_put;
+       }
+
+       fwnode_handle_put(fwnode);
+
+       i2c_set_clientdata(i2c, data);
+
+       return 0;
+
+err_role_put:
+       usb_role_switch_put(data->role_sw);
+err_fwnode_put:
+       fwnode_handle_put(fwnode);
+
+       return ret;
+}
+
+static int rt1719_remove(struct i2c_client *i2c)
+{
+       struct rt1719_data *data = i2c_get_clientdata(i2c);
+
+       typec_unregister_port(data->port);
+       usb_role_switch_put(data->role_sw);
+
+       return 0;
+}
+
+static const struct of_device_id __maybe_unused rt1719_device_table[] = {
+       { .compatible = "richtek,rt1719", },
+       { }
+};
+MODULE_DEVICE_TABLE(of, rt1719_device_table);
+
+static struct i2c_driver rt1719_driver = {
+       .driver = {
+               .name = "rt1719",
+               .of_match_table = rt1719_device_table,
+       },
+       .probe_new = rt1719_probe,
+       .remove = rt1719_remove,
+};
+module_i2c_driver(rt1719_driver);
+
+MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>");
+MODULE_DESCRIPTION("Richtek RT1719 Sink Only USBPD Controller Driver");
+MODULE_LICENSE("GPL v2");
index 5fce795..3bc2f4e 100644 (file)
@@ -5928,7 +5928,6 @@ 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;
 
@@ -5944,23 +5943,10 @@ static int tcpm_fw_get_caps(struct tcpm_port *port,
         */
        fw_devlink_purge_absent_suppliers(fwnode);
 
-       /* USB data support is optional */
-       ret = fwnode_property_read_string(fwnode, "data-role", &cap_str);
-       if (ret == 0) {
-               ret = typec_find_port_data_role(cap_str);
-               if (ret < 0)
-                       return ret;
-               port->typec_caps.data = ret;
-       }
-
-       ret = fwnode_property_read_string(fwnode, "power-role", &cap_str);
+       ret = typec_get_fw_cap(&port->typec_caps, fwnode);
        if (ret < 0)
                return ret;
 
-       ret = typec_find_port_power_role(cap_str);
-       if (ret < 0)
-               return ret;
-       port->typec_caps.type = ret;
        port->port_type = port->typec_caps.type;
        port->pd_supported = !fwnode_property_read_bool(fwnode, "pd-disable");
 
@@ -5997,14 +5983,6 @@ static int tcpm_fw_get_caps(struct tcpm_port *port,
        if (port->port_type == TYPEC_PORT_SRC)
                return 0;
 
-       /* Get the preferred power role for DRP */
-       ret = fwnode_property_read_string(fwnode, "try-power-role", &cap_str);
-       if (ret < 0)
-               return ret;
-
-       port->typec_caps.prefer_role = typec_find_power_role(cap_str);
-       if (port->typec_caps.prefer_role < 0)
-               return -EINVAL;
 sink:
        port->self_powered = fwnode_property_read_bool(fwnode, "self-powered");
 
diff --git a/drivers/usb/typec/wusb3801.c b/drivers/usb/typec/wusb3801.c
new file mode 100644 (file)
index 0000000..e63509f
--- /dev/null
@@ -0,0 +1,437 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Willsemi WUSB3801 Type-C port controller driver
+ *
+ * Copyright (C) 2022 Samuel Holland <samuel@sholland.org>
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/usb/typec.h>
+
+#define WUSB3801_REG_DEVICE_ID         0x01
+#define WUSB3801_REG_CTRL0             0x02
+#define WUSB3801_REG_INT               0x03
+#define WUSB3801_REG_STAT              0x04
+#define WUSB3801_REG_CTRL1             0x05
+#define WUSB3801_REG_TEST00            0x06
+#define WUSB3801_REG_TEST01            0x07
+#define WUSB3801_REG_TEST02            0x08
+#define WUSB3801_REG_TEST03            0x09
+#define WUSB3801_REG_TEST04            0x0a
+#define WUSB3801_REG_TEST05            0x0b
+#define WUSB3801_REG_TEST06            0x0c
+#define WUSB3801_REG_TEST07            0x0d
+#define WUSB3801_REG_TEST08            0x0e
+#define WUSB3801_REG_TEST09            0x0f
+#define WUSB3801_REG_TEST0A            0x10
+#define WUSB3801_REG_TEST0B            0x11
+#define WUSB3801_REG_TEST0C            0x12
+#define WUSB3801_REG_TEST0D            0x13
+#define WUSB3801_REG_TEST0E            0x14
+#define WUSB3801_REG_TEST0F            0x15
+#define WUSB3801_REG_TEST10            0x16
+#define WUSB3801_REG_TEST11            0x17
+#define WUSB3801_REG_TEST12            0x18
+
+#define WUSB3801_DEVICE_ID_VERSION_ID  GENMASK(7, 3)
+#define WUSB3801_DEVICE_ID_VENDOR_ID   GENMASK(2, 0)
+
+#define WUSB3801_CTRL0_DIS_ACC_SUPPORT BIT(7)
+#define WUSB3801_CTRL0_TRY             GENMASK(6, 5)
+#define WUSB3801_CTRL0_TRY_NONE                (0x0 << 5)
+#define WUSB3801_CTRL0_TRY_SNK         (0x1 << 5)
+#define WUSB3801_CTRL0_TRY_SRC         (0x2 << 5)
+#define WUSB3801_CTRL0_CURRENT         GENMASK(4, 3) /* SRC */
+#define WUSB3801_CTRL0_CURRENT_DEFAULT (0x0 << 3)
+#define WUSB3801_CTRL0_CURRENT_1_5A    (0x1 << 3)
+#define WUSB3801_CTRL0_CURRENT_3_0A    (0x2 << 3)
+#define WUSB3801_CTRL0_ROLE            GENMASK(2, 1)
+#define WUSB3801_CTRL0_ROLE_SNK                (0x0 << 1)
+#define WUSB3801_CTRL0_ROLE_SRC                (0x1 << 1)
+#define WUSB3801_CTRL0_ROLE_DRP                (0x2 << 1)
+#define WUSB3801_CTRL0_INT_MASK                BIT(0)
+
+#define WUSB3801_INT_ATTACHED          BIT(0)
+#define WUSB3801_INT_DETACHED          BIT(1)
+
+#define WUSB3801_STAT_VBUS_DETECTED    BIT(7)
+#define WUSB3801_STAT_CURRENT          GENMASK(6, 5) /* SNK */
+#define WUSB3801_STAT_CURRENT_STANDBY  (0x0 << 5)
+#define WUSB3801_STAT_CURRENT_DEFAULT  (0x1 << 5)
+#define WUSB3801_STAT_CURRENT_1_5A     (0x2 << 5)
+#define WUSB3801_STAT_CURRENT_3_0A     (0x3 << 5)
+#define WUSB3801_STAT_PARTNER          GENMASK(4, 2)
+#define WUSB3801_STAT_PARTNER_STANDBY  (0x0 << 2)
+#define WUSB3801_STAT_PARTNER_SNK      (0x1 << 2)
+#define WUSB3801_STAT_PARTNER_SRC      (0x2 << 2)
+#define WUSB3801_STAT_PARTNER_AUDIO    (0x3 << 2)
+#define WUSB3801_STAT_PARTNER_DEBUG    (0x4 << 2)
+#define WUSB3801_STAT_ORIENTATION      GENMASK(1, 0)
+#define WUSB3801_STAT_ORIENTATION_NONE (0x0 << 0)
+#define WUSB3801_STAT_ORIENTATION_CC1  (0x1 << 0)
+#define WUSB3801_STAT_ORIENTATION_CC2  (0x2 << 0)
+#define WUSB3801_STAT_ORIENTATION_BOTH (0x3 << 0)
+
+#define WUSB3801_CTRL1_SM_RESET                BIT(0)
+
+#define WUSB3801_TEST01_VENDOR_SUB_ID  (BIT(8) | BIT(6))
+
+#define WUSB3801_TEST02_FORCE_ERR_RCY  BIT(8)
+
+#define WUSB3801_TEST0A_WAIT_VBUS      BIT(5)
+
+struct wusb3801 {
+       struct typec_capability cap;
+       struct device           *dev;
+       struct typec_partner    *partner;
+       struct typec_port       *port;
+       struct regmap           *regmap;
+       struct regulator        *vbus_supply;
+       unsigned int            partner_type;
+       enum typec_port_type    port_type;
+       enum typec_pwr_opmode   pwr_opmode;
+       bool                    vbus_on;
+};
+
+static enum typec_role wusb3801_get_default_role(struct wusb3801 *wusb3801)
+{
+       switch (wusb3801->port_type) {
+       case TYPEC_PORT_SRC:
+               return TYPEC_SOURCE;
+       case TYPEC_PORT_SNK:
+               return TYPEC_SINK;
+       case TYPEC_PORT_DRP:
+       default:
+               if (wusb3801->cap.prefer_role == TYPEC_SOURCE)
+                       return TYPEC_SOURCE;
+               return TYPEC_SINK;
+       }
+}
+
+static int wusb3801_map_port_type(enum typec_port_type type)
+{
+       switch (type) {
+       case TYPEC_PORT_SRC:
+               return WUSB3801_CTRL0_ROLE_SRC;
+       case TYPEC_PORT_SNK:
+               return WUSB3801_CTRL0_ROLE_SNK;
+       case TYPEC_PORT_DRP:
+       default:
+               return WUSB3801_CTRL0_ROLE_DRP;
+       }
+}
+
+static int wusb3801_map_pwr_opmode(enum typec_pwr_opmode mode)
+{
+       switch (mode) {
+       case TYPEC_PWR_MODE_USB:
+       default:
+               return WUSB3801_CTRL0_CURRENT_DEFAULT;
+       case TYPEC_PWR_MODE_1_5A:
+               return WUSB3801_CTRL0_CURRENT_1_5A;
+       case TYPEC_PWR_MODE_3_0A:
+               return WUSB3801_CTRL0_CURRENT_3_0A;
+       }
+}
+
+static unsigned int wusb3801_map_try_role(int role)
+{
+       switch (role) {
+       case TYPEC_NO_PREFERRED_ROLE:
+       default:
+               return WUSB3801_CTRL0_TRY_NONE;
+       case TYPEC_SINK:
+               return WUSB3801_CTRL0_TRY_SNK;
+       case TYPEC_SOURCE:
+               return WUSB3801_CTRL0_TRY_SRC;
+       }
+}
+
+static enum typec_orientation wusb3801_unmap_orientation(unsigned int status)
+{
+       switch (status & WUSB3801_STAT_ORIENTATION) {
+       case WUSB3801_STAT_ORIENTATION_NONE:
+       case WUSB3801_STAT_ORIENTATION_BOTH:
+       default:
+               return TYPEC_ORIENTATION_NONE;
+       case WUSB3801_STAT_ORIENTATION_CC1:
+               return TYPEC_ORIENTATION_NORMAL;
+       case WUSB3801_STAT_ORIENTATION_CC2:
+               return TYPEC_ORIENTATION_REVERSE;
+       }
+}
+
+static enum typec_pwr_opmode wusb3801_unmap_pwr_opmode(unsigned int status)
+{
+       switch (status & WUSB3801_STAT_CURRENT) {
+       case WUSB3801_STAT_CURRENT_STANDBY:
+       case WUSB3801_STAT_CURRENT_DEFAULT:
+       default:
+               return TYPEC_PWR_MODE_USB;
+       case WUSB3801_STAT_CURRENT_1_5A:
+               return TYPEC_PWR_MODE_1_5A;
+       case WUSB3801_STAT_CURRENT_3_0A:
+               return TYPEC_PWR_MODE_3_0A;
+       }
+}
+
+static int wusb3801_try_role(struct typec_port *port, int role)
+{
+       struct wusb3801 *wusb3801 = typec_get_drvdata(port);
+
+       return regmap_update_bits(wusb3801->regmap, WUSB3801_REG_CTRL0,
+                                 WUSB3801_CTRL0_TRY,
+                                 wusb3801_map_try_role(role));
+}
+
+static int wusb3801_port_type_set(struct typec_port *port,
+                                 enum typec_port_type type)
+{
+       struct wusb3801 *wusb3801 = typec_get_drvdata(port);
+       int ret;
+
+       ret = regmap_update_bits(wusb3801->regmap, WUSB3801_REG_CTRL0,
+                                WUSB3801_CTRL0_ROLE,
+                                wusb3801_map_port_type(type));
+       if (ret)
+               return ret;
+
+       wusb3801->port_type = type;
+
+       return 0;
+}
+
+static const struct typec_operations wusb3801_typec_ops = {
+       .try_role       = wusb3801_try_role,
+       .port_type_set  = wusb3801_port_type_set,
+};
+
+static int wusb3801_hw_init(struct wusb3801 *wusb3801)
+{
+       return regmap_write(wusb3801->regmap, WUSB3801_REG_CTRL0,
+                           wusb3801_map_try_role(wusb3801->cap.prefer_role) |
+                           wusb3801_map_pwr_opmode(wusb3801->pwr_opmode) |
+                           wusb3801_map_port_type(wusb3801->port_type));
+}
+
+static void wusb3801_hw_update(struct wusb3801 *wusb3801)
+{
+       struct typec_port *port = wusb3801->port;
+       struct device *dev = wusb3801->dev;
+       unsigned int partner_type, status;
+       int ret;
+
+       ret = regmap_read(wusb3801->regmap, WUSB3801_REG_STAT, &status);
+       if (ret) {
+               dev_warn(dev, "Failed to read port status: %d\n", ret);
+               status = 0;
+       }
+       dev_dbg(dev, "status = 0x%02x\n", status);
+
+       partner_type = status & WUSB3801_STAT_PARTNER;
+
+       if (partner_type == WUSB3801_STAT_PARTNER_SNK) {
+               if (!wusb3801->vbus_on) {
+                       ret = regulator_enable(wusb3801->vbus_supply);
+                       if (ret)
+                               dev_warn(dev, "Failed to enable VBUS: %d\n", ret);
+                       wusb3801->vbus_on = true;
+               }
+       } else {
+               if (wusb3801->vbus_on) {
+                       regulator_disable(wusb3801->vbus_supply);
+                       wusb3801->vbus_on = false;
+               }
+       }
+
+       if (partner_type != wusb3801->partner_type) {
+               struct typec_partner_desc desc = {};
+               enum typec_data_role data_role;
+               enum typec_role pwr_role = wusb3801_get_default_role(wusb3801);
+
+               switch (partner_type) {
+               case WUSB3801_STAT_PARTNER_STANDBY:
+                       break;
+               case WUSB3801_STAT_PARTNER_SNK:
+                       pwr_role = TYPEC_SOURCE;
+                       break;
+               case WUSB3801_STAT_PARTNER_SRC:
+                       pwr_role = TYPEC_SINK;
+                       break;
+               case WUSB3801_STAT_PARTNER_AUDIO:
+                       desc.accessory = TYPEC_ACCESSORY_AUDIO;
+                       break;
+               case WUSB3801_STAT_PARTNER_DEBUG:
+                       desc.accessory = TYPEC_ACCESSORY_DEBUG;
+                       break;
+               }
+
+               if (wusb3801->partner) {
+                       typec_unregister_partner(wusb3801->partner);
+                       wusb3801->partner = NULL;
+               }
+
+               if (partner_type != WUSB3801_STAT_PARTNER_STANDBY) {
+                       wusb3801->partner = typec_register_partner(port, &desc);
+                       if (IS_ERR(wusb3801->partner))
+                               dev_err(dev, "Failed to register partner: %ld\n",
+                                       PTR_ERR(wusb3801->partner));
+               }
+
+               data_role = pwr_role == TYPEC_SOURCE ? TYPEC_HOST : TYPEC_DEVICE;
+               typec_set_data_role(port, data_role);
+               typec_set_pwr_role(port, pwr_role);
+               typec_set_vconn_role(port, pwr_role);
+       }
+
+       typec_set_pwr_opmode(wusb3801->port,
+                            partner_type == WUSB3801_STAT_PARTNER_SRC
+                               ? wusb3801_unmap_pwr_opmode(status)
+                               : wusb3801->pwr_opmode);
+       typec_set_orientation(wusb3801->port,
+                             wusb3801_unmap_orientation(status));
+
+       wusb3801->partner_type = partner_type;
+}
+
+static irqreturn_t wusb3801_irq(int irq, void *data)
+{
+       struct wusb3801 *wusb3801 = data;
+       unsigned int dummy;
+
+       /*
+        * The interrupt register must be read in order to clear the IRQ,
+        * but all of the useful information is in the status register.
+        */
+       regmap_read(wusb3801->regmap, WUSB3801_REG_INT, &dummy);
+
+       wusb3801_hw_update(wusb3801);
+
+       return IRQ_HANDLED;
+}
+
+static const struct regmap_config config = {
+       .reg_bits       = 8,
+       .val_bits       = 8,
+       .max_register   = WUSB3801_REG_TEST12,
+};
+
+static int wusb3801_probe(struct i2c_client *client)
+{
+       struct device *dev = &client->dev;
+       struct fwnode_handle *connector;
+       struct wusb3801 *wusb3801;
+       const char *cap_str;
+       int ret;
+
+       wusb3801 = devm_kzalloc(dev, sizeof(*wusb3801), GFP_KERNEL);
+       if (!wusb3801)
+               return -ENOMEM;
+
+       i2c_set_clientdata(client, wusb3801);
+
+       wusb3801->dev = dev;
+
+       wusb3801->regmap = devm_regmap_init_i2c(client, &config);
+       if (IS_ERR(wusb3801->regmap))
+               return PTR_ERR(wusb3801->regmap);
+
+       wusb3801->vbus_supply = devm_regulator_get(dev, "vbus");
+       if (IS_ERR(wusb3801->vbus_supply))
+               return PTR_ERR(wusb3801->vbus_supply);
+
+       connector = device_get_named_child_node(dev, "connector");
+       if (!connector)
+               return -ENODEV;
+
+       ret = typec_get_fw_cap(&wusb3801->cap, connector);
+       if (ret)
+               goto err_put_connector;
+       wusb3801->port_type = wusb3801->cap.type;
+
+       ret = fwnode_property_read_string(connector, "typec-power-opmode", &cap_str);
+       if (ret)
+               goto err_put_connector;
+
+       ret = typec_find_pwr_opmode(cap_str);
+       if (ret < 0 || ret == TYPEC_PWR_MODE_PD)
+               goto err_put_connector;
+       wusb3801->pwr_opmode = ret;
+
+       /* Initialize the hardware with the devicetree settings. */
+       ret = wusb3801_hw_init(wusb3801);
+       if (ret)
+               return ret;
+
+       wusb3801->cap.revision          = USB_TYPEC_REV_1_2;
+       wusb3801->cap.accessory[0]      = TYPEC_ACCESSORY_AUDIO;
+       wusb3801->cap.accessory[1]      = TYPEC_ACCESSORY_DEBUG;
+       wusb3801->cap.orientation_aware = true;
+       wusb3801->cap.driver_data       = wusb3801;
+       wusb3801->cap.ops               = &wusb3801_typec_ops;
+
+       wusb3801->port = typec_register_port(dev, &wusb3801->cap);
+       if (IS_ERR(wusb3801->port)) {
+               ret = PTR_ERR(wusb3801->port);
+               goto err_put_connector;
+       }
+
+       /* Initialize the port attributes from the hardware state. */
+       wusb3801_hw_update(wusb3801);
+
+       ret = request_threaded_irq(client->irq, NULL, wusb3801_irq,
+                                  IRQF_ONESHOT, dev_name(dev), wusb3801);
+       if (ret)
+               goto err_unregister_port;
+
+       fwnode_handle_put(connector);
+
+       return 0;
+
+err_unregister_port:
+       typec_unregister_port(wusb3801->port);
+err_put_connector:
+       fwnode_handle_put(connector);
+
+       return ret;
+}
+
+static int wusb3801_remove(struct i2c_client *client)
+{
+       struct wusb3801 *wusb3801 = i2c_get_clientdata(client);
+
+       free_irq(client->irq, wusb3801);
+
+       if (wusb3801->partner)
+               typec_unregister_partner(wusb3801->partner);
+       typec_unregister_port(wusb3801->port);
+
+       if (wusb3801->vbus_on)
+               regulator_disable(wusb3801->vbus_supply);
+
+       return 0;
+}
+
+static const struct of_device_id wusb3801_of_match[] = {
+       { .compatible = "willsemi,wusb3801" },
+       {}
+};
+MODULE_DEVICE_TABLE(of, wusb3801_of_match);
+
+static struct i2c_driver wusb3801_driver = {
+       .probe_new      = wusb3801_probe,
+       .remove         = wusb3801_remove,
+       .driver         = {
+               .name           = "wusb3801",
+               .of_match_table = wusb3801_of_match,
+       },
+};
+
+module_i2c_driver(wusb3801_driver);
+
+MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>");
+MODULE_DESCRIPTION("Willsemi WUSB3801 Type-C port controller driver");
+MODULE_LICENSE("GPL");
index 678faa8..d43252b 100644 (file)
@@ -26,7 +26,7 @@ static struct platform_driver vudc_driver = {
        },
 };
 
-static struct list_head vudc_devices = LIST_HEAD_INIT(vudc_devices);
+static LIST_HEAD(vudc_devices);
 
 static int __init init(void)
 {
index 7ba45a9..fdf737d 100644 (file)
@@ -295,6 +295,9 @@ int typec_set_mode(struct typec_port *port, int mode);
 
 void *typec_get_drvdata(struct typec_port *port);
 
+int typec_get_fw_cap(struct typec_capability *cap,
+                    struct fwnode_handle *fwnode);
+
 int typec_find_pwr_opmode(const char *name);
 int typec_find_orientation(const char *name);
 int typec_find_port_power_role(const char *name);