Merge tag 'usb-6.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
authorLinus Torvalds <torvalds@linux-foundation.org>
Sat, 4 Nov 2023 02:00:42 +0000 (16:00 -1000)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sat, 4 Nov 2023 02:00:42 +0000 (16:00 -1000)
Pull USB/Thunderbolt updates from Greg KH:
 "Here is the "big" set of USB and Thunderbolt changes for 6.7-rc1.
  Nothing really major in here, just lots of constant development for
  new hardware. Included in here are:

   - Thunderbolt (i.e. USB4) fixes for reported issues and support for
     new hardware types and devices

   - USB typec additions of new drivers and cleanups for some existing
     ones

   - xhci cleanups and expanded tracing support and some platform
     specific updates

   - USB "La Jolla Cove Adapter (LJCA)" support added, and the gpio,
     spi, and i2c drivers for that type of device (all acked by the
     respective subsystem maintainers.)

   - lots of USB gadget driver updates and cleanups

   - new USB dwc3 platforms supported, as well as other dwc3 fixes and
     cleanups

   - USB chipidea driver updates

   - other smaller driver cleanups and additions, full details in the
     shortlog

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

* tag 'usb-6.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (167 commits)
  usb: gadget: uvc: Add missing initialization of ssp config descriptor
  usb: storage: set 1.50 as the lower bcdDevice for older "Super Top" compatibility
  usb: raw-gadget: report suspend, resume, reset, and disconnect events
  usb: raw-gadget: don't disable device if usb_ep_queue fails
  usb: raw-gadget: properly handle interrupted requests
  usb:cdnsp: remove TRB_FLUSH_ENDPOINT command
  usb: gadget: aspeed_udc: Convert to platform remove callback returning void
  dt-bindings: usb: fsa4480: Add compatible for OCP96011
  usb: typec: fsa4480: Add support to swap SBU orientation
  dt-bindings: usb: fsa4480: Add data-lanes property to endpoint
  usb: typec: tcpm: Fix NULL pointer dereference in tcpm_pd_svdm()
  Revert "dt-bindings: usb: Add bindings for multiport properties on DWC3 controller"
  Revert "dt-bindings: usb: qcom,dwc3: Add bindings for SC8280 Multiport"
  thunderbolt: Fix one kernel-doc comment
  usb: gadget: f_ncm: Always set current gadget in ncm_bind()
  usb: core: Remove duplicated check in usb_hub_create_port_device
  usb: typec: tcpm: Add additional checks for contaminant
  arm64: dts: rockchip: rk3588s: Add USB3 host controller
  usb: dwc3: add optional PHY interface clocks
  dt-bindings: usb: add rk3588 compatible to rockchip,dwc3
  ...

150 files changed:
Documentation/ABI/testing/configfs-usb-gadget-uac2
Documentation/ABI/testing/sysfs-bus-usb
Documentation/ABI/testing/sysfs-class-usb_power_delivery
Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-usb3-uni-phy.yaml
Documentation/devicetree/bindings/phy/qcom,snps-eusb2-phy.yaml
Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml
Documentation/devicetree/bindings/usb/ci-hdrc-usb2.yaml
Documentation/devicetree/bindings/usb/fcs,fsa4480.yaml
Documentation/devicetree/bindings/usb/genesys,gl850g.yaml
Documentation/devicetree/bindings/usb/gpio-sbu-mux.yaml
Documentation/devicetree/bindings/usb/nxp,ptn36502.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/usb/qcom,dwc3.yaml
Documentation/devicetree/bindings/usb/realtek,rtd-dwc3.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml
Documentation/devicetree/bindings/usb/snps,dwc3.yaml
Documentation/devicetree/bindings/usb/ti,tps6598x.yaml
Documentation/devicetree/bindings/usb/vialab,vl817.yaml
Documentation/driver-api/usb/dma.rst
Documentation/usb/gadget-testing.rst
arch/arm64/boot/dts/qcom/sm8550-mtp.dts
arch/arm64/boot/dts/qcom/sm8550-qrd.dts
arch/arm64/boot/dts/rockchip/rk3588s.dtsi
drivers/gpio/Kconfig
drivers/gpio/gpio-ljca.c
drivers/i2c/busses/Kconfig
drivers/i2c/busses/Makefile
drivers/i2c/busses/i2c-ljca.c [new file with mode: 0644]
drivers/platform/chrome/cros_ec_typec.c
drivers/power/supply/tps65217_charger.c
drivers/spi/Kconfig
drivers/spi/Makefile
drivers/spi/spi-ljca.c [new file with mode: 0644]
drivers/thunderbolt/clx.c
drivers/thunderbolt/dma_test.c
drivers/thunderbolt/path.c
drivers/thunderbolt/quirks.c
drivers/thunderbolt/retimer.c
drivers/thunderbolt/switch.c
drivers/thunderbolt/tb.c
drivers/thunderbolt/tb.h
drivers/thunderbolt/tb_regs.h
drivers/thunderbolt/tunnel.c
drivers/thunderbolt/tunnel.h
drivers/thunderbolt/usb4.c
drivers/usb/Kconfig
drivers/usb/c67x00/c67x00-hcd.h
drivers/usb/cdns3/cdnsp-debug.h
drivers/usb/cdns3/cdnsp-gadget.c
drivers/usb/cdns3/cdnsp-gadget.h
drivers/usb/cdns3/cdnsp-ring.c
drivers/usb/chipidea/Kconfig
drivers/usb/chipidea/Makefile
drivers/usb/chipidea/ci_hdrc_npcm.c [new file with mode: 0644]
drivers/usb/chipidea/ci_hdrc_tegra.c
drivers/usb/chipidea/ci_hdrc_usb2.c
drivers/usb/chipidea/host.c
drivers/usb/chipidea/otg.c
drivers/usb/core/hcd-pci.c
drivers/usb/core/hub.c
drivers/usb/core/hub.h
drivers/usb/core/port.c
drivers/usb/dwc2/hcd.c
drivers/usb/dwc2/params.c
drivers/usb/dwc3/Kconfig
drivers/usb/dwc3/Makefile
drivers/usb/dwc3/core.c
drivers/usb/dwc3/core.h
drivers/usb/dwc3/dwc3-rtk.c [new file with mode: 0644]
drivers/usb/dwc3/dwc3-xilinx.c
drivers/usb/gadget/function/f_ncm.c
drivers/usb/gadget/function/f_uac2.c
drivers/usb/gadget/function/f_uvc.c
drivers/usb/gadget/function/u_ether.c
drivers/usb/gadget/function/u_uac2.h
drivers/usb/gadget/legacy/inode.c
drivers/usb/gadget/legacy/raw_gadget.c
drivers/usb/gadget/udc/aspeed_udc.c
drivers/usb/gadget/udc/at91_udc.c
drivers/usb/gadget/udc/core.c
drivers/usb/gadget/udc/fsl_qe_udc.c
drivers/usb/gadget/udc/fsl_udc_core.c
drivers/usb/gadget/udc/fusb300_udc.c
drivers/usb/gadget/udc/lpc32xx_udc.c
drivers/usb/gadget/udc/m66592-udc.c
drivers/usb/gadget/udc/r8a66597-udc.c
drivers/usb/host/pci-quirks.c
drivers/usb/host/pci-quirks.h
drivers/usb/host/xhci-debugfs.c
drivers/usb/host/xhci-ext-caps.h
drivers/usb/host/xhci-hub.c
drivers/usb/host/xhci-mem.c
drivers/usb/host/xhci-mtk-sch.c
drivers/usb/host/xhci-mtk.h
drivers/usb/host/xhci-pci.c
drivers/usb/host/xhci-plat.c
drivers/usb/host/xhci-ring.c
drivers/usb/host/xhci-trace.h
drivers/usb/host/xhci.c
drivers/usb/host/xhci.h
drivers/usb/misc/Kconfig
drivers/usb/misc/Makefile
drivers/usb/misc/onboard_usb_hub.c
drivers/usb/misc/onboard_usb_hub.h
drivers/usb/misc/usb-ljca.c [new file with mode: 0644]
drivers/usb/misc/usbtest.c
drivers/usb/mon/mon_main.c
drivers/usb/mtu3/mtu3_plat.c
drivers/usb/musb/da8xx.c
drivers/usb/musb/musb_dsps.c
drivers/usb/storage/uas-detect.h
drivers/usb/storage/uas.c
drivers/usb/storage/unusual_cypress.h
drivers/usb/storage/usb.c
drivers/usb/storage/usb.h
drivers/usb/storage/usual-tables.c
drivers/usb/typec/altmodes/displayport.c
drivers/usb/typec/anx7411.c
drivers/usb/typec/class.c
drivers/usb/typec/class.h
drivers/usb/typec/mux/Kconfig
drivers/usb/typec/mux/Makefile
drivers/usb/typec/mux/fsa4480.c
drivers/usb/typec/mux/intel_pmc_mux.c
drivers/usb/typec/mux/ptn36502.c [new file with mode: 0644]
drivers/usb/typec/pd.c
drivers/usb/typec/port-mapper.c
drivers/usb/typec/tcpm/tcpci_rt1711h.c
drivers/usb/typec/tcpm/tcpm.c
drivers/usb/typec/tipd/core.c
drivers/usb/typec/tipd/tps6598x.h
drivers/usb/typec/tipd/trace.h
drivers/usb/typec/ucsi/displayport.c
drivers/usb/typec/ucsi/ucsi_ccg.c
drivers/usb/typec/ucsi/ucsi_glink.c
drivers/usb/usbip/stub_dev.c
drivers/usb/usbip/vhci_hcd.c
include/linux/thunderbolt.h
include/linux/usb.h
include/linux/usb/chipidea.h
include/linux/usb/composite.h
include/linux/usb/gadget.h
include/linux/usb/hcd.h
include/linux/usb/ljca.h [new file with mode: 0644]
include/linux/usb/pd.h
include/linux/usb/pd_vdo.h
include/linux/usb/renesas_usbhs.h
include/linux/usb/typec.h
include/linux/usb/typec_dp.h
include/linux/usb/typec_tbt.h
include/uapi/linux/usb/raw_gadget.h

index 3371c39f651db5c698f40da037bb35a9eca75978..a2bf4fd82a5b7c3ec512256a39ae72dc8995f844 100644 (file)
@@ -35,4 +35,6 @@ Description:
                req_number              the number of pre-allocated requests
                                        for both capture and playback
                function_name           name of the interface
+               c_terminal_type         code of the capture terminal type
+               p_terminal_type         code of the playback terminal type
                =====================   =======================================
index a44bfe0200616cf0c32461ef6d0963c9c936a4fe..2b7108e2197756db86cfbd83aa3370457b4f4bf1 100644 (file)
@@ -313,6 +313,15 @@ Description:
                Inter-Chip SSIC devices support asymmetric lanes up to 4 lanes per
                direction. Devices before USB 3.2 are single lane (tx_lanes = 1)
 
+What:          /sys/bus/usb/devices/.../typec
+Date:          November 2023
+Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+               Symlink to the USB Type-C partner device. USB Type-C partner
+               represents the component that communicates over the
+               Configuration Channel (CC signal on USB Type-C connectors and
+               cables) with the local port.
+
 What:          /sys/bus/usb/devices/usbX/bAlternateSetting
 Description:
                The current interface alternate setting number, in decimal.
index 1bf9d1d7902ce4e93ee296ecc057dbedf9ae25b8..61d233c320ea7f6e2e81890f166c89f65e9b0e84 100644 (file)
@@ -124,6 +124,13 @@ Contact:   Heikki Krogerus <heikki.krogerus@linux.intel.com>
 Description:
                The voltage the supply supports in millivolts.
 
+What:          /sys/class/usb_power_delivery/.../source-capabilities/<position>:fixed_supply/peak_current
+Date:          October 2023
+Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+               This file shows the value of the Fixed Power Source Peak Current
+               Capability field.
+
 What:          /sys/class/usb_power_delivery/.../source-capabilities/<position>:fixed_supply/maximum_current
 Date:          May 2022
 Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
index f99fbbcd68fba1fd1c446e73a6d9fe2025f8cd15..d58dd744d5d27c1a4a3df2e11273bcb756338602 100644 (file)
@@ -20,6 +20,7 @@ properties:
       - qcom,qcm2290-qmp-usb3-phy
       - qcom,sa8775p-qmp-usb3-uni-phy
       - qcom,sc8280xp-qmp-usb3-uni-phy
+      - qcom,sdx75-qmp-usb3-uni-phy
       - qcom,sm6115-qmp-usb3-phy
 
   reg:
@@ -75,6 +76,7 @@ allOf:
           contains:
             enum:
               - qcom,ipq9574-qmp-usb3-phy
+              - qcom,sdx75-qmp-usb3-uni-phy
     then:
       properties:
         clock-names:
index c53bab107b6dd10bedaeace490c3babc02ab7428..c95828607ab6bc6b23217ebe5e380c60f2d224a1 100644 (file)
@@ -14,7 +14,12 @@ description:
 
 properties:
   compatible:
-    const: qcom,sm8550-snps-eusb2-phy
+    oneOf:
+      - items:
+          - enum:
+              - qcom,sdx75-snps-eusb2-phy
+          - const: qcom,sm8550-snps-eusb2-phy
+      - const: qcom,sm8550-snps-eusb2-phy
 
   reg:
     maxItems: 1
index bceb479f74c54922fb52ef382c154f3c258eda79..422921cf1f827fcce7d5fe51f3ed419a47d8a2a4 100644 (file)
@@ -35,6 +35,12 @@ properties:
   '#size-cells':
     const: 0
 
+  orientation-gpios:
+    description: Array of input gpios for the Type-C connector orientation indication.
+      The GPIO indication is used to detect the orientation of the Type-C connector.
+      The array should contain a gpio entry for each PMIC Glink connector, in reg order.
+      It is defined that GPIO active level means "CC2" or Reversed/Flipped orientation.
+
 patternProperties:
   '^connector@\d$':
     $ref: /schemas/connector/usb-connector.yaml#
@@ -44,6 +50,19 @@ patternProperties:
 required:
   - compatible
 
+allOf:
+  - if:
+      not:
+        properties:
+          compatible:
+            contains:
+              enum:
+                - qcom,sm8450-pmic-glink
+                - qcom,sm8550-pmic-glink
+    then:
+      properties:
+        orientation-gpios: false
+
 additionalProperties: false
 
 examples:
index 1394557517b1822d8502b8502160f0d6bc857b1a..b7e664f7395b33405bff1cb181e79fe38aeb378a 100644 (file)
@@ -15,7 +15,9 @@ properties:
     oneOf:
       - enum:
           - chipidea,usb2
+          - fsl,imx27-usb
           - lsi,zevio-usb
+          - nuvoton,npcm750-udc
           - nvidia,tegra20-ehci
           - nvidia,tegra20-udc
           - nvidia,tegra30-ehci
@@ -66,6 +68,10 @@ properties:
       - items:
           - const: xlnx,zynq-usb-2.20a
           - const: chipidea,usb2
+      - items:
+          - enum:
+              - nuvoton,npcm845-udc
+          - const: nuvoton,npcm750-udc
 
   reg:
     minItems: 1
@@ -388,6 +394,7 @@ allOf:
             enum:
               - chipidea,usb2
               - lsi,zevio-usb
+              - nuvoton,npcm750-udc
               - nvidia,tegra20-udc
               - nvidia,tegra30-udc
               - nvidia,tegra114-udc
index f6e7a5c1ff0b0998d00b49b8843ce90776b123a3..f9410eb76a621a2e6d1d98ab44f7bad3c37eb123 100644 (file)
@@ -11,8 +11,12 @@ maintainers:
 
 properties:
   compatible:
-    enum:
-      - fcs,fsa4480
+    oneOf:
+      - const: fcs,fsa4480
+      - items:
+          - enum:
+              - ocs,ocp96011
+          - const: fcs,fsa4480
 
   reg:
     maxItems: 1
@@ -32,10 +36,43 @@ properties:
     type: boolean
 
   port:
-    $ref: /schemas/graph.yaml#/properties/port
+    $ref: /schemas/graph.yaml#/$defs/port-base
     description:
       A port node to link the FSA4480 to a TypeC controller for the purpose of
       handling altmode muxing and orientation switching.
+    unevaluatedProperties: false
+
+    properties:
+      endpoint:
+        $ref: /schemas/graph.yaml#/$defs/endpoint-base
+        unevaluatedProperties: false
+
+        properties:
+          data-lanes:
+            $ref: /schemas/types.yaml#/definitions/uint32-array
+            description:
+              Specifies how the AUX+/- lines are connected to SBU1/2.
+            oneOf:
+              - items:
+                  - const: 0
+                  - const: 1
+                description: |
+                  Default AUX/SBU layout (FSA4480)
+                  - AUX+ connected to SBU2
+                  - AUX- connected to SBU1
+                  Default AUX/SBU layout (OCP96011)
+                  - AUX+ connected to SBU1
+                  - AUX- connected to SBU2
+              - items:
+                  - const: 1
+                  - const: 0
+                description: |
+                  Swapped AUX/SBU layout (FSA4480)
+                  - AUX+ connected to SBU1
+                  - AUX- connected to SBU2
+                  Swapped AUX/SBU layout (OCP96011)
+                  - AUX+ connected to SBU2
+                  - AUX- connected to SBU1
 
 required:
   - compatible
index d0927f6768a4822e6ae605fb9b4adb9281681ab2..ee08b9c3721f8998439258a10ef944ad11a8fccc 100644 (file)
@@ -4,7 +4,7 @@
 $id: http://devicetree.org/schemas/usb/genesys,gl850g.yaml#
 $schema: http://devicetree.org/meta-schemas/core.yaml#
 
-title: Genesys Logic GL850G USB 2.0 hub controller
+title: Genesys Logic USB hub controller
 
 maintainers:
   - Icenowy Zheng <uwu@icenowy.me>
@@ -18,6 +18,7 @@ properties:
       - usb5e3,608
       - usb5e3,610
       - usb5e3,620
+      - usb5e3,626
 
   reg: true
 
index f196beb826d8d34aad5bd8de4de90d44e1b38f61..d3b2b666ec2a4c694e2b5dcce15a03fd1adff191 100644 (file)
@@ -19,6 +19,7 @@ properties:
   compatible:
     items:
       - enum:
+          - nxp,cbdtu02043
           - onnn,fsusb43l10x
           - pericom,pi3usb102
       - const: gpio-sbu-mux
@@ -50,7 +51,6 @@ required:
   - compatible
   - enable-gpios
   - select-gpios
-  - mode-switch
   - orientation-switch
   - port
 
diff --git a/Documentation/devicetree/bindings/usb/nxp,ptn36502.yaml b/Documentation/devicetree/bindings/usb/nxp,ptn36502.yaml
new file mode 100644 (file)
index 0000000..eee548a
--- /dev/null
@@ -0,0 +1,94 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/nxp,ptn36502.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NXP PTN36502 Type-C USB 3.1 Gen 1 and DisplayPort v1.2 combo redriver
+
+maintainers:
+  - Luca Weiss <luca.weiss@fairphone.com>
+
+properties:
+  compatible:
+    enum:
+      - nxp,ptn36502
+
+  reg:
+    maxItems: 1
+
+  vdd18-supply:
+    description: Power supply for VDD18 pin
+
+  retimer-switch:
+    description: Flag the port as possible handle of SuperSpeed signals retiming
+    type: boolean
+
+  orientation-switch:
+    description: Flag the port as possible handler of orientation switching
+    type: boolean
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: Super Speed (SS) Output endpoint to the Type-C connector
+
+      port@1:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: Super Speed (SS) Input endpoint from the Super-Speed PHY
+
+      port@2:
+        $ref: /schemas/graph.yaml#/properties/port
+        description:
+          Sideband Use (SBU) AUX lines endpoint to the Type-C connector for the purpose of
+          handling altmode muxing and orientation switching.
+
+required:
+  - compatible
+  - reg
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        typec-mux@1a {
+            compatible = "nxp,ptn36502";
+            reg = <0x1a>;
+
+            vdd18-supply = <&usb_redrive_1v8>;
+
+            retimer-switch;
+            orientation-switch;
+
+            ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                port@0 {
+                    reg = <0>;
+                    usb_con_ss: endpoint {
+                        remote-endpoint = <&typec_con_ss>;
+                    };
+                };
+                port@1 {
+                    reg = <1>;
+                    phy_con_ss: endpoint {
+                        remote-endpoint = <&usb_phy_ss>;
+                    };
+                };
+                port@2 {
+                    reg = <2>;
+                    usb_con_sbu: endpoint {
+                        remote-endpoint = <&typec_dp_aux>;
+                    };
+                };
+            };
+        };
+    };
+...
index 67591057f2349b07ca10029933531e0d54030795..e889158ca20578893ef0c7072ce1fbf64f70a41c 100644 (file)
@@ -14,6 +14,7 @@ properties:
     items:
       - enum:
           - qcom,ipq4019-dwc3
+          - qcom,ipq5018-dwc3
           - qcom,ipq5332-dwc3
           - qcom,ipq6018-dwc3
           - qcom,ipq8064-dwc3
@@ -34,6 +35,7 @@ properties:
           - qcom,sdm845-dwc3
           - qcom,sdx55-dwc3
           - qcom,sdx65-dwc3
+          - qcom,sdx75-dwc3
           - qcom,sm4250-dwc3
           - qcom,sm6115-dwc3
           - qcom,sm6125-dwc3
@@ -180,6 +182,8 @@ allOf:
               - qcom,sdm670-dwc3
               - qcom,sdm845-dwc3
               - qcom,sdx55-dwc3
+              - qcom,sdx65-dwc3
+              - qcom,sdx75-dwc3
               - qcom,sm6350-dwc3
     then:
       properties:
@@ -238,6 +242,7 @@ allOf:
         compatible:
           contains:
             enum:
+              - qcom,ipq5018-dwc3
               - qcom,ipq5332-dwc3
               - qcom,msm8994-dwc3
               - qcom,qcs404-dwc3
@@ -363,6 +368,7 @@ allOf:
               - qcom,sdm845-dwc3
               - qcom,sdx55-dwc3
               - qcom,sdx65-dwc3
+              - qcom,sdx75-dwc3
               - qcom,sm4250-dwc3
               - qcom,sm6125-dwc3
               - qcom,sm6350-dwc3
@@ -411,6 +417,7 @@ allOf:
         compatible:
           contains:
             enum:
+              - qcom,ipq5018-dwc3
               - qcom,ipq5332-dwc3
               - qcom,sdm660-dwc3
     then:
diff --git a/Documentation/devicetree/bindings/usb/realtek,rtd-dwc3.yaml b/Documentation/devicetree/bindings/usb/realtek,rtd-dwc3.yaml
new file mode 100644 (file)
index 0000000..345d013
--- /dev/null
@@ -0,0 +1,80 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+# Copyright 2023 Realtek Semiconductor Corporation
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/realtek,rtd-dwc3.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Realtek DWC3 USB SoC Controller Glue
+
+maintainers:
+  - Stanley Chang <stanley_chang@realtek.com>
+
+description:
+  The Realtek DHC SoC embeds a DWC3 USB IP Core configured for USB 2.0
+  and USB 3.0 in host or dual-role mode.
+
+properties:
+  compatible:
+    items:
+      - enum:
+          - realtek,rtd1295-dwc3
+          - realtek,rtd1315e-dwc3
+          - realtek,rtd1319-dwc3
+          - realtek,rtd1319d-dwc3
+          - realtek,rtd1395-dwc3
+          - realtek,rtd1619-dwc3
+          - realtek,rtd1619b-dwc3
+      - const: realtek,rtd-dwc3
+
+  reg:
+    items:
+      - description: Address and length of register set for wrapper of dwc3 core.
+      - description: Address and length of register set for pm control.
+
+  '#address-cells':
+    const: 1
+
+  '#size-cells':
+    const: 1
+
+  ranges: true
+
+patternProperties:
+  "^usb@[0-9a-f]+$":
+    $ref: snps,dwc3.yaml#
+    description: Required child node
+
+required:
+  - compatible
+  - reg
+  - "#address-cells"
+  - "#size-cells"
+  - ranges
+
+additionalProperties: false
+
+examples:
+  - |
+    usb@98013e00 {
+        compatible = "realtek,rtd1319d-dwc3", "realtek,rtd-dwc3";
+        reg = <0x98013e00 0x140>, <0x98013f60 0x4>;
+        #address-cells = <1>;
+        #size-cells = <1>;
+        ranges;
+
+        usb@98050000 {
+            compatible = "snps,dwc3";
+            reg = <0x98050000 0x9000>;
+            interrupts = <0 94 4>;
+            phys = <&usb2phy &usb3phy>;
+            phy-names = "usb2-phy", "usb3-phy";
+            dr_mode = "otg";
+            usb-role-switch;
+            role-switch-default-mode = "host";
+            snps,dis_u2_susphy_quirk;
+            snps,parkmode-disable-ss-quirk;
+            snps,parkmode-disable-hs-quirk;
+            maximum-speed = "high-speed";
+        };
+    };
index c983dfe0f629418d260061771a6e3ff42692a553..c4924113f9bdec186d49c89a995c2b15a57e64d5 100644 (file)
@@ -20,9 +20,6 @@ description:
   Type-C PHY
   Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt
 
-allOf:
-  - $ref: snps,dwc3.yaml#
-
 select:
   properties:
     compatible:
@@ -30,6 +27,7 @@ select:
         enum:
           - rockchip,rk3328-dwc3
           - rockchip,rk3568-dwc3
+          - rockchip,rk3588-dwc3
   required:
     - compatible
 
@@ -39,6 +37,7 @@ properties:
       - enum:
           - rockchip,rk3328-dwc3
           - rockchip,rk3568-dwc3
+          - rockchip,rk3588-dwc3
       - const: snps,dwc3
 
   reg:
@@ -58,7 +57,9 @@ properties:
           Master/Core clock, must to be >= 62.5 MHz for SS
           operation and >= 30MHz for HS operation
       - description:
-          Controller grf clock
+          Controller grf clock OR UTMI clock
+      - description:
+          PIPE clock
 
   clock-names:
     minItems: 3
@@ -66,7 +67,10 @@ properties:
       - const: ref_clk
       - const: suspend_clk
       - const: bus_clk
-      - const: grf_clk
+      - enum:
+          - grf_clk
+          - utmi
+      - const: pipe
 
   power-domains:
     maxItems: 1
@@ -86,6 +90,52 @@ required:
   - clocks
   - clock-names
 
+allOf:
+  - $ref: snps,dwc3.yaml#
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: rockchip,rk3328-dwc3
+    then:
+      properties:
+        clocks:
+          minItems: 3
+          maxItems: 4
+        clock-names:
+          minItems: 3
+          items:
+            - const: ref_clk
+            - const: suspend_clk
+            - const: bus_clk
+            - const: grf_clk
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: rockchip,rk3568-dwc3
+    then:
+      properties:
+        clocks:
+          maxItems: 3
+        clock-names:
+          maxItems: 3
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: rockchip,rk3588-dwc3
+    then:
+      properties:
+        clock-names:
+          minItems: 3
+          items:
+            - const: ref_clk
+            - const: suspend_clk
+            - const: bus_clk
+            - const: utmi
+            - const: pipe
+
 examples:
   - |
     #include <dt-bindings/clock/rk3328-cru.h>
index a696f23730d3ecd965cfcd487a320bec5b964684..ee5af4b381b1bc97b11954a2141485e1f06671e1 100644 (file)
@@ -310,6 +310,62 @@ properties:
     maximum: 62
     deprecated: true
 
+  snps,rx-thr-num-pkt:
+    description:
+      USB RX packet threshold count. In host mode, this field specifies
+      the space that must be available in the RX FIFO before the core can
+      start the corresponding USB RX transaction (burst).
+      In device mode, this field specifies the space that must be
+      available in the RX FIFO before the core can send ERDY for a
+      flow-controlled endpoint. It is only used for SuperSpeed.
+      The valid values for this field are from 1 to 15. (DWC3 SuperSpeed
+      USB 3.0 Controller Databook)
+    $ref: /schemas/types.yaml#/definitions/uint8
+    minimum: 1
+    maximum: 15
+
+  snps,rx-max-burst:
+    description:
+      Max USB RX burst size. In host mode, this field specifies the
+      Maximum Bulk IN burst the DWC_usb3 core can perform. When the system
+      bus is slower than the USB, RX FIFO can overrun during a long burst.
+      You can program a smaller value to this field to limit the RX burst
+      size that the core can perform. It only applies to SS Bulk,
+      Isochronous, and Interrupt IN endpoints in the host mode.
+      In device mode, this field specifies the NUMP value that is sent in
+      ERDY for an OUT endpoint.
+      The valid values for this field are from 1 to 16. (DWC3 SuperSpeed
+      USB 3.0 Controller Databook)
+    $ref: /schemas/types.yaml#/definitions/uint8
+    minimum: 1
+    maximum: 16
+
+  snps,tx-thr-num-pkt:
+    description:
+      USB TX packet threshold count. This field specifies the number of
+      packets that must be in the TXFIFO before the core can start
+      transmission for the corresponding USB transaction (burst).
+      This count is valid in both host and device modes. It is only used
+      for SuperSpeed operation.
+      Valid values are from 1 to 15. (DWC3 SuperSpeed USB 3.0 Controller
+      Databook)
+    $ref: /schemas/types.yaml#/definitions/uint8
+    minimum: 1
+    maximum: 15
+
+  snps,tx-max-burst:
+    description:
+      Max USB TX burst size. When the system bus is slower than the USB,
+      TX FIFO can underrun during a long burst. Program a smaller value
+      to this field to limit the TX burst size that the core can execute.
+      In Host mode, it only applies to SS Bulk, Isochronous, and Interrupt
+      OUT endpoints. This value is not used in device mode.
+      Valid values are from 1 to 16. (DWC3 SuperSpeed USB 3.0 Controller
+      Databook)
+    $ref: /schemas/types.yaml#/definitions/uint8
+    minimum: 1
+    maximum: 16
+
   snps,rx-thr-num-pkt-prd:
     description:
       Periodic ESS RX packet threshold count (host mode only). Set this and
index 6ab674dea4c69a705ce25cd21626fd502d304fa8..323d664ae06a4de232eb21e2ee2ea8e814d69d17 100644 (file)
@@ -20,8 +20,23 @@ properties:
     enum:
       - ti,tps6598x
       - apple,cd321x
+      - ti,tps25750
+
   reg:
-    maxItems: 1
+    minItems: 1
+    items:
+      - description: main PD controller address
+      - description: |
+          I2C slave address field in PBMs input data
+          which is used as the device address when writing the
+          patch for TPS25750.
+          The patch address can be any value except 0x00, 0x20,
+          0x21, 0x22, and 0x23
+
+  reg-names:
+    items:
+      - const: main
+      - const: patch-address
 
   wakeup-source: true
 
@@ -35,10 +50,42 @@ properties:
   connector:
     $ref: /schemas/connector/usb-connector.yaml#
 
+  firmware-name:
+    description: |
+      Should contain the name of the default patch binary
+      file located on the firmware search path which is
+      used to switch the controller into APP mode.
+      This is used when tps25750 doesn't have an EEPROM
+      connected to it.
+    maxItems: 1
+
 required:
   - compatible
   - reg
 
+allOf:
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: ti,tps25750
+    then:
+      properties:
+        reg:
+          maxItems: 2
+
+        connector:
+          required:
+            - data-role
+
+      required:
+        - connector
+        - reg-names
+    else:
+      properties:
+        reg:
+          maxItems: 1
+
 additionalProperties: false
 
 examples:
@@ -71,4 +118,36 @@ examples:
             };
         };
     };
+
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        typec@21 {
+            compatible = "ti,tps25750";
+            reg = <0x21>, <0x0f>;
+            reg-names = "main", "patch-address";
+
+            interrupt-parent = <&msmgpio>;
+            interrupts = <100 IRQ_TYPE_LEVEL_LOW>;
+            interrupt-names = "irq";
+            firmware-name = "tps25750.bin";
+
+            pinctrl-names = "default";
+            pinctrl-0 = <&typec_pins>;
+
+            typec_con0: connector {
+                compatible = "usb-c-connector";
+                label = "USB-C";
+                data-role = "dual";
+                port {
+                    typec_ep0: endpoint {
+                        remote-endpoint = <&otg_ep>;
+                    };
+                };
+            };
+        };
+    };
 ...
index 76db9071b352f32539c914cb6b0f618e85ae9eb5..c815010ba9c2e82e1a504ad9cd280ec0366dfc4c 100644 (file)
@@ -37,7 +37,6 @@ properties:
 required:
   - compatible
   - reg
-  - reset-gpios
   - vdd-supply
   - peer-hub
 
index d32c27e11b907bf02fca7a0b67fce0407078a90c..02f6825ff830dfa6899063f9e1390f4fa5d3ed09 100644 (file)
@@ -93,44 +93,18 @@ DMA address space of the device.  However, most buffers passed to your
 driver can safely be used with such DMA mapping.  (See the first section
 of Documentation/core-api/dma-api-howto.rst, titled "What memory is DMA-able?")
 
-- When you're using scatterlists, you can map everything at once.  On some
-  systems, this kicks in an IOMMU and turns the scatterlists into single
-  DMA transactions::
+- When you have the scatterlists which have been mapped for the USB controller,
+  you could use the new ``usb_sg_*()`` calls, which would turn scatterlist
+  into URBs::
 
-       int usb_buffer_map_sg (struct usb_device *dev, unsigned pipe,
-               struct scatterlist *sg, int nents);
+       int usb_sg_init(struct usb_sg_request *io, struct usb_device *dev,
+               unsigned pipe, unsigned period, struct scatterlist *sg,
+               int nents, size_t length, gfp_t mem_flags);
 
-       void usb_buffer_dmasync_sg (struct usb_device *dev, unsigned pipe,
-               struct scatterlist *sg, int n_hw_ents);
+       void usb_sg_wait(struct usb_sg_request *io);
 
-       void usb_buffer_unmap_sg (struct usb_device *dev, unsigned pipe,
-               struct scatterlist *sg, int n_hw_ents);
+       void usb_sg_cancel(struct usb_sg_request *io);
 
-  It's probably easier to use the new ``usb_sg_*()`` calls, which do the DMA
-  mapping and apply other tweaks to make scatterlist i/o be fast.
-
-- Some drivers may prefer to work with the model that they're mapping large
-  buffers, synchronizing their safe re-use.  (If there's no re-use, then let
-  usbcore do the map/unmap.)  Large periodic transfers make good examples
-  here, since it's cheaper to just synchronize the buffer than to unmap it
-  each time an urb completes and then re-map it on during resubmission.
-
-  These calls all work with initialized urbs:  ``urb->dev``, ``urb->pipe``,
-  ``urb->transfer_buffer``, and ``urb->transfer_buffer_length`` must all be
-  valid when these calls are used (``urb->setup_packet`` must be valid too
-  if urb is a control request)::
-
-       struct urb *usb_buffer_map (struct urb *urb);
-
-       void usb_buffer_dmasync (struct urb *urb);
-
-       void usb_buffer_unmap (struct urb *urb);
-
-  The calls manage ``urb->transfer_dma`` for you, and set
-  ``URB_NO_TRANSFER_DMA_MAP`` so that usbcore won't map or unmap the buffer.
-  They cannot be used for setup_packet buffers in control requests.
-
-Note that several of those interfaces are currently commented out, since
-they don't have current users.  See the source code.  Other than the dmasync
-calls (where the underlying DMA primitives have changed), most of them can
-easily be commented back in if you want to use them.
+  When the USB controller doesn't support DMA, the ``usb_sg_init()`` would try
+  to submit URBs in PIO way as long as the page in scatterlists is not in the
+  Highmem, which could be very rare in modern architectures.
index 394cd226bfaeb489aa0f42414f9fbcb5210272ef..29072c166d236a25e175df3f1677b607a4449013 100644 (file)
@@ -755,6 +755,8 @@ The uac2 function provides these attributes in its function directory:
        req_number       the number of pre-allocated request for both capture
                         and playback
        function_name    name of the interface
+       c_terminal_type  code of the capture terminal type
+       p_terminal_type  code of the playback terminal type
        ================ ====================================================
 
 The attributes have sane default values.
index 5b3488736fbed2a8a1c5023d3eae16ad6f9e60cf..9a70875028b7ecdcf890d3c6cb87d6d074f1f342 100644 (file)
@@ -59,6 +59,7 @@
                compatible = "qcom,sm8550-pmic-glink", "qcom,pmic-glink";
                #address-cells = <1>;
                #size-cells = <0>;
+               orientation-gpios = <&tlmm 11 GPIO_ACTIVE_HIGH>;
 
                connector@0 {
                        compatible = "usb-c-connector";
index 320662024e8914e6359c6a2c3936df44f281b131..eef811def39bcf88cdbaed763fcfe2b9954a1104 100644 (file)
@@ -77,6 +77,7 @@
                compatible = "qcom,sm8550-pmic-glink", "qcom,pmic-glink";
                #address-cells = <1>;
                #size-cells = <0>;
+               orientation-gpios = <&tlmm 11 GPIO_ACTIVE_HIGH>;
 
                connector@0 {
                        compatible = "usb-c-connector";
index 2993e1255042100ab791f9e3e442feb3b005372a..7064c0e9179f1d868c5ebf28645f1f83a205098e 100644 (file)
                status = "disabled";
        };
 
+       usb_host2_xhci: usb@fcd00000 {
+               compatible = "rockchip,rk3588-dwc3", "snps,dwc3";
+               reg = <0x0 0xfcd00000 0x0 0x400000>;
+               interrupts = <GIC_SPI 222 IRQ_TYPE_LEVEL_HIGH 0>;
+               clocks = <&cru REF_CLK_USB3OTG2>, <&cru SUSPEND_CLK_USB3OTG2>,
+                        <&cru ACLK_USB3OTG2>, <&cru CLK_UTMI_OTG2>,
+                        <&cru CLK_PIPEPHY2_PIPE_U3_G>;
+               clock-names = "ref_clk", "suspend_clk", "bus_clk", "utmi", "pipe";
+               dr_mode = "host";
+               phys = <&combphy2_psu PHY_TYPE_USB3>;
+               phy-names = "usb3-phy";
+               phy_type = "utmi_wide";
+               resets = <&cru SRST_A_USB3OTG2>;
+               snps,dis_enblslpm_quirk;
+               snps,dis-u2-freeclk-exists-quirk;
+               snps,dis-del-phy-power-chg-quirk;
+               snps,dis-tx-ipgap-linecheck-quirk;
+               snps,dis_rxdet_inp3_quirk;
+               status = "disabled";
+       };
+
        pmu1grf: syscon@fd58a000 {
                compatible = "rockchip,rk3588-pmugrf", "syscon", "simple-mfd";
                reg = <0x0 0xfd58a000 0x0 0x10000>;
index 913948876c9360f21fbbdf9a94505397061ce8b2..b3a133ed31ee58b3c010c0ffb3f9e7f7f3e7540e 100644 (file)
@@ -1312,9 +1312,9 @@ config GPIO_KEMPLD
 
 config GPIO_LJCA
        tristate "INTEL La Jolla Cove Adapter GPIO support"
-       depends on MFD_LJCA
+       depends on USB_LJCA
        select GPIOLIB_IRQCHIP
-       default MFD_LJCA
+       default USB_LJCA
        help
          Select this option to enable GPIO driver for the INTEL
          La Jolla Cove Adapter (LJCA) board.
index aca69329455f1f264a1a31b355dcfe70c1f578fb..dfec9fbfc7a9bd8a98916a5ad2676aa380470852 100644 (file)
@@ -6,6 +6,7 @@
  */
 
 #include <linux/acpi.h>
+#include <linux/auxiliary_bus.h>
 #include <linux/bitfield.h>
 #include <linux/bitops.h>
 #include <linux/dev_printk.h>
 #include <linux/irq.h>
 #include <linux/kernel.h>
 #include <linux/kref.h>
-#include <linux/mfd/ljca.h>
 #include <linux/module.h>
-#include <linux/platform_device.h>
 #include <linux/slab.h>
 #include <linux/types.h>
+#include <linux/usb/ljca.h>
 
 /* GPIO commands */
-#define LJCA_GPIO_CONFIG       1
-#define LJCA_GPIO_READ         2
-#define LJCA_GPIO_WRITE                3
-#define LJCA_GPIO_INT_EVENT    4
-#define LJCA_GPIO_INT_MASK     5
-#define LJCA_GPIO_INT_UNMASK   6
+#define LJCA_GPIO_CONFIG               1
+#define LJCA_GPIO_READ                 2
+#define LJCA_GPIO_WRITE                        3
+#define LJCA_GPIO_INT_EVENT            4
+#define LJCA_GPIO_INT_MASK             5
+#define LJCA_GPIO_INT_UNMASK           6
 
 #define LJCA_GPIO_CONF_DISABLE         BIT(0)
 #define LJCA_GPIO_CONF_INPUT           BIT(1)
 #define LJCA_GPIO_CONF_INTERRUPT       BIT(6)
 #define LJCA_GPIO_INT_TYPE             BIT(7)
 
-#define LJCA_GPIO_CONF_EDGE    FIELD_PREP(LJCA_GPIO_INT_TYPE, 1)
-#define LJCA_GPIO_CONF_LEVEL   FIELD_PREP(LJCA_GPIO_INT_TYPE, 0)
+#define LJCA_GPIO_CONF_EDGE            FIELD_PREP(LJCA_GPIO_INT_TYPE, 1)
+#define LJCA_GPIO_CONF_LEVEL           FIELD_PREP(LJCA_GPIO_INT_TYPE, 0)
 
 /* Intentional overlap with PULLUP / PULLDOWN */
-#define LJCA_GPIO_CONF_SET     BIT(3)
-#define LJCA_GPIO_CONF_CLR     BIT(4)
+#define LJCA_GPIO_CONF_SET             BIT(3)
+#define LJCA_GPIO_CONF_CLR             BIT(4)
 
-struct gpio_op {
+#define LJCA_GPIO_BUF_SIZE             60u
+
+struct ljca_gpio_op {
        u8 index;
        u8 value;
 } __packed;
 
-struct gpio_packet {
+struct ljca_gpio_packet {
        u8 num;
-       struct gpio_op item[];
+       struct ljca_gpio_op item[] __counted_by(num);
 } __packed;
 
-#define LJCA_GPIO_BUF_SIZE 60
 struct ljca_gpio_dev {
-       struct platform_device *pdev;
+       struct ljca_client *ljca;
        struct gpio_chip gc;
        struct ljca_gpio_info *gpio_info;
        DECLARE_BITMAP(unmasked_irqs, LJCA_MAX_GPIO_NUM);
        DECLARE_BITMAP(enabled_irqs, LJCA_MAX_GPIO_NUM);
        DECLARE_BITMAP(reenable_irqs, LJCA_MAX_GPIO_NUM);
+       DECLARE_BITMAP(output_enabled, LJCA_MAX_GPIO_NUM);
        u8 *connect_mode;
-       /* mutex to protect irq bus */
+       /* protect irq bus */
        struct mutex irq_lock;
        struct work_struct work;
-       /* lock to protect package transfer to Hardware */
+       /* protect package transfer to hardware */
        struct mutex trans_lock;
 
        u8 obuf[LJCA_GPIO_BUF_SIZE];
        u8 ibuf[LJCA_GPIO_BUF_SIZE];
 };
 
-static int gpio_config(struct ljca_gpio_dev *ljca_gpio, u8 gpio_id, u8 config)
+static int ljca_gpio_config(struct ljca_gpio_dev *ljca_gpio, u8 gpio_id,
+                           u8 config)
 {
-       struct gpio_packet *packet = (struct gpio_packet *)ljca_gpio->obuf;
+       struct ljca_gpio_packet *packet =
+                               (struct ljca_gpio_packet *)ljca_gpio->obuf;
        int ret;
 
        mutex_lock(&ljca_gpio->trans_lock);
@@ -82,43 +86,43 @@ static int gpio_config(struct ljca_gpio_dev *ljca_gpio, u8 gpio_id, u8 config)
        packet->item[0].value = config | ljca_gpio->connect_mode[gpio_id];
        packet->num = 1;
 
-       ret = ljca_transfer(ljca_gpio->gpio_info->ljca, LJCA_GPIO_CONFIG, packet,
-                           struct_size(packet, item, packet->num), NULL, NULL);
+       ret = ljca_transfer(ljca_gpio->ljca, LJCA_GPIO_CONFIG, (u8 *)packet,
+                           struct_size(packet, item, packet->num), NULL, 0);
        mutex_unlock(&ljca_gpio->trans_lock);
-       return ret;
+
+       return ret < 0 ? ret : 0;
 }
 
 static int ljca_gpio_read(struct ljca_gpio_dev *ljca_gpio, u8 gpio_id)
 {
-       struct gpio_packet *packet = (struct gpio_packet *)ljca_gpio->obuf;
-       struct gpio_packet *ack_packet = (struct gpio_packet *)ljca_gpio->ibuf;
-       unsigned int ibuf_len = LJCA_GPIO_BUF_SIZE;
+       struct ljca_gpio_packet *ack_packet =
+                               (struct ljca_gpio_packet *)ljca_gpio->ibuf;
+       struct ljca_gpio_packet *packet =
+                               (struct ljca_gpio_packet *)ljca_gpio->obuf;
        int ret;
 
        mutex_lock(&ljca_gpio->trans_lock);
        packet->num = 1;
        packet->item[0].index = gpio_id;
-       ret = ljca_transfer(ljca_gpio->gpio_info->ljca, LJCA_GPIO_READ, packet,
-                           struct_size(packet, item, packet->num), ljca_gpio->ibuf, &ibuf_len);
-       if (ret)
-               goto out_unlock;
-
-       if (!ibuf_len || ack_packet->num != packet->num) {
-               dev_err(&ljca_gpio->pdev->dev, "failed gpio_id:%u %u", gpio_id, ack_packet->num);
-               ret = -EIO;
+       ret = ljca_transfer(ljca_gpio->ljca, LJCA_GPIO_READ, (u8 *)packet,
+                           struct_size(packet, item, packet->num),
+                           ljca_gpio->ibuf, LJCA_GPIO_BUF_SIZE);
+
+       if (ret <= 0 || ack_packet->num != packet->num) {
+               dev_err(&ljca_gpio->ljca->auxdev.dev,
+                       "read package error, gpio_id: %u num: %u ret: %d\n",
+                       gpio_id, ack_packet->num, ret);
+               ret = ret < 0 ? ret : -EIO;
        }
-
-out_unlock:
        mutex_unlock(&ljca_gpio->trans_lock);
-       if (ret)
-               return ret;
-       return ack_packet->item[0].value > 0;
+
+       return ret < 0 ? ret : ack_packet->item[0].value > 0;
 }
 
-static int ljca_gpio_write(struct ljca_gpio_dev *ljca_gpio, u8 gpio_id,
-                          int value)
+static int ljca_gpio_write(struct ljca_gpio_dev *ljca_gpio, u8 gpio_id, int value)
 {
-       struct gpio_packet *packet = (struct gpio_packet *)ljca_gpio->obuf;
+       struct ljca_gpio_packet *packet =
+                       (struct ljca_gpio_packet *)ljca_gpio->obuf;
        int ret;
 
        mutex_lock(&ljca_gpio->trans_lock);
@@ -126,10 +130,11 @@ static int ljca_gpio_write(struct ljca_gpio_dev *ljca_gpio, u8 gpio_id,
        packet->item[0].index = gpio_id;
        packet->item[0].value = value & 1;
 
-       ret = ljca_transfer(ljca_gpio->gpio_info->ljca, LJCA_GPIO_WRITE, packet,
-                           struct_size(packet, item, packet->num), NULL, NULL);
+       ret = ljca_transfer(ljca_gpio->ljca, LJCA_GPIO_WRITE, (u8 *)packet,
+                           struct_size(packet, item, packet->num), NULL, 0);
        mutex_unlock(&ljca_gpio->trans_lock);
-       return ret;
+
+       return ret < 0 ? ret : 0;
 }
 
 static int ljca_gpio_get_value(struct gpio_chip *chip, unsigned int offset)
@@ -147,16 +152,24 @@ static void ljca_gpio_set_value(struct gpio_chip *chip, unsigned int offset,
 
        ret = ljca_gpio_write(ljca_gpio, offset, val);
        if (ret)
-               dev_err(chip->parent, "offset:%u val:%d set value failed %d\n", offset, val, ret);
+               dev_err(chip->parent,
+                       "set value failed offset: %u val: %d ret: %d\n",
+                       offset, val, ret);
 }
 
-static int ljca_gpio_direction_input(struct gpio_chip *chip,
-                                    unsigned int offset)
+static int ljca_gpio_direction_input(struct gpio_chip *chip, unsigned int offset)
 {
        struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(chip);
        u8 config = LJCA_GPIO_CONF_INPUT | LJCA_GPIO_CONF_CLR;
+       int ret;
+
+       ret = ljca_gpio_config(ljca_gpio, offset, config);
+       if (ret)
+               return ret;
 
-       return gpio_config(ljca_gpio, offset, config);
+       clear_bit(offset, ljca_gpio->output_enabled);
+
+       return 0;
 }
 
 static int ljca_gpio_direction_output(struct gpio_chip *chip,
@@ -166,14 +179,26 @@ static int ljca_gpio_direction_output(struct gpio_chip *chip,
        u8 config = LJCA_GPIO_CONF_OUTPUT | LJCA_GPIO_CONF_CLR;
        int ret;
 
-       ret = gpio_config(ljca_gpio, offset, config);
+       ret = ljca_gpio_config(ljca_gpio, offset, config);
        if (ret)
                return ret;
 
        ljca_gpio_set_value(chip, offset, val);
+       set_bit(offset, ljca_gpio->output_enabled);
+
        return 0;
 }
 
+static int ljca_gpio_get_direction(struct gpio_chip *chip, unsigned int offset)
+{
+       struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(chip);
+
+       if (test_bit(offset, ljca_gpio->output_enabled))
+               return GPIO_LINE_DIRECTION_OUT;
+
+       return GPIO_LINE_DIRECTION_IN;
+}
+
 static int ljca_gpio_set_config(struct gpio_chip *chip, unsigned int offset,
                                unsigned long config)
 {
@@ -197,7 +222,8 @@ static int ljca_gpio_set_config(struct gpio_chip *chip, unsigned int offset,
        return 0;
 }
 
-static int ljca_gpio_init_valid_mask(struct gpio_chip *chip, unsigned long *valid_mask,
+static int ljca_gpio_init_valid_mask(struct gpio_chip *chip,
+                                    unsigned long *valid_mask,
                                     unsigned int ngpios)
 {
        struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(chip);
@@ -208,15 +234,18 @@ static int ljca_gpio_init_valid_mask(struct gpio_chip *chip, unsigned long *vali
        return 0;
 }
 
-static void ljca_gpio_irq_init_valid_mask(struct gpio_chip *chip, unsigned long *valid_mask,
+static void ljca_gpio_irq_init_valid_mask(struct gpio_chip *chip,
+                                         unsigned long *valid_mask,
                                          unsigned int ngpios)
 {
        ljca_gpio_init_valid_mask(chip, valid_mask, ngpios);
 }
 
-static int ljca_enable_irq(struct ljca_gpio_dev *ljca_gpio, int gpio_id, bool enable)
+static int ljca_enable_irq(struct ljca_gpio_dev *ljca_gpio, int gpio_id,
+                          bool enable)
 {
-       struct gpio_packet *packet = (struct gpio_packet *)ljca_gpio->obuf;
+       struct ljca_gpio_packet *packet =
+                       (struct ljca_gpio_packet *)ljca_gpio->obuf;
        int ret;
 
        mutex_lock(&ljca_gpio->trans_lock);
@@ -224,18 +253,20 @@ static int ljca_enable_irq(struct ljca_gpio_dev *ljca_gpio, int gpio_id, bool en
        packet->item[0].index = gpio_id;
        packet->item[0].value = 0;
 
-       ret = ljca_transfer(ljca_gpio->gpio_info->ljca,
-                           enable ? LJCA_GPIO_INT_UNMASK : LJCA_GPIO_INT_MASK, packet,
-                           struct_size(packet, item, packet->num), NULL, NULL);
+       ret = ljca_transfer(ljca_gpio->ljca,
+                           enable ? LJCA_GPIO_INT_UNMASK : LJCA_GPIO_INT_MASK,
+                           (u8 *)packet, struct_size(packet, item, packet->num),
+                           NULL, 0);
        mutex_unlock(&ljca_gpio->trans_lock);
-       return ret;
+
+       return ret < 0 ? ret : 0;
 }
 
 static void ljca_gpio_async(struct work_struct *work)
 {
-       struct ljca_gpio_dev *ljca_gpio = container_of(work, struct ljca_gpio_dev, work);
-       int gpio_id;
-       int unmasked;
+       struct ljca_gpio_dev *ljca_gpio =
+                       container_of(work, struct ljca_gpio_dev, work);
+       int gpio_id, unmasked;
 
        for_each_set_bit(gpio_id, ljca_gpio->reenable_irqs, ljca_gpio->gc.ngpio) {
                clear_bit(gpio_id, ljca_gpio->reenable_irqs);
@@ -245,20 +276,22 @@ static void ljca_gpio_async(struct work_struct *work)
        }
 }
 
-static void ljca_gpio_event_cb(void *context, u8 cmd, const void *evt_data, int len)
+static void ljca_gpio_event_cb(void *context, u8 cmd, const void *evt_data,
+                              int len)
 {
-       const struct gpio_packet *packet = evt_data;
+       const struct ljca_gpio_packet *packet = evt_data;
        struct ljca_gpio_dev *ljca_gpio = context;
-       int i;
-       int irq;
+       int i, irq;
 
        if (cmd != LJCA_GPIO_INT_EVENT)
                return;
 
        for (i = 0; i < packet->num; i++) {
-               irq = irq_find_mapping(ljca_gpio->gc.irq.domain, packet->item[i].index);
+               irq = irq_find_mapping(ljca_gpio->gc.irq.domain,
+                                      packet->item[i].index);
                if (!irq) {
-                       dev_err(ljca_gpio->gc.parent, "gpio_id %u does not mapped to IRQ yet\n",
+                       dev_err(ljca_gpio->gc.parent,
+                               "gpio_id %u does not mapped to IRQ yet\n",
                                packet->item[i].index);
                        return;
                }
@@ -299,18 +332,22 @@ static int ljca_irq_set_type(struct irq_data *irqd, unsigned int type)
        ljca_gpio->connect_mode[gpio_id] = LJCA_GPIO_CONF_INTERRUPT;
        switch (type) {
        case IRQ_TYPE_LEVEL_HIGH:
-               ljca_gpio->connect_mode[gpio_id] |= (LJCA_GPIO_CONF_LEVEL | LJCA_GPIO_CONF_PULLUP);
+               ljca_gpio->connect_mode[gpio_id] |=
+                       (LJCA_GPIO_CONF_LEVEL | LJCA_GPIO_CONF_PULLUP);
                break;
        case IRQ_TYPE_LEVEL_LOW:
-               ljca_gpio->connect_mode[gpio_id] |= (LJCA_GPIO_CONF_LEVEL | LJCA_GPIO_CONF_PULLDOWN);
+               ljca_gpio->connect_mode[gpio_id] |=
+                       (LJCA_GPIO_CONF_LEVEL | LJCA_GPIO_CONF_PULLDOWN);
                break;
        case IRQ_TYPE_EDGE_BOTH:
                break;
        case IRQ_TYPE_EDGE_RISING:
-               ljca_gpio->connect_mode[gpio_id] |= (LJCA_GPIO_CONF_EDGE | LJCA_GPIO_CONF_PULLUP);
+               ljca_gpio->connect_mode[gpio_id] |=
+                       (LJCA_GPIO_CONF_EDGE | LJCA_GPIO_CONF_PULLUP);
                break;
        case IRQ_TYPE_EDGE_FALLING:
-               ljca_gpio->connect_mode[gpio_id] |= (LJCA_GPIO_CONF_EDGE | LJCA_GPIO_CONF_PULLDOWN);
+               ljca_gpio->connect_mode[gpio_id] |=
+                       (LJCA_GPIO_CONF_EDGE | LJCA_GPIO_CONF_PULLDOWN);
                break;
        default:
                return -EINVAL;
@@ -332,15 +369,14 @@ static void ljca_irq_bus_unlock(struct irq_data *irqd)
        struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
        struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(gc);
        int gpio_id = irqd_to_hwirq(irqd);
-       int enabled;
-       int unmasked;
+       int enabled, unmasked;
 
        enabled = test_bit(gpio_id, ljca_gpio->enabled_irqs);
        unmasked = test_bit(gpio_id, ljca_gpio->unmasked_irqs);
 
        if (enabled != unmasked) {
                if (unmasked) {
-                       gpio_config(ljca_gpio, gpio_id, 0);
+                       ljca_gpio_config(ljca_gpio, gpio_id, 0);
                        ljca_enable_irq(ljca_gpio, gpio_id, true);
                        set_bit(gpio_id, ljca_gpio->enabled_irqs);
                } else {
@@ -363,43 +399,48 @@ static const struct irq_chip ljca_gpio_irqchip = {
        GPIOCHIP_IRQ_RESOURCE_HELPERS,
 };
 
-static int ljca_gpio_probe(struct platform_device *pdev)
+static int ljca_gpio_probe(struct auxiliary_device *auxdev,
+                          const struct auxiliary_device_id *aux_dev_id)
 {
+       struct ljca_client *ljca = auxiliary_dev_to_ljca_client(auxdev);
        struct ljca_gpio_dev *ljca_gpio;
        struct gpio_irq_chip *girq;
        int ret;
 
-       ljca_gpio = devm_kzalloc(&pdev->dev, sizeof(*ljca_gpio), GFP_KERNEL);
+       ljca_gpio = devm_kzalloc(&auxdev->dev, sizeof(*ljca_gpio), GFP_KERNEL);
        if (!ljca_gpio)
                return -ENOMEM;
 
-       ljca_gpio->gpio_info = dev_get_platdata(&pdev->dev);
-       ljca_gpio->connect_mode = devm_kcalloc(&pdev->dev, ljca_gpio->gpio_info->num,
-                                              sizeof(*ljca_gpio->connect_mode), GFP_KERNEL);
+       ljca_gpio->ljca = ljca;
+       ljca_gpio->gpio_info = dev_get_platdata(&auxdev->dev);
+       ljca_gpio->connect_mode = devm_kcalloc(&auxdev->dev,
+                                              ljca_gpio->gpio_info->num,
+                                              sizeof(*ljca_gpio->connect_mode),
+                                              GFP_KERNEL);
        if (!ljca_gpio->connect_mode)
                return -ENOMEM;
 
        mutex_init(&ljca_gpio->irq_lock);
        mutex_init(&ljca_gpio->trans_lock);
-       ljca_gpio->pdev = pdev;
        ljca_gpio->gc.direction_input = ljca_gpio_direction_input;
        ljca_gpio->gc.direction_output = ljca_gpio_direction_output;
+       ljca_gpio->gc.get_direction = ljca_gpio_get_direction;
        ljca_gpio->gc.get = ljca_gpio_get_value;
        ljca_gpio->gc.set = ljca_gpio_set_value;
        ljca_gpio->gc.set_config = ljca_gpio_set_config;
        ljca_gpio->gc.init_valid_mask = ljca_gpio_init_valid_mask;
        ljca_gpio->gc.can_sleep = true;
-       ljca_gpio->gc.parent = &pdev->dev;
+       ljca_gpio->gc.parent = &auxdev->dev;
 
        ljca_gpio->gc.base = -1;
        ljca_gpio->gc.ngpio = ljca_gpio->gpio_info->num;
-       ljca_gpio->gc.label = ACPI_COMPANION(&pdev->dev) ?
-                             acpi_dev_name(ACPI_COMPANION(&pdev->dev)) :
-                             dev_name(&pdev->dev);
+       ljca_gpio->gc.label = ACPI_COMPANION(&auxdev->dev) ?
+                             acpi_dev_name(ACPI_COMPANION(&auxdev->dev)) :
+                             dev_name(&auxdev->dev);
        ljca_gpio->gc.owner = THIS_MODULE;
 
-       platform_set_drvdata(pdev, ljca_gpio);
-       ljca_register_event_cb(ljca_gpio->gpio_info->ljca, ljca_gpio_event_cb, ljca_gpio);
+       auxiliary_set_drvdata(auxdev, ljca_gpio);
+       ljca_register_event_cb(ljca, ljca_gpio_event_cb, ljca_gpio);
 
        girq = &ljca_gpio->gc.irq;
        gpio_irq_chip_set_chip(girq, &ljca_gpio_irqchip);
@@ -413,7 +454,7 @@ static int ljca_gpio_probe(struct platform_device *pdev)
        INIT_WORK(&ljca_gpio->work, ljca_gpio_async);
        ret = gpiochip_add_data(&ljca_gpio->gc, ljca_gpio);
        if (ret) {
-               ljca_unregister_event_cb(ljca_gpio->gpio_info->ljca);
+               ljca_unregister_event_cb(ljca);
                mutex_destroy(&ljca_gpio->irq_lock);
                mutex_destroy(&ljca_gpio->trans_lock);
        }
@@ -421,33 +462,33 @@ static int ljca_gpio_probe(struct platform_device *pdev)
        return ret;
 }
 
-static void ljca_gpio_remove(struct platform_device *pdev)
+static void ljca_gpio_remove(struct auxiliary_device *auxdev)
 {
-       struct ljca_gpio_dev *ljca_gpio = platform_get_drvdata(pdev);
+       struct ljca_gpio_dev *ljca_gpio = auxiliary_get_drvdata(auxdev);
 
        gpiochip_remove(&ljca_gpio->gc);
-       ljca_unregister_event_cb(ljca_gpio->gpio_info->ljca);
+       ljca_unregister_event_cb(ljca_gpio->ljca);
+       cancel_work_sync(&ljca_gpio->work);
        mutex_destroy(&ljca_gpio->irq_lock);
        mutex_destroy(&ljca_gpio->trans_lock);
 }
 
-#define LJCA_GPIO_DRV_NAME "ljca-gpio"
-static const struct platform_device_id ljca_gpio_id[] = {
-       { LJCA_GPIO_DRV_NAME, 0 },
-       { /* sentinel */ }
+static const struct auxiliary_device_id ljca_gpio_id_table[] = {
+       { "usb_ljca.ljca-gpio", 0 },
+       { /* sentinel */ },
 };
-MODULE_DEVICE_TABLE(platform, ljca_gpio_id);
+MODULE_DEVICE_TABLE(auxiliary, ljca_gpio_id_table);
 
-static struct platform_driver ljca_gpio_driver = {
-       .driver.name = LJCA_GPIO_DRV_NAME,
+static struct auxiliary_driver ljca_gpio_driver = {
        .probe = ljca_gpio_probe,
-       .remove_new = ljca_gpio_remove,
+       .remove = ljca_gpio_remove,
+       .id_table = ljca_gpio_id_table,
 };
-module_platform_driver(ljca_gpio_driver);
+module_auxiliary_driver(ljca_gpio_driver);
 
-MODULE_AUTHOR("Ye Xiang <xiang.ye@intel.com>");
-MODULE_AUTHOR("Wang Zhifeng <zhifeng.wang@intel.com>");
-MODULE_AUTHOR("Zhang Lixu <lixu.zhang@intel.com>");
+MODULE_AUTHOR("Wentong Wu <wentong.wu@intel.com>");
+MODULE_AUTHOR("Zhifeng Wang <zhifeng.wang@intel.com>");
+MODULE_AUTHOR("Lixu Zhang <lixu.zhang@intel.com>");
 MODULE_DESCRIPTION("Intel La Jolla Cove Adapter USB-GPIO driver");
 MODULE_LICENSE("GPL");
 MODULE_IMPORT_NS(LJCA);
index 6644eebedaf3b7f4727fd88da340bd3a72314ac3..fd887f76767544895e35e7812652dfdbed09fb94 100644 (file)
@@ -1264,6 +1264,17 @@ config I2C_DLN2
         This driver can also be built as a module.  If so, the module
         will be called i2c-dln2.
 
+config I2C_LJCA
+       tristate "I2C functionality of Intel La Jolla Cove Adapter"
+       depends on USB_LJCA
+       default USB_LJCA
+       help
+         If you say yes to this option, I2C functionality support of Intel
+         La Jolla Cove Adapter (LJCA) will be included.
+
+         This driver can also be built as a module.  If so, the module
+         will be called i2c-ljca.
+
 config I2C_CP2615
        tristate "Silicon Labs CP2615 USB sound card and I2C adapter"
        depends on USB
index af56fe2c75c09c791c13cbe78c7e206b7fe06f46..3757b9391e60ae9b0e1c2ec5e564e0ae55af0c2a 100644 (file)
@@ -133,6 +133,7 @@ obj-$(CONFIG_I2C_GXP)               += i2c-gxp.o
 # External I2C/SMBus adapter drivers
 obj-$(CONFIG_I2C_DIOLAN_U2C)   += i2c-diolan-u2c.o
 obj-$(CONFIG_I2C_DLN2)         += i2c-dln2.o
+obj-$(CONFIG_I2C_LJCA)         += i2c-ljca.o
 obj-$(CONFIG_I2C_CP2615) += i2c-cp2615.o
 obj-$(CONFIG_I2C_PARPORT)      += i2c-parport.o
 obj-$(CONFIG_I2C_PCI1XXXX)     += i2c-mchp-pci1xxxx.o
diff --git a/drivers/i2c/busses/i2c-ljca.c b/drivers/i2c/busses/i2c-ljca.c
new file mode 100644 (file)
index 0000000..b492762
--- /dev/null
@@ -0,0 +1,343 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel La Jolla Cove Adapter USB-I2C driver
+ *
+ * Copyright (c) 2023, Intel Corporation.
+ */
+
+#include <linux/acpi.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/dev_printk.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/usb/ljca.h>
+
+/* I2C init flags */
+#define LJCA_I2C_INIT_FLAG_MODE                        BIT(0)
+#define LJCA_I2C_INIT_FLAG_MODE_POLLING                FIELD_PREP(LJCA_I2C_INIT_FLAG_MODE, 0)
+#define LJCA_I2C_INIT_FLAG_MODE_INTERRUPT      FIELD_PREP(LJCA_I2C_INIT_FLAG_MODE, 1)
+
+#define LJCA_I2C_INIT_FLAG_ADDR_16BIT          BIT(0)
+
+#define LJCA_I2C_INIT_FLAG_FREQ                        GENMASK(2, 1)
+#define LJCA_I2C_INIT_FLAG_FREQ_100K           FIELD_PREP(LJCA_I2C_INIT_FLAG_FREQ, 0)
+#define LJCA_I2C_INIT_FLAG_FREQ_400K           FIELD_PREP(LJCA_I2C_INIT_FLAG_FREQ, 1)
+#define LJCA_I2C_INIT_FLAG_FREQ_1M             FIELD_PREP(LJCA_I2C_INIT_FLAG_FREQ, 2)
+
+#define LJCA_I2C_BUF_SIZE                      60u
+#define LJCA_I2C_MAX_XFER_SIZE                 (LJCA_I2C_BUF_SIZE - sizeof(struct ljca_i2c_rw_packet))
+
+/* I2C commands */
+enum ljca_i2c_cmd {
+       LJCA_I2C_INIT = 1,
+       LJCA_I2C_XFER,
+       LJCA_I2C_START,
+       LJCA_I2C_STOP,
+       LJCA_I2C_READ,
+       LJCA_I2C_WRITE,
+};
+
+enum ljca_xfer_type {
+       LJCA_I2C_WRITE_XFER_TYPE,
+       LJCA_I2C_READ_XFER_TYPE,
+};
+
+/* I2C raw commands: Init/Start/Read/Write/Stop */
+struct ljca_i2c_rw_packet {
+       u8 id;
+       __le16 len;
+       u8 data[] __counted_by(len);
+} __packed;
+
+struct ljca_i2c_dev {
+       struct ljca_client *ljca;
+       struct ljca_i2c_info *i2c_info;
+       struct i2c_adapter adap;
+
+       u8 obuf[LJCA_I2C_BUF_SIZE];
+       u8 ibuf[LJCA_I2C_BUF_SIZE];
+};
+
+static int ljca_i2c_init(struct ljca_i2c_dev *ljca_i2c, u8 id)
+{
+       struct ljca_i2c_rw_packet *w_packet =
+                       (struct ljca_i2c_rw_packet *)ljca_i2c->obuf;
+       int ret;
+
+       w_packet->id = id;
+       w_packet->len = cpu_to_le16(sizeof(*w_packet->data));
+       w_packet->data[0] = LJCA_I2C_INIT_FLAG_FREQ_400K;
+
+       ret = ljca_transfer(ljca_i2c->ljca, LJCA_I2C_INIT, (u8 *)w_packet,
+                           struct_size(w_packet, data, 1), NULL, 0);
+
+       return ret < 0 ? ret : 0;
+}
+
+static int ljca_i2c_start(struct ljca_i2c_dev *ljca_i2c, u8 slave_addr,
+                         enum ljca_xfer_type type)
+{
+       struct ljca_i2c_rw_packet *w_packet =
+                       (struct ljca_i2c_rw_packet *)ljca_i2c->obuf;
+       struct ljca_i2c_rw_packet *r_packet =
+                       (struct ljca_i2c_rw_packet *)ljca_i2c->ibuf;
+       s16 rp_len;
+       int ret;
+
+       w_packet->id = ljca_i2c->i2c_info->id;
+       w_packet->len = cpu_to_le16(sizeof(*w_packet->data));
+       w_packet->data[0] = (slave_addr << 1) | type;
+
+       ret = ljca_transfer(ljca_i2c->ljca, LJCA_I2C_START, (u8 *)w_packet,
+                           struct_size(w_packet, data, 1), (u8 *)r_packet,
+                           LJCA_I2C_BUF_SIZE);
+       if (ret < 0 || ret < sizeof(*r_packet))
+               return ret < 0 ? ret : -EIO;
+
+       rp_len = le16_to_cpu(r_packet->len);
+       if (rp_len < 0 || r_packet->id != w_packet->id) {
+               dev_dbg(&ljca_i2c->adap.dev,
+                       "i2c start failed len: %d id: %d %d\n",
+                       rp_len, r_packet->id, w_packet->id);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static void ljca_i2c_stop(struct ljca_i2c_dev *ljca_i2c, u8 slave_addr)
+{
+       struct ljca_i2c_rw_packet *w_packet =
+                       (struct ljca_i2c_rw_packet *)ljca_i2c->obuf;
+       struct ljca_i2c_rw_packet *r_packet =
+                       (struct ljca_i2c_rw_packet *)ljca_i2c->ibuf;
+       s16 rp_len;
+       int ret;
+
+       w_packet->id = ljca_i2c->i2c_info->id;
+       w_packet->len = cpu_to_le16(sizeof(*w_packet->data));
+       w_packet->data[0] = 0;
+
+       ret = ljca_transfer(ljca_i2c->ljca, LJCA_I2C_STOP, (u8 *)w_packet,
+                           struct_size(w_packet, data, 1), (u8 *)r_packet,
+                           LJCA_I2C_BUF_SIZE);
+       if (ret < 0 || ret < sizeof(*r_packet)) {
+               dev_dbg(&ljca_i2c->adap.dev,
+                       "i2c stop failed ret: %d id: %d\n",
+                       ret, w_packet->id);
+               return;
+       }
+
+       rp_len = le16_to_cpu(r_packet->len);
+       if (rp_len < 0 || r_packet->id != w_packet->id)
+               dev_dbg(&ljca_i2c->adap.dev,
+                       "i2c stop failed len: %d id: %d %d\n",
+                       rp_len, r_packet->id, w_packet->id);
+}
+
+static int ljca_i2c_pure_read(struct ljca_i2c_dev *ljca_i2c, u8 *data, u8 len)
+{
+       struct ljca_i2c_rw_packet *w_packet =
+                       (struct ljca_i2c_rw_packet *)ljca_i2c->obuf;
+       struct ljca_i2c_rw_packet *r_packet =
+                       (struct ljca_i2c_rw_packet *)ljca_i2c->ibuf;
+       s16 rp_len;
+       int ret;
+
+       w_packet->id = ljca_i2c->i2c_info->id;
+       w_packet->len = cpu_to_le16(len);
+       w_packet->data[0] = 0;
+
+       ret = ljca_transfer(ljca_i2c->ljca, LJCA_I2C_READ, (u8 *)w_packet,
+                           struct_size(w_packet, data, 1), (u8 *)r_packet,
+                           LJCA_I2C_BUF_SIZE);
+       if (ret < 0 || ret < sizeof(*r_packet))
+               return ret < 0 ? ret : -EIO;
+
+       rp_len = le16_to_cpu(r_packet->len);
+       if (rp_len != len || r_packet->id != w_packet->id) {
+               dev_dbg(&ljca_i2c->adap.dev,
+                       "i2c raw read failed len: %d id: %d %d\n",
+                       rp_len, r_packet->id, w_packet->id);
+               return -EIO;
+       }
+
+       memcpy(data, r_packet->data, len);
+
+       return 0;
+}
+
+static int ljca_i2c_read(struct ljca_i2c_dev *ljca_i2c, u8 slave_addr, u8 *data,
+                        u8 len)
+{
+       int ret;
+
+       ret = ljca_i2c_start(ljca_i2c, slave_addr, LJCA_I2C_READ_XFER_TYPE);
+       if (!ret)
+               ret = ljca_i2c_pure_read(ljca_i2c, data, len);
+
+       ljca_i2c_stop(ljca_i2c, slave_addr);
+
+       return ret;
+}
+
+static int ljca_i2c_pure_write(struct ljca_i2c_dev *ljca_i2c, u8 *data, u8 len)
+{
+       struct ljca_i2c_rw_packet *w_packet =
+                       (struct ljca_i2c_rw_packet *)ljca_i2c->obuf;
+       struct ljca_i2c_rw_packet *r_packet =
+                       (struct ljca_i2c_rw_packet *)ljca_i2c->ibuf;
+       s16 rplen;
+       int ret;
+
+       w_packet->id = ljca_i2c->i2c_info->id;
+       w_packet->len = cpu_to_le16(len);
+       memcpy(w_packet->data, data, len);
+
+       ret = ljca_transfer(ljca_i2c->ljca, LJCA_I2C_WRITE, (u8 *)w_packet,
+                           struct_size(w_packet, data, len), (u8 *)r_packet,
+                           LJCA_I2C_BUF_SIZE);
+       if (ret < 0 || ret < sizeof(*r_packet))
+               return ret < 0 ? ret : -EIO;
+
+       rplen = le16_to_cpu(r_packet->len);
+       if (rplen != len || r_packet->id != w_packet->id) {
+               dev_dbg(&ljca_i2c->adap.dev,
+                       "i2c write failed len: %d id: %d/%d\n",
+                       rplen, r_packet->id, w_packet->id);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static int ljca_i2c_write(struct ljca_i2c_dev *ljca_i2c, u8 slave_addr,
+                         u8 *data, u8 len)
+{
+       int ret;
+
+       ret = ljca_i2c_start(ljca_i2c, slave_addr, LJCA_I2C_WRITE_XFER_TYPE);
+       if (!ret)
+               ret = ljca_i2c_pure_write(ljca_i2c, data, len);
+
+       ljca_i2c_stop(ljca_i2c, slave_addr);
+
+       return ret;
+}
+
+static int ljca_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msg,
+                        int num)
+{
+       struct ljca_i2c_dev *ljca_i2c;
+       struct i2c_msg *cur_msg;
+       int i, ret;
+
+       ljca_i2c = i2c_get_adapdata(adapter);
+       if (!ljca_i2c)
+               return -EINVAL;
+
+       for (i = 0; i < num; i++) {
+               cur_msg = &msg[i];
+               if (cur_msg->flags & I2C_M_RD)
+                       ret = ljca_i2c_read(ljca_i2c, cur_msg->addr,
+                                           cur_msg->buf, cur_msg->len);
+               else
+                       ret = ljca_i2c_write(ljca_i2c, cur_msg->addr,
+                                            cur_msg->buf, cur_msg->len);
+
+               if (ret)
+                       return ret;
+       }
+
+       return num;
+}
+
+static u32 ljca_i2c_func(struct i2c_adapter *adap)
+{
+       return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
+}
+
+static const struct i2c_adapter_quirks ljca_i2c_quirks = {
+       .flags = I2C_AQ_NO_ZERO_LEN,
+       .max_read_len = LJCA_I2C_MAX_XFER_SIZE,
+       .max_write_len = LJCA_I2C_MAX_XFER_SIZE,
+};
+
+static const struct i2c_algorithm ljca_i2c_algo = {
+       .master_xfer = ljca_i2c_xfer,
+       .functionality = ljca_i2c_func,
+};
+
+static int ljca_i2c_probe(struct auxiliary_device *auxdev,
+                         const struct auxiliary_device_id *aux_dev_id)
+{
+       struct ljca_client *ljca = auxiliary_dev_to_ljca_client(auxdev);
+       struct ljca_i2c_dev *ljca_i2c;
+       int ret;
+
+       ljca_i2c = devm_kzalloc(&auxdev->dev, sizeof(*ljca_i2c), GFP_KERNEL);
+       if (!ljca_i2c)
+               return -ENOMEM;
+
+       ljca_i2c->ljca = ljca;
+       ljca_i2c->i2c_info = dev_get_platdata(&auxdev->dev);
+
+       ljca_i2c->adap.owner = THIS_MODULE;
+       ljca_i2c->adap.class = I2C_CLASS_HWMON;
+       ljca_i2c->adap.algo = &ljca_i2c_algo;
+       ljca_i2c->adap.quirks = &ljca_i2c_quirks;
+       ljca_i2c->adap.dev.parent = &auxdev->dev;
+
+       snprintf(ljca_i2c->adap.name, sizeof(ljca_i2c->adap.name), "%s-%s-%d",
+                dev_name(&auxdev->dev), dev_name(auxdev->dev.parent),
+                ljca_i2c->i2c_info->id);
+
+       device_set_node(&ljca_i2c->adap.dev, dev_fwnode(&auxdev->dev));
+
+       i2c_set_adapdata(&ljca_i2c->adap, ljca_i2c);
+       auxiliary_set_drvdata(auxdev, ljca_i2c);
+
+       ret = ljca_i2c_init(ljca_i2c, ljca_i2c->i2c_info->id);
+       if (ret)
+               return dev_err_probe(&auxdev->dev, -EIO,
+                                    "i2c init failed id: %d\n",
+                                    ljca_i2c->i2c_info->id);
+
+       ret = devm_i2c_add_adapter(&auxdev->dev, &ljca_i2c->adap);
+       if (ret)
+               return ret;
+
+       if (has_acpi_companion(&ljca_i2c->adap.dev))
+               acpi_dev_clear_dependencies(ACPI_COMPANION(&ljca_i2c->adap.dev));
+
+       return 0;
+}
+
+static void ljca_i2c_remove(struct auxiliary_device *auxdev)
+{
+       struct ljca_i2c_dev *ljca_i2c = auxiliary_get_drvdata(auxdev);
+
+       i2c_del_adapter(&ljca_i2c->adap);
+}
+
+static const struct auxiliary_device_id ljca_i2c_id_table[] = {
+       { "usb_ljca.ljca-i2c", 0 },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(auxiliary, ljca_i2c_id_table);
+
+static struct auxiliary_driver ljca_i2c_driver = {
+       .probe = ljca_i2c_probe,
+       .remove = ljca_i2c_remove,
+       .id_table = ljca_i2c_id_table,
+};
+module_auxiliary_driver(ljca_i2c_driver);
+
+MODULE_AUTHOR("Wentong Wu <wentong.wu@intel.com>");
+MODULE_AUTHOR("Zhifeng Wang <zhifeng.wang@intel.com>");
+MODULE_AUTHOR("Lixu Zhang <lixu.zhang@intel.com>");
+MODULE_DESCRIPTION("Intel La Jolla Cove Adapter USB-I2C driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(LJCA);
index 67000e4a8082bca53853d05709c668ebe1023b9e..2b2f14a1b71193ca62319a24a5384aa541835eec 100644 (file)
@@ -492,6 +492,8 @@ static int cros_typec_enable_dp(struct cros_typec_data *typec,
 {
        struct cros_typec_port *port = typec->ports[port_num];
        struct typec_displayport_data dp_data;
+       u32 cable_tbt_vdo;
+       u32 cable_dp_vdo;
        int ret;
 
        if (typec->pd_ctrl_ver < 2) {
@@ -524,6 +526,32 @@ static int cros_typec_enable_dp(struct cros_typec_data *typec,
        port->state.data = &dp_data;
        port->state.mode = TYPEC_MODAL_STATE(ffs(pd_ctrl->dp_mode));
 
+       /* Get cable VDO for cables with DPSID to check DPAM2.1 is supported */
+       cable_dp_vdo = cros_typec_get_cable_vdo(port, USB_TYPEC_DP_SID);
+
+       /**
+        * Get cable VDO for thunderbolt cables and cables with DPSID but does not
+        * support DPAM2.1.
+        */
+       cable_tbt_vdo = cros_typec_get_cable_vdo(port, USB_TYPEC_TBT_SID);
+
+       if (cable_dp_vdo & DP_CAP_DPAM_VERSION) {
+               dp_data.conf |= cable_dp_vdo;
+       } else if (cable_tbt_vdo) {
+               dp_data.conf |=  TBT_CABLE_SPEED(cable_tbt_vdo) << DP_CONF_SIGNALLING_SHIFT;
+
+               /* Cable Type */
+               if (cable_tbt_vdo & TBT_CABLE_OPTICAL)
+                       dp_data.conf |= DP_CONF_CABLE_TYPE_OPTICAL << DP_CONF_CABLE_TYPE_SHIFT;
+               else if (cable_tbt_vdo & TBT_CABLE_RETIMER)
+                       dp_data.conf |= DP_CONF_CABLE_TYPE_RE_TIMER << DP_CONF_CABLE_TYPE_SHIFT;
+               else if (cable_tbt_vdo & TBT_CABLE_ACTIVE_PASSIVE)
+                       dp_data.conf |= DP_CONF_CABLE_TYPE_RE_DRIVER << DP_CONF_CABLE_TYPE_SHIFT;
+       } else if (PD_IDH_PTYPE(port->c_identity.id_header) == IDH_PTYPE_PCABLE) {
+               dp_data.conf |= VDO_TYPEC_CABLE_SPEED(port->c_identity.vdo[0]) <<
+                               DP_CONF_SIGNALLING_SHIFT;
+       }
+
        ret = cros_typec_retimer_set(port->retimer, port->state);
        if (!ret)
                ret = typec_mux_set(port->mux, &port->state);
index b3a1ba326a3e95853f54c9876f32e000958ee7f1..2382749a2f5311f384c8f68b83aab90329baf660 100644 (file)
@@ -237,7 +237,7 @@ static int tps65217_charger_probe(struct platform_device *pdev)
        for (i = 0; i < NUM_CHARGER_IRQS; i++) {
                ret = devm_request_threaded_irq(&pdev->dev, irq[i], NULL,
                                                tps65217_charger_irq,
-                                               IRQF_ONESHOT, "tps65217-charger",
+                                               IRQF_SHARED, "tps65217-charger",
                                                charger);
                if (ret) {
                        dev_err(charger->dev,
index d4ac184bce950d3b66a6e0f44abce9d48d187075..35dbfacecf1cd52dda2b596bbd7f5d2694efe35d 100644 (file)
@@ -616,6 +616,17 @@ config SPI_FSL_ESPI
          From MPC8536, 85xx platform uses the controller, and all P10xx,
          P20xx, P30xx,P40xx, P50xx uses this controller.
 
+config SPI_LJCA
+       tristate "Intel La Jolla Cove Adapter SPI support"
+       depends on USB_LJCA
+       default USB_LJCA
+       help
+         Select this option to enable SPI driver for the Intel
+         La Jolla Cove Adapter (LJCA) board.
+
+         This driver can also be built as a module. If so, the module
+         will be called spi-ljca.
+
 config SPI_MESON_SPICC
        tristate "Amlogic Meson SPICC controller"
        depends on COMMON_CLK
index 6af54842b9fa4f2b95355ada8418ad54173649b3..4ff8d725ba5e6fa6ea59a74e0bab81f637f1e375 100644 (file)
@@ -71,6 +71,7 @@ obj-$(CONFIG_SPI_INTEL_PCI)           += spi-intel-pci.o
 obj-$(CONFIG_SPI_INTEL_PLATFORM)       += spi-intel-platform.o
 obj-$(CONFIG_SPI_LANTIQ_SSC)           += spi-lantiq-ssc.o
 obj-$(CONFIG_SPI_JCORE)                        += spi-jcore.o
+obj-$(CONFIG_SPI_LJCA)                 += spi-ljca.o
 obj-$(CONFIG_SPI_LM70_LLP)             += spi-lm70llp.o
 obj-$(CONFIG_SPI_LOONGSON_CORE)                += spi-loongson-core.o
 obj-$(CONFIG_SPI_LOONGSON_PCI)         += spi-loongson-pci.o
diff --git a/drivers/spi/spi-ljca.c b/drivers/spi/spi-ljca.c
new file mode 100644 (file)
index 0000000..c5a066c
--- /dev/null
@@ -0,0 +1,297 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel La Jolla Cove Adapter USB-SPI driver
+ *
+ * Copyright (c) 2023, Intel Corporation.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/dev_printk.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/usb/ljca.h>
+
+#define LJCA_SPI_BUS_MAX_HZ            48000000
+
+#define LJCA_SPI_BUF_SIZE              60u
+#define LJCA_SPI_MAX_XFER_SIZE         \
+       (LJCA_SPI_BUF_SIZE - sizeof(struct ljca_spi_xfer_packet))
+
+#define LJCA_SPI_CLK_MODE_POLARITY     BIT(0)
+#define LJCA_SPI_CLK_MODE_PHASE                BIT(1)
+
+#define LJCA_SPI_XFER_INDICATOR_ID     GENMASK(5, 0)
+#define LJCA_SPI_XFER_INDICATOR_CMPL   BIT(6)
+#define LJCA_SPI_XFER_INDICATOR_INDEX  BIT(7)
+
+/* SPI commands */
+enum ljca_spi_cmd {
+       LJCA_SPI_INIT = 1,
+       LJCA_SPI_READ,
+       LJCA_SPI_WRITE,
+       LJCA_SPI_WRITEREAD,
+       LJCA_SPI_DEINIT,
+};
+
+enum {
+       LJCA_SPI_BUS_SPEED_24M,
+       LJCA_SPI_BUS_SPEED_12M,
+       LJCA_SPI_BUS_SPEED_8M,
+       LJCA_SPI_BUS_SPEED_6M,
+       LJCA_SPI_BUS_SPEED_4_8M, /*4.8MHz*/
+       LJCA_SPI_BUS_SPEED_MIN = LJCA_SPI_BUS_SPEED_4_8M,
+};
+
+enum {
+       LJCA_SPI_CLOCK_LOW_POLARITY,
+       LJCA_SPI_CLOCK_HIGH_POLARITY,
+};
+
+enum {
+       LJCA_SPI_CLOCK_FIRST_PHASE,
+       LJCA_SPI_CLOCK_SECOND_PHASE,
+};
+
+struct ljca_spi_init_packet {
+       u8 index;
+       u8 speed;
+       u8 mode;
+} __packed;
+
+struct ljca_spi_xfer_packet {
+       u8 indicator;
+       u8 len;
+       u8 data[] __counted_by(len);
+} __packed;
+
+struct ljca_spi_dev {
+       struct ljca_client *ljca;
+       struct spi_controller *controller;
+       struct ljca_spi_info *spi_info;
+       u8 speed;
+       u8 mode;
+
+       u8 obuf[LJCA_SPI_BUF_SIZE];
+       u8 ibuf[LJCA_SPI_BUF_SIZE];
+};
+
+static int ljca_spi_read_write(struct ljca_spi_dev *ljca_spi, const u8 *w_data,
+                              u8 *r_data, int len, int id, int complete,
+                              int cmd)
+{
+       struct ljca_spi_xfer_packet *w_packet =
+                       (struct ljca_spi_xfer_packet *)ljca_spi->obuf;
+       struct ljca_spi_xfer_packet *r_packet =
+                       (struct ljca_spi_xfer_packet *)ljca_spi->ibuf;
+       int ret;
+
+       w_packet->indicator = FIELD_PREP(LJCA_SPI_XFER_INDICATOR_ID, id) |
+                             FIELD_PREP(LJCA_SPI_XFER_INDICATOR_CMPL, complete) |
+                             FIELD_PREP(LJCA_SPI_XFER_INDICATOR_INDEX,
+                                        ljca_spi->spi_info->id);
+
+       if (cmd == LJCA_SPI_READ) {
+               w_packet->len = sizeof(u16);
+               *(__le16 *)&w_packet->data[0] = cpu_to_le16(len);
+       } else {
+               w_packet->len = len;
+               memcpy(w_packet->data, w_data, len);
+       }
+
+       ret = ljca_transfer(ljca_spi->ljca, cmd, (u8 *)w_packet,
+                           struct_size(w_packet, data, w_packet->len),
+                           (u8 *)r_packet, LJCA_SPI_BUF_SIZE);
+       if (ret < 0)
+               return ret;
+       else if (ret < sizeof(*r_packet) || r_packet->len <= 0)
+               return -EIO;
+
+       if (r_data)
+               memcpy(r_data, r_packet->data, r_packet->len);
+
+       return 0;
+}
+
+static int ljca_spi_init(struct ljca_spi_dev *ljca_spi, u8 div, u8 mode)
+{
+       struct ljca_spi_init_packet w_packet = {};
+       int ret;
+
+       if (ljca_spi->mode == mode && ljca_spi->speed == div)
+               return 0;
+
+       w_packet.index = ljca_spi->spi_info->id;
+       w_packet.speed = div;
+       w_packet.mode = FIELD_PREP(LJCA_SPI_CLK_MODE_POLARITY,
+                                  (mode & SPI_CPOL) ? LJCA_SPI_CLOCK_HIGH_POLARITY :
+                                                      LJCA_SPI_CLOCK_LOW_POLARITY) |
+                       FIELD_PREP(LJCA_SPI_CLK_MODE_PHASE,
+                                  (mode & SPI_CPHA) ? LJCA_SPI_CLOCK_SECOND_PHASE :
+                                                      LJCA_SPI_CLOCK_FIRST_PHASE);
+
+       ret = ljca_transfer(ljca_spi->ljca, LJCA_SPI_INIT, (u8 *)&w_packet,
+                           sizeof(w_packet), NULL, 0);
+       if (ret < 0)
+               return ret;
+
+       ljca_spi->mode = mode;
+       ljca_spi->speed = div;
+
+       return 0;
+}
+
+static int ljca_spi_deinit(struct ljca_spi_dev *ljca_spi)
+{
+       struct ljca_spi_init_packet w_packet = {};
+       int ret;
+
+       w_packet.index = ljca_spi->spi_info->id;
+
+       ret = ljca_transfer(ljca_spi->ljca, LJCA_SPI_DEINIT, (u8 *)&w_packet,
+                           sizeof(w_packet), NULL, 0);
+
+       return ret < 0 ? ret : 0;
+}
+
+static inline int ljca_spi_transfer(struct ljca_spi_dev *ljca_spi,
+                                   const u8 *tx_data, u8 *rx_data, u16 len)
+{
+       int complete, cur_len;
+       int remaining = len;
+       int cmd, ret, i;
+       int offset = 0;
+
+       if (tx_data && rx_data)
+               cmd = LJCA_SPI_WRITEREAD;
+       else if (tx_data)
+               cmd = LJCA_SPI_WRITE;
+       else if (rx_data)
+               cmd = LJCA_SPI_READ;
+       else
+               return -EINVAL;
+
+       for (i = 0; remaining > 0; i++) {
+               cur_len = min_t(unsigned int, remaining, LJCA_SPI_MAX_XFER_SIZE);
+               complete = (cur_len == remaining);
+
+               ret = ljca_spi_read_write(ljca_spi,
+                                         tx_data ? tx_data + offset : NULL,
+                                         rx_data ? rx_data + offset : NULL,
+                                         cur_len, i, complete, cmd);
+               if (ret)
+                       return ret;
+
+               offset += cur_len;
+               remaining -= cur_len;
+       }
+
+       return 0;
+}
+
+static int ljca_spi_transfer_one(struct spi_controller *controller,
+                                struct spi_device *spi,
+                                struct spi_transfer *xfer)
+{
+       u8 div = DIV_ROUND_UP(controller->max_speed_hz, xfer->speed_hz) / 2 - 1;
+       struct ljca_spi_dev *ljca_spi = spi_controller_get_devdata(controller);
+       int ret;
+
+       div = min_t(u8, LJCA_SPI_BUS_SPEED_MIN, div);
+
+       ret = ljca_spi_init(ljca_spi, div, spi->mode);
+       if (ret) {
+               dev_err(&ljca_spi->ljca->auxdev.dev,
+                       "cannot initialize transfer ret %d\n", ret);
+               return ret;
+       }
+
+       ret = ljca_spi_transfer(ljca_spi, xfer->tx_buf, xfer->rx_buf, xfer->len);
+       if (ret)
+               dev_err(&ljca_spi->ljca->auxdev.dev,
+                       "transfer failed len: %d\n", xfer->len);
+
+       return ret;
+}
+
+static int ljca_spi_probe(struct auxiliary_device *auxdev,
+                         const struct auxiliary_device_id *aux_dev_id)
+{
+       struct ljca_client *ljca = auxiliary_dev_to_ljca_client(auxdev);
+       struct spi_controller *controller;
+       struct ljca_spi_dev *ljca_spi;
+       int ret;
+
+       controller = devm_spi_alloc_master(&auxdev->dev, sizeof(*ljca_spi));
+       if (!controller)
+               return -ENOMEM;
+
+       ljca_spi = spi_controller_get_devdata(controller);
+       ljca_spi->ljca = ljca;
+       ljca_spi->spi_info = dev_get_platdata(&auxdev->dev);
+       ljca_spi->controller = controller;
+
+       controller->bus_num = -1;
+       controller->mode_bits = SPI_CPHA | SPI_CPOL;
+       controller->transfer_one = ljca_spi_transfer_one;
+       controller->auto_runtime_pm = false;
+       controller->max_speed_hz = LJCA_SPI_BUS_MAX_HZ;
+
+       device_set_node(&ljca_spi->controller->dev, dev_fwnode(&auxdev->dev));
+       auxiliary_set_drvdata(auxdev, controller);
+
+       ret = spi_register_controller(controller);
+       if (ret)
+               dev_err(&auxdev->dev, "Failed to register controller\n");
+
+       return ret;
+}
+
+static void ljca_spi_dev_remove(struct auxiliary_device *auxdev)
+{
+       struct spi_controller *controller = auxiliary_get_drvdata(auxdev);
+       struct ljca_spi_dev *ljca_spi = spi_controller_get_devdata(controller);
+
+       spi_unregister_controller(controller);
+       ljca_spi_deinit(ljca_spi);
+}
+
+static int ljca_spi_dev_suspend(struct device *dev)
+{
+       struct spi_controller *controller = dev_get_drvdata(dev);
+
+       return spi_controller_suspend(controller);
+}
+
+static int ljca_spi_dev_resume(struct device *dev)
+{
+       struct spi_controller *controller = dev_get_drvdata(dev);
+
+       return spi_controller_resume(controller);
+}
+
+static const struct dev_pm_ops ljca_spi_pm = {
+       SYSTEM_SLEEP_PM_OPS(ljca_spi_dev_suspend, ljca_spi_dev_resume)
+};
+
+static const struct auxiliary_device_id ljca_spi_id_table[] = {
+       { "usb_ljca.ljca-spi", 0 },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(auxiliary, ljca_spi_id_table);
+
+static struct auxiliary_driver ljca_spi_driver = {
+       .driver.pm      = &ljca_spi_pm,
+       .probe          = ljca_spi_probe,
+       .remove         = ljca_spi_dev_remove,
+       .id_table       = ljca_spi_id_table,
+};
+module_auxiliary_driver(ljca_spi_driver);
+
+MODULE_AUTHOR("Wentong Wu <wentong.wu@intel.com>");
+MODULE_AUTHOR("Zhifeng Wang <zhifeng.wang@intel.com>");
+MODULE_AUTHOR("Lixu Zhang <lixu.zhang@intel.com>");
+MODULE_DESCRIPTION("Intel La Jolla Cove Adapter USB-SPI driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(LJCA);
index 13d217ae98e6b14c55e5d78b806e02cc8b18609b..787dfd1550e5da81e325f21d6e9e78d316cb4e04 100644 (file)
@@ -174,6 +174,28 @@ bool tb_port_clx_is_enabled(struct tb_port *port, unsigned int clx)
        return !!(tb_port_clx(port) & clx);
 }
 
+/**
+ * tb_switch_clx_is_supported() - Is CLx supported on this type of router
+ * @sw: The router to check CLx support for
+ */
+static bool tb_switch_clx_is_supported(const struct tb_switch *sw)
+{
+       if (!clx_enabled)
+               return false;
+
+       if (sw->quirks & QUIRK_NO_CLX)
+               return false;
+
+       /*
+        * CLx is not enabled and validated on Intel USB4 platforms
+        * before Alder Lake.
+        */
+       if (tb_switch_is_tiger_lake(sw))
+               return false;
+
+       return tb_switch_is_usb4(sw) || tb_switch_is_titan_ridge(sw);
+}
+
 /**
  * tb_switch_clx_init() - Initialize router CL states
  * @sw: Router
@@ -273,28 +295,6 @@ static int tb_switch_mask_clx_objections(struct tb_switch *sw)
                           sw->cap_lp + offset, ARRAY_SIZE(val));
 }
 
-/**
- * tb_switch_clx_is_supported() - Is CLx supported on this type of router
- * @sw: The router to check CLx support for
- */
-bool tb_switch_clx_is_supported(const struct tb_switch *sw)
-{
-       if (!clx_enabled)
-               return false;
-
-       if (sw->quirks & QUIRK_NO_CLX)
-               return false;
-
-       /*
-        * CLx is not enabled and validated on Intel USB4 platforms
-        * before Alder Lake.
-        */
-       if (tb_switch_is_tiger_lake(sw))
-               return false;
-
-       return tb_switch_is_usb4(sw) || tb_switch_is_titan_ridge(sw);
-}
-
 static bool validate_mask(unsigned int clx)
 {
        /* Previous states need to be enabled */
@@ -405,6 +405,9 @@ int tb_switch_clx_disable(struct tb_switch *sw)
        if (!clx)
                return 0;
 
+       if (sw->is_unplugged)
+               return clx;
+
        up = tb_upstream_port(sw);
        down = tb_switch_downstream_port(sw);
 
index 39476fc488015b2470bc790e568308f8680a904d..9e47a63f28e786c297adacddab0137bf0c4fe6ce 100644 (file)
@@ -101,7 +101,7 @@ struct dma_test {
        unsigned int packets_sent;
        unsigned int packets_received;
        unsigned int link_speed;
-       unsigned int link_width;
+       enum tb_link_width link_width;
        unsigned int crc_errors;
        unsigned int buffer_overflow_errors;
        enum dma_test_result result;
@@ -465,9 +465,9 @@ DMA_TEST_DEBUGFS_ATTR(packets_to_send, packets_to_send_get,
 static int dma_test_set_bonding(struct dma_test *dt)
 {
        switch (dt->link_width) {
-       case 2:
+       case TB_LINK_WIDTH_DUAL:
                return tb_xdomain_lane_bonding_enable(dt->xd);
-       case 1:
+       case TB_LINK_WIDTH_SINGLE:
                tb_xdomain_lane_bonding_disable(dt->xd);
                fallthrough;
        default:
@@ -490,12 +490,8 @@ static void dma_test_check_errors(struct dma_test *dt, int ret)
        if (!dt->error_code) {
                if (dt->link_speed && dt->xd->link_speed != dt->link_speed) {
                        dt->error_code = DMA_TEST_SPEED_ERROR;
-               } else if (dt->link_width) {
-                       const struct tb_xdomain *xd = dt->xd;
-
-                       if ((dt->link_width == 1 && xd->link_width != TB_LINK_WIDTH_SINGLE) ||
-                           (dt->link_width == 2 && xd->link_width < TB_LINK_WIDTH_DUAL))
-                               dt->error_code = DMA_TEST_WIDTH_ERROR;
+               } else if (dt->link_width && dt->link_width != dt->xd->link_width) {
+                       dt->error_code = DMA_TEST_WIDTH_ERROR;
                } else if (dt->packets_to_send != dt->packets_sent ||
                         dt->packets_to_receive != dt->packets_received ||
                         dt->crc_errors || dt->buffer_overflow_errors) {
index ee03fd75a4728f1ca10a603e0fbe1698ad0cf54b..091a81bbdbdc94623b1fda9a059c1b6396595c15 100644 (file)
@@ -19,9 +19,9 @@ static void tb_dump_hop(const struct tb_path_hop *hop, const struct tb_regs_hop
 
        tb_port_dbg(port, " In HopID: %d => Out port: %d Out HopID: %d\n",
                    hop->in_hop_index, regs->out_port, regs->next_hop);
-       tb_port_dbg(port, "  Weight: %d Priority: %d Credits: %d Drop: %d\n",
-                   regs->weight, regs->priority,
-                   regs->initial_credits, regs->drop_packages);
+       tb_port_dbg(port, "  Weight: %d Priority: %d Credits: %d Drop: %d PM: %d\n",
+                   regs->weight, regs->priority, regs->initial_credits,
+                   regs->drop_packages, regs->pmps);
        tb_port_dbg(port, "   Counter enabled: %d Counter index: %d\n",
                    regs->counter_enable, regs->counter);
        tb_port_dbg(port, "  Flow Control (In/Eg): %d/%d Shared Buffer (In/Eg): %d/%d\n",
@@ -535,6 +535,7 @@ int tb_path_activate(struct tb_path *path)
                hop.next_hop = path->hops[i].next_hop_index;
                hop.out_port = path->hops[i].out_port->port;
                hop.initial_credits = path->hops[i].initial_credits;
+               hop.pmps = path->hops[i].pm_support;
                hop.unknown1 = 0;
                hop.enable = 1;
 
index 488138a28ae13bde67b6ae4953f6fccfc17a3c29..e6bfa63b40aee4675293620c5326cdb160af9a19 100644 (file)
@@ -31,6 +31,9 @@ static void quirk_usb3_maximum_bandwidth(struct tb_switch *sw)
 {
        struct tb_port *port;
 
+       if (tb_switch_is_icm(sw))
+               return;
+
        tb_switch_for_each_port(sw, port) {
                if (!tb_port_is_usb3_down(port))
                        continue;
index 47becb363adacb50c5813946d6355d8a3dd6d14a..d49d6628dbf29970a398e381b1603875ec1d7f0c 100644 (file)
@@ -94,6 +94,7 @@ static int tb_retimer_nvm_add(struct tb_retimer *rt)
                goto err_nvm;
 
        rt->nvm = nvm;
+       dev_dbg(&rt->dev, "NVM version %x.%x\n", nvm->major, nvm->minor);
        return 0;
 
 err_nvm:
index bd5815f8f23bd61a92fd45b8e263e4b8acb067f5..1e15ffa792955d7382b19546226bdcfa603a5685 100644 (file)
@@ -372,6 +372,7 @@ static int tb_switch_nvm_add(struct tb_switch *sw)
                ret = tb_nvm_add_active(nvm, nvm_read);
                if (ret)
                        goto err_nvm;
+               tb_sw_dbg(sw, "NVM version %x.%x\n", nvm->major, nvm->minor);
        }
 
        if (!sw->no_nvm_upgrade) {
@@ -914,6 +915,48 @@ int tb_port_get_link_speed(struct tb_port *port)
        }
 }
 
+/**
+ * tb_port_get_link_generation() - Returns link generation
+ * @port: Lane adapter
+ *
+ * Returns link generation as number or negative errno in case of
+ * failure. Does not distinguish between Thunderbolt 1 and Thunderbolt 2
+ * links so for those always returns 2.
+ */
+int tb_port_get_link_generation(struct tb_port *port)
+{
+       int ret;
+
+       ret = tb_port_get_link_speed(port);
+       if (ret < 0)
+               return ret;
+
+       switch (ret) {
+       case 40:
+               return 4;
+       case 20:
+               return 3;
+       default:
+               return 2;
+       }
+}
+
+static const char *width_name(enum tb_link_width width)
+{
+       switch (width) {
+       case TB_LINK_WIDTH_SINGLE:
+               return "symmetric, single lane";
+       case TB_LINK_WIDTH_DUAL:
+               return "symmetric, dual lanes";
+       case TB_LINK_WIDTH_ASYM_TX:
+               return "asymmetric, 3 transmitters, 1 receiver";
+       case TB_LINK_WIDTH_ASYM_RX:
+               return "asymmetric, 3 receivers, 1 transmitter";
+       default:
+               return "unknown";
+       }
+}
+
 /**
  * tb_port_get_link_width() - Get current link width
  * @port: Port to check (USB4 or CIO)
@@ -939,8 +982,15 @@ int tb_port_get_link_width(struct tb_port *port)
                LANE_ADP_CS_1_CURRENT_WIDTH_SHIFT;
 }
 
-static bool tb_port_is_width_supported(struct tb_port *port,
-                                      unsigned int width_mask)
+/**
+ * tb_port_width_supported() - Is the given link width supported
+ * @port: Port to check
+ * @width: Widths to check (bitmask)
+ *
+ * Can be called to any lane adapter. Checks if given @width is
+ * supported by the hardware and returns %true if it is.
+ */
+bool tb_port_width_supported(struct tb_port *port, unsigned int width)
 {
        u32 phy, widths;
        int ret;
@@ -948,20 +998,23 @@ static bool tb_port_is_width_supported(struct tb_port *port,
        if (!port->cap_phy)
                return false;
 
+       if (width & (TB_LINK_WIDTH_ASYM_TX | TB_LINK_WIDTH_ASYM_RX)) {
+               if (tb_port_get_link_generation(port) < 4 ||
+                   !usb4_port_asym_supported(port))
+                       return false;
+       }
+
        ret = tb_port_read(port, &phy, TB_CFG_PORT,
                           port->cap_phy + LANE_ADP_CS_0, 1);
        if (ret)
                return false;
 
-       widths = (phy & LANE_ADP_CS_0_SUPPORTED_WIDTH_MASK) >>
-               LANE_ADP_CS_0_SUPPORTED_WIDTH_SHIFT;
-
-       return widths & width_mask;
-}
-
-static bool is_gen4_link(struct tb_port *port)
-{
-       return tb_port_get_link_speed(port) > 20;
+       /*
+        * The field encoding is the same as &enum tb_link_width (which is
+        * passed to @width).
+        */
+       widths = FIELD_GET(LANE_ADP_CS_0_SUPPORTED_WIDTH_MASK, phy);
+       return widths & width;
 }
 
 /**
@@ -991,15 +1044,23 @@ int tb_port_set_link_width(struct tb_port *port, enum tb_link_width width)
        switch (width) {
        case TB_LINK_WIDTH_SINGLE:
                /* Gen 4 link cannot be single */
-               if (is_gen4_link(port))
+               if (tb_port_get_link_generation(port) >= 4)
                        return -EOPNOTSUPP;
                val |= LANE_ADP_CS_1_TARGET_WIDTH_SINGLE <<
                        LANE_ADP_CS_1_TARGET_WIDTH_SHIFT;
                break;
+
        case TB_LINK_WIDTH_DUAL:
+               if (tb_port_get_link_generation(port) >= 4)
+                       return usb4_port_asym_set_link_width(port, width);
                val |= LANE_ADP_CS_1_TARGET_WIDTH_DUAL <<
                        LANE_ADP_CS_1_TARGET_WIDTH_SHIFT;
                break;
+
+       case TB_LINK_WIDTH_ASYM_TX:
+       case TB_LINK_WIDTH_ASYM_RX:
+               return usb4_port_asym_set_link_width(port, width);
+
        default:
                return -EINVAL;
        }
@@ -1124,7 +1185,7 @@ void tb_port_lane_bonding_disable(struct tb_port *port)
 /**
  * tb_port_wait_for_link_width() - Wait until link reaches specific width
  * @port: Port to wait for
- * @width_mask: Expected link width mask
+ * @width: Expected link width (bitmask)
  * @timeout_msec: Timeout in ms how long to wait
  *
  * Should be used after both ends of the link have been bonded (or
@@ -1133,14 +1194,15 @@ void tb_port_lane_bonding_disable(struct tb_port *port)
  * within the given timeout, %0 if it did. Can be passed a mask of
  * expected widths and succeeds if any of the widths is reached.
  */
-int tb_port_wait_for_link_width(struct tb_port *port, unsigned int width_mask,
+int tb_port_wait_for_link_width(struct tb_port *port, unsigned int width,
                                int timeout_msec)
 {
        ktime_t timeout = ktime_add_ms(ktime_get(), timeout_msec);
        int ret;
 
        /* Gen 4 link does not support single lane */
-       if ((width_mask & TB_LINK_WIDTH_SINGLE) && is_gen4_link(port))
+       if ((width & TB_LINK_WIDTH_SINGLE) &&
+           tb_port_get_link_generation(port) >= 4)
                return -EOPNOTSUPP;
 
        do {
@@ -1153,7 +1215,7 @@ int tb_port_wait_for_link_width(struct tb_port *port, unsigned int width_mask,
                         */
                        if (ret != -EACCES)
                                return ret;
-               } else if (ret & width_mask) {
+               } else if (ret & width) {
                        return 0;
                }
 
@@ -1332,7 +1394,7 @@ int tb_pci_port_enable(struct tb_port *port, bool enable)
  * tb_dp_port_hpd_is_active() - Is HPD already active
  * @port: DP out port to check
  *
- * Checks if the DP OUT adapter port has HDP bit already set.
+ * Checks if the DP OUT adapter port has HPD bit already set.
  */
 int tb_dp_port_hpd_is_active(struct tb_port *port)
 {
@@ -1344,14 +1406,14 @@ int tb_dp_port_hpd_is_active(struct tb_port *port)
        if (ret)
                return ret;
 
-       return !!(data & ADP_DP_CS_2_HDP);
+       return !!(data & ADP_DP_CS_2_HPD);
 }
 
 /**
  * tb_dp_port_hpd_clear() - Clear HPD from DP IN port
  * @port: Port to clear HPD
  *
- * If the DP IN port has HDP set, this function can be used to clear it.
+ * If the DP IN port has HPD set, this function can be used to clear it.
  */
 int tb_dp_port_hpd_clear(struct tb_port *port)
 {
@@ -1363,7 +1425,7 @@ int tb_dp_port_hpd_clear(struct tb_port *port)
        if (ret)
                return ret;
 
-       data |= ADP_DP_CS_3_HDPC;
+       data |= ADP_DP_CS_3_HPDC;
        return tb_port_write(port, &data, TB_CFG_PORT,
                             port->cap_adap + ADP_DP_CS_3, 1);
 }
@@ -2697,6 +2759,38 @@ static int tb_switch_update_link_attributes(struct tb_switch *sw)
        return 0;
 }
 
+/* Must be called after tb_switch_update_link_attributes() */
+static void tb_switch_link_init(struct tb_switch *sw)
+{
+       struct tb_port *up, *down;
+       bool bonded;
+
+       if (!tb_route(sw) || tb_switch_is_icm(sw))
+               return;
+
+       tb_sw_dbg(sw, "current link speed %u.0 Gb/s\n", sw->link_speed);
+       tb_sw_dbg(sw, "current link width %s\n", width_name(sw->link_width));
+
+       bonded = sw->link_width >= TB_LINK_WIDTH_DUAL;
+
+       /*
+        * Gen 4 links come up as bonded so update the port structures
+        * accordingly.
+        */
+       up = tb_upstream_port(sw);
+       down = tb_switch_downstream_port(sw);
+
+       up->bonded = bonded;
+       if (up->dual_link_port)
+               up->dual_link_port->bonded = bonded;
+       tb_port_update_credits(up);
+
+       down->bonded = bonded;
+       if (down->dual_link_port)
+               down->dual_link_port->bonded = bonded;
+       tb_port_update_credits(down);
+}
+
 /**
  * tb_switch_lane_bonding_enable() - Enable lane bonding
  * @sw: Switch to enable lane bonding
@@ -2705,24 +2799,20 @@ static int tb_switch_update_link_attributes(struct tb_switch *sw)
  * switch. If conditions are correct and both switches support the feature,
  * lanes are bonded. It is safe to call this to any switch.
  */
-int tb_switch_lane_bonding_enable(struct tb_switch *sw)
+static int tb_switch_lane_bonding_enable(struct tb_switch *sw)
 {
        struct tb_port *up, *down;
-       u64 route = tb_route(sw);
-       unsigned int width_mask;
+       unsigned int width;
        int ret;
 
-       if (!route)
-               return 0;
-
        if (!tb_switch_lane_bonding_possible(sw))
                return 0;
 
        up = tb_upstream_port(sw);
        down = tb_switch_downstream_port(sw);
 
-       if (!tb_port_is_width_supported(up, TB_LINK_WIDTH_DUAL) ||
-           !tb_port_is_width_supported(down, TB_LINK_WIDTH_DUAL))
+       if (!tb_port_width_supported(up, TB_LINK_WIDTH_DUAL) ||
+           !tb_port_width_supported(down, TB_LINK_WIDTH_DUAL))
                return 0;
 
        /*
@@ -2746,21 +2836,10 @@ int tb_switch_lane_bonding_enable(struct tb_switch *sw)
        }
 
        /* Any of the widths are all bonded */
-       width_mask = TB_LINK_WIDTH_DUAL | TB_LINK_WIDTH_ASYM_TX |
-                    TB_LINK_WIDTH_ASYM_RX;
-
-       ret = tb_port_wait_for_link_width(down, width_mask, 100);
-       if (ret) {
-               tb_port_warn(down, "timeout enabling lane bonding\n");
-               return ret;
-       }
+       width = TB_LINK_WIDTH_DUAL | TB_LINK_WIDTH_ASYM_TX |
+               TB_LINK_WIDTH_ASYM_RX;
 
-       tb_port_update_credits(down);
-       tb_port_update_credits(up);
-       tb_switch_update_link_attributes(sw);
-
-       tb_sw_dbg(sw, "lane bonding enabled\n");
-       return ret;
+       return tb_port_wait_for_link_width(down, width, 100);
 }
 
 /**
@@ -2770,20 +2849,27 @@ int tb_switch_lane_bonding_enable(struct tb_switch *sw)
  * Disables lane bonding between @sw and parent. This can be called even
  * if lanes were not bonded originally.
  */
-void tb_switch_lane_bonding_disable(struct tb_switch *sw)
+static int tb_switch_lane_bonding_disable(struct tb_switch *sw)
 {
        struct tb_port *up, *down;
        int ret;
 
-       if (!tb_route(sw))
-               return;
-
        up = tb_upstream_port(sw);
        if (!up->bonded)
-               return;
+               return 0;
 
-       down = tb_switch_downstream_port(sw);
+       /*
+        * If the link is Gen 4 there is no way to switch the link to
+        * two single lane links so avoid that here. Also don't bother
+        * if the link is not up anymore (sw is unplugged).
+        */
+       ret = tb_port_get_link_generation(up);
+       if (ret < 0)
+               return ret;
+       if (ret >= 4)
+               return -EOPNOTSUPP;
 
+       down = tb_switch_downstream_port(sw);
        tb_port_lane_bonding_disable(up);
        tb_port_lane_bonding_disable(down);
 
@@ -2791,15 +2877,160 @@ void tb_switch_lane_bonding_disable(struct tb_switch *sw)
         * It is fine if we get other errors as the router might have
         * been unplugged.
         */
-       ret = tb_port_wait_for_link_width(down, TB_LINK_WIDTH_SINGLE, 100);
-       if (ret == -ETIMEDOUT)
-               tb_sw_warn(sw, "timeout disabling lane bonding\n");
+       return tb_port_wait_for_link_width(down, TB_LINK_WIDTH_SINGLE, 100);
+}
+
+static int tb_switch_asym_enable(struct tb_switch *sw, enum tb_link_width width)
+{
+       struct tb_port *up, *down, *port;
+       enum tb_link_width down_width;
+       int ret;
+
+       up = tb_upstream_port(sw);
+       down = tb_switch_downstream_port(sw);
+
+       if (width == TB_LINK_WIDTH_ASYM_TX) {
+               down_width = TB_LINK_WIDTH_ASYM_RX;
+               port = down;
+       } else {
+               down_width = TB_LINK_WIDTH_ASYM_TX;
+               port = up;
+       }
+
+       ret = tb_port_set_link_width(up, width);
+       if (ret)
+               return ret;
+
+       ret = tb_port_set_link_width(down, down_width);
+       if (ret)
+               return ret;
+
+       /*
+        * Initiate the change in the router that one of its TX lanes is
+        * changing to RX but do so only if there is an actual change.
+        */
+       if (sw->link_width != width) {
+               ret = usb4_port_asym_start(port);
+               if (ret)
+                       return ret;
+
+               ret = tb_port_wait_for_link_width(up, width, 100);
+               if (ret)
+                       return ret;
+       }
+
+       sw->link_width = width;
+       return 0;
+}
+
+static int tb_switch_asym_disable(struct tb_switch *sw)
+{
+       struct tb_port *up, *down;
+       int ret;
+
+       up = tb_upstream_port(sw);
+       down = tb_switch_downstream_port(sw);
+
+       ret = tb_port_set_link_width(up, TB_LINK_WIDTH_DUAL);
+       if (ret)
+               return ret;
+
+       ret = tb_port_set_link_width(down, TB_LINK_WIDTH_DUAL);
+       if (ret)
+               return ret;
+
+       /*
+        * Initiate the change in the router that has three TX lanes and
+        * is changing one of its TX lanes to RX but only if there is a
+        * change in the link width.
+        */
+       if (sw->link_width > TB_LINK_WIDTH_DUAL) {
+               if (sw->link_width == TB_LINK_WIDTH_ASYM_TX)
+                       ret = usb4_port_asym_start(up);
+               else
+                       ret = usb4_port_asym_start(down);
+               if (ret)
+                       return ret;
+
+               ret = tb_port_wait_for_link_width(up, TB_LINK_WIDTH_DUAL, 100);
+               if (ret)
+                       return ret;
+       }
+
+       sw->link_width = TB_LINK_WIDTH_DUAL;
+       return 0;
+}
+
+/**
+ * tb_switch_set_link_width() - Configure router link width
+ * @sw: Router to configure
+ * @width: The new link width
+ *
+ * Set device router link width to @width from router upstream port
+ * perspective. Supports also asymmetric links if the routers boths side
+ * of the link supports it.
+ *
+ * Does nothing for host router.
+ *
+ * Returns %0 in case of success, negative errno otherwise.
+ */
+int tb_switch_set_link_width(struct tb_switch *sw, enum tb_link_width width)
+{
+       struct tb_port *up, *down;
+       int ret = 0;
+
+       if (!tb_route(sw))
+               return 0;
+
+       up = tb_upstream_port(sw);
+       down = tb_switch_downstream_port(sw);
+
+       switch (width) {
+       case TB_LINK_WIDTH_SINGLE:
+               ret = tb_switch_lane_bonding_disable(sw);
+               break;
+
+       case TB_LINK_WIDTH_DUAL:
+               if (sw->link_width == TB_LINK_WIDTH_ASYM_TX ||
+                   sw->link_width == TB_LINK_WIDTH_ASYM_RX) {
+                       ret = tb_switch_asym_disable(sw);
+                       if (ret)
+                               break;
+               }
+               ret = tb_switch_lane_bonding_enable(sw);
+               break;
+
+       case TB_LINK_WIDTH_ASYM_TX:
+       case TB_LINK_WIDTH_ASYM_RX:
+               ret = tb_switch_asym_enable(sw, width);
+               break;
+       }
+
+       switch (ret) {
+       case 0:
+               break;
+
+       case -ETIMEDOUT:
+               tb_sw_warn(sw, "timeout changing link width\n");
+               return ret;
+
+       case -ENOTCONN:
+       case -EOPNOTSUPP:
+       case -ENODEV:
+               return ret;
+
+       default:
+               tb_sw_dbg(sw, "failed to change link width: %d\n", ret);
+               return ret;
+       }
 
        tb_port_update_credits(down);
        tb_port_update_credits(up);
+
        tb_switch_update_link_attributes(sw);
 
-       tb_sw_dbg(sw, "lane bonding disabled\n");
+       tb_sw_dbg(sw, "link width set to %s\n", width_name(width));
+       return ret;
 }
 
 /**
@@ -2959,6 +3190,8 @@ int tb_switch_add(struct tb_switch *sw)
                if (ret)
                        return ret;
 
+               tb_switch_link_init(sw);
+
                ret = tb_switch_clx_init(sw);
                if (ret)
                        return ret;
index 27bd6ca6f99e4101b8b4d8c9eab8c58cfd034726..5acdeb766860da51f912677a37c99c6b0166d922 100644 (file)
 #include "tb_regs.h"
 #include "tunnel.h"
 
-#define TB_TIMEOUT     100     /* ms */
-#define MAX_GROUPS     7       /* max Group_ID is 7 */
+#define TB_TIMEOUT             100     /* ms */
+
+/*
+ * Minimum bandwidth (in Mb/s) that is needed in the single transmitter/receiver
+ * direction. This is 40G - 10% guard band bandwidth.
+ */
+#define TB_ASYM_MIN            (40000 * 90 / 100)
+
+/*
+ * Threshold bandwidth (in Mb/s) that is used to switch the links to
+ * asymmetric and back. This is selected as 45G which means when the
+ * request is higher than this, we switch the link to asymmetric, and
+ * when it is less than this we switch it back. The 45G is selected so
+ * that we still have 27G (of the total 72G) for bulk PCIe traffic when
+ * switching back to symmetric.
+ */
+#define TB_ASYM_THRESHOLD      45000
+
+#define MAX_GROUPS             7       /* max Group_ID is 7 */
+
+static unsigned int asym_threshold = TB_ASYM_THRESHOLD;
+module_param_named(asym_threshold, asym_threshold, uint, 0444);
+MODULE_PARM_DESC(asym_threshold,
+               "threshold (Mb/s) when to Gen 4 switch link symmetry. 0 disables. (default: "
+               __MODULE_STRING(TB_ASYM_THRESHOLD) ")");
 
 /**
  * struct tb_cm - Simple Thunderbolt connection manager
@@ -190,7 +213,7 @@ static void tb_add_dp_resources(struct tb_switch *sw)
                if (!tb_switch_query_dp_resource(sw, port))
                        continue;
 
-               list_add_tail(&port->list, &tcm->dp_resources);
+               list_add(&port->list, &tcm->dp_resources);
                tb_port_dbg(port, "DP IN resource available\n");
        }
 }
@@ -255,13 +278,13 @@ static int tb_enable_clx(struct tb_switch *sw)
         * this in the future to cover the whole topology if it turns
         * out to be beneficial.
         */
-       while (sw && sw->config.depth > 1)
+       while (sw && tb_switch_depth(sw) > 1)
                sw = tb_switch_parent(sw);
 
        if (!sw)
                return 0;
 
-       if (sw->config.depth != 1)
+       if (tb_switch_depth(sw) != 1)
                return 0;
 
        /*
@@ -285,14 +308,32 @@ static int tb_enable_clx(struct tb_switch *sw)
        return ret == -EOPNOTSUPP ? 0 : ret;
 }
 
-/* Disables CL states up to the host router */
-static void tb_disable_clx(struct tb_switch *sw)
+/**
+ * tb_disable_clx() - Disable CL states up to host router
+ * @sw: Router to start
+ *
+ * Disables CL states from @sw up to the host router. Returns true if
+ * any CL state were disabled. This can be used to figure out whether
+ * the link was setup by us or the boot firmware so we don't
+ * accidentally enable them if they were not enabled during discovery.
+ */
+static bool tb_disable_clx(struct tb_switch *sw)
 {
+       bool disabled = false;
+
        do {
-               if (tb_switch_clx_disable(sw) < 0)
+               int ret;
+
+               ret = tb_switch_clx_disable(sw);
+               if (ret > 0)
+                       disabled = true;
+               else if (ret < 0)
                        tb_sw_warn(sw, "failed to disable CL states\n");
+
                sw = tb_switch_parent(sw);
        } while (sw);
+
+       return disabled;
 }
 
 static int tb_increase_switch_tmu_accuracy(struct device *dev, void *data)
@@ -553,7 +594,7 @@ static struct tb_tunnel *tb_find_first_usb3_tunnel(struct tb *tb,
        struct tb_switch *sw;
 
        /* Pick the router that is deepest in the topology */
-       if (dst_port->sw->config.depth > src_port->sw->config.depth)
+       if (tb_port_path_direction_downstream(src_port, dst_port))
                sw = dst_port->sw;
        else
                sw = src_port->sw;
@@ -572,133 +613,294 @@ static struct tb_tunnel *tb_find_first_usb3_tunnel(struct tb *tb,
        return tb_find_tunnel(tb, TB_TUNNEL_USB3, usb3_down, NULL);
 }
 
-static int tb_available_bandwidth(struct tb *tb, struct tb_port *src_port,
-       struct tb_port *dst_port, int *available_up, int *available_down)
-{
-       int usb3_consumed_up, usb3_consumed_down, ret;
-       struct tb_cm *tcm = tb_priv(tb);
+/**
+ * tb_consumed_usb3_pcie_bandwidth() - Consumed USB3/PCIe bandwidth over a single link
+ * @tb: Domain structure
+ * @src_port: Source protocol adapter
+ * @dst_port: Destination protocol adapter
+ * @port: USB4 port the consumed bandwidth is calculated
+ * @consumed_up: Consumed upsream bandwidth (Mb/s)
+ * @consumed_down: Consumed downstream bandwidth (Mb/s)
+ *
+ * Calculates consumed USB3 and PCIe bandwidth at @port between path
+ * from @src_port to @dst_port. Does not take tunnel starting from
+ * @src_port and ending from @src_port into account.
+ */
+static int tb_consumed_usb3_pcie_bandwidth(struct tb *tb,
+                                          struct tb_port *src_port,
+                                          struct tb_port *dst_port,
+                                          struct tb_port *port,
+                                          int *consumed_up,
+                                          int *consumed_down)
+{
+       int pci_consumed_up, pci_consumed_down;
        struct tb_tunnel *tunnel;
-       struct tb_port *port;
 
-       tb_dbg(tb, "calculating available bandwidth between %llx:%u <-> %llx:%u\n",
-              tb_route(src_port->sw), src_port->port, tb_route(dst_port->sw),
-              dst_port->port);
+       *consumed_up = *consumed_down = 0;
 
        tunnel = tb_find_first_usb3_tunnel(tb, src_port, dst_port);
        if (tunnel && tunnel->src_port != src_port &&
            tunnel->dst_port != dst_port) {
-               ret = tb_tunnel_consumed_bandwidth(tunnel, &usb3_consumed_up,
-                                                  &usb3_consumed_down);
+               int ret;
+
+               ret = tb_tunnel_consumed_bandwidth(tunnel, consumed_up,
+                                                  consumed_down);
                if (ret)
                        return ret;
-       } else {
-               usb3_consumed_up = 0;
-               usb3_consumed_down = 0;
        }
 
-       /* Maximum possible bandwidth asymmetric Gen 4 link is 120 Gb/s */
-       *available_up = *available_down = 120000;
+       /*
+        * If there is anything reserved for PCIe bulk traffic take it
+        * into account here too.
+        */
+       if (tb_tunnel_reserved_pci(port, &pci_consumed_up, &pci_consumed_down)) {
+               *consumed_up += pci_consumed_up;
+               *consumed_down += pci_consumed_down;
+       }
 
-       /* Find the minimum available bandwidth over all links */
-       tb_for_each_port_on_path(src_port, dst_port, port) {
-               int link_speed, link_width, up_bw, down_bw;
+       return 0;
+}
 
-               if (!tb_port_is_null(port))
+/**
+ * tb_consumed_dp_bandwidth() - Consumed DP bandwidth over a single link
+ * @tb: Domain structure
+ * @src_port: Source protocol adapter
+ * @dst_port: Destination protocol adapter
+ * @port: USB4 port the consumed bandwidth is calculated
+ * @consumed_up: Consumed upsream bandwidth (Mb/s)
+ * @consumed_down: Consumed downstream bandwidth (Mb/s)
+ *
+ * Calculates consumed DP bandwidth at @port between path from @src_port
+ * to @dst_port. Does not take tunnel starting from @src_port and ending
+ * from @src_port into account.
+ */
+static int tb_consumed_dp_bandwidth(struct tb *tb,
+                                   struct tb_port *src_port,
+                                   struct tb_port *dst_port,
+                                   struct tb_port *port,
+                                   int *consumed_up,
+                                   int *consumed_down)
+{
+       struct tb_cm *tcm = tb_priv(tb);
+       struct tb_tunnel *tunnel;
+       int ret;
+
+       *consumed_up = *consumed_down = 0;
+
+       /*
+        * Find all DP tunnels that cross the port and reduce
+        * their consumed bandwidth from the available.
+        */
+       list_for_each_entry(tunnel, &tcm->tunnel_list, list) {
+               int dp_consumed_up, dp_consumed_down;
+
+               if (tb_tunnel_is_invalid(tunnel))
                        continue;
 
-               if (tb_is_upstream_port(port)) {
-                       link_speed = port->sw->link_speed;
+               if (!tb_tunnel_is_dp(tunnel))
+                       continue;
+
+               if (!tb_tunnel_port_on_path(tunnel, port))
+                       continue;
+
+               /*
+                * Ignore the DP tunnel between src_port and dst_port
+                * because it is the same tunnel and we may be
+                * re-calculating estimated bandwidth.
+                */
+               if (tunnel->src_port == src_port &&
+                   tunnel->dst_port == dst_port)
+                       continue;
+
+               ret = tb_tunnel_consumed_bandwidth(tunnel, &dp_consumed_up,
+                                                  &dp_consumed_down);
+               if (ret)
+                       return ret;
+
+               *consumed_up += dp_consumed_up;
+               *consumed_down += dp_consumed_down;
+       }
+
+       return 0;
+}
+
+static bool tb_asym_supported(struct tb_port *src_port, struct tb_port *dst_port,
+                             struct tb_port *port)
+{
+       bool downstream = tb_port_path_direction_downstream(src_port, dst_port);
+       enum tb_link_width width;
+
+       if (tb_is_upstream_port(port))
+               width = downstream ? TB_LINK_WIDTH_ASYM_RX : TB_LINK_WIDTH_ASYM_TX;
+       else
+               width = downstream ? TB_LINK_WIDTH_ASYM_TX : TB_LINK_WIDTH_ASYM_RX;
+
+       return tb_port_width_supported(port, width);
+}
+
+/**
+ * tb_maximum_bandwidth() - Maximum bandwidth over a single link
+ * @tb: Domain structure
+ * @src_port: Source protocol adapter
+ * @dst_port: Destination protocol adapter
+ * @port: USB4 port the total bandwidth is calculated
+ * @max_up: Maximum upstream bandwidth (Mb/s)
+ * @max_down: Maximum downstream bandwidth (Mb/s)
+ * @include_asym: Include bandwidth if the link is switched from
+ *               symmetric to asymmetric
+ *
+ * Returns maximum possible bandwidth in @max_up and @max_down over a
+ * single link at @port. If @include_asym is set then includes the
+ * additional banwdith if the links are transitioned into asymmetric to
+ * direction from @src_port to @dst_port.
+ */
+static int tb_maximum_bandwidth(struct tb *tb, struct tb_port *src_port,
+                               struct tb_port *dst_port, struct tb_port *port,
+                               int *max_up, int *max_down, bool include_asym)
+{
+       bool downstream = tb_port_path_direction_downstream(src_port, dst_port);
+       int link_speed, link_width, up_bw, down_bw;
+
+       /*
+        * Can include asymmetric, only if it is actually supported by
+        * the lane adapter.
+        */
+       if (!tb_asym_supported(src_port, dst_port, port))
+               include_asym = false;
+
+       if (tb_is_upstream_port(port)) {
+               link_speed = port->sw->link_speed;
+               /*
+                * sw->link_width is from upstream perspective so we use
+                * the opposite for downstream of the host router.
+                */
+               if (port->sw->link_width == TB_LINK_WIDTH_ASYM_TX) {
+                       up_bw = link_speed * 3 * 1000;
+                       down_bw = link_speed * 1 * 1000;
+               } else if (port->sw->link_width == TB_LINK_WIDTH_ASYM_RX) {
+                       up_bw = link_speed * 1 * 1000;
+                       down_bw = link_speed * 3 * 1000;
+               } else if (include_asym) {
                        /*
-                        * sw->link_width is from upstream perspective
-                        * so we use the opposite for downstream of the
-                        * host router.
+                        * The link is symmetric at the moment but we
+                        * can switch it to asymmetric as needed. Report
+                        * this bandwidth as available (even though it
+                        * is not yet enabled).
                         */
-                       if (port->sw->link_width == TB_LINK_WIDTH_ASYM_TX) {
-                               up_bw = link_speed * 3 * 1000;
-                               down_bw = link_speed * 1 * 1000;
-                       } else if (port->sw->link_width == TB_LINK_WIDTH_ASYM_RX) {
+                       if (downstream) {
                                up_bw = link_speed * 1 * 1000;
                                down_bw = link_speed * 3 * 1000;
                        } else {
-                               up_bw = link_speed * port->sw->link_width * 1000;
-                               down_bw = up_bw;
+                               up_bw = link_speed * 3 * 1000;
+                               down_bw = link_speed * 1 * 1000;
                        }
                } else {
-                       link_speed = tb_port_get_link_speed(port);
-                       if (link_speed < 0)
-                               return link_speed;
-
-                       link_width = tb_port_get_link_width(port);
-                       if (link_width < 0)
-                               return link_width;
-
-                       if (link_width == TB_LINK_WIDTH_ASYM_TX) {
+                       up_bw = link_speed * port->sw->link_width * 1000;
+                       down_bw = up_bw;
+               }
+       } else {
+               link_speed = tb_port_get_link_speed(port);
+               if (link_speed < 0)
+                       return link_speed;
+
+               link_width = tb_port_get_link_width(port);
+               if (link_width < 0)
+                       return link_width;
+
+               if (link_width == TB_LINK_WIDTH_ASYM_TX) {
+                       up_bw = link_speed * 1 * 1000;
+                       down_bw = link_speed * 3 * 1000;
+               } else if (link_width == TB_LINK_WIDTH_ASYM_RX) {
+                       up_bw = link_speed * 3 * 1000;
+                       down_bw = link_speed * 1 * 1000;
+               } else if (include_asym) {
+                       /*
+                        * The link is symmetric at the moment but we
+                        * can switch it to asymmetric as needed. Report
+                        * this bandwidth as available (even though it
+                        * is not yet enabled).
+                        */
+                       if (downstream) {
                                up_bw = link_speed * 1 * 1000;
                                down_bw = link_speed * 3 * 1000;
-                       } else if (link_width == TB_LINK_WIDTH_ASYM_RX) {
+                       } else {
                                up_bw = link_speed * 3 * 1000;
                                down_bw = link_speed * 1 * 1000;
-                       } else {
-                               up_bw = link_speed * link_width * 1000;
-                               down_bw = up_bw;
                        }
+               } else {
+                       up_bw = link_speed * link_width * 1000;
+                       down_bw = up_bw;
                }
+       }
 
-               /* Leave 10% guard band */
-               up_bw -= up_bw / 10;
-               down_bw -= down_bw / 10;
-
-               tb_port_dbg(port, "link total bandwidth %d/%d Mb/s\n", up_bw,
-                           down_bw);
+       /* Leave 10% guard band */
+       *max_up = up_bw - up_bw / 10;
+       *max_down = down_bw - down_bw / 10;
 
-               /*
-                * Find all DP tunnels that cross the port and reduce
-                * their consumed bandwidth from the available.
-                */
-               list_for_each_entry(tunnel, &tcm->tunnel_list, list) {
-                       int dp_consumed_up, dp_consumed_down;
+       tb_port_dbg(port, "link maximum bandwidth %d/%d Mb/s\n", *max_up, *max_down);
+       return 0;
+}
 
-                       if (tb_tunnel_is_invalid(tunnel))
-                               continue;
+/**
+ * tb_available_bandwidth() - Available bandwidth for tunneling
+ * @tb: Domain structure
+ * @src_port: Source protocol adapter
+ * @dst_port: Destination protocol adapter
+ * @available_up: Available bandwidth upstream (Mb/s)
+ * @available_down: Available bandwidth downstream (Mb/s)
+ * @include_asym: Include bandwidth if the link is switched from
+ *               symmetric to asymmetric
+ *
+ * Calculates maximum available bandwidth for protocol tunneling between
+ * @src_port and @dst_port at the moment. This is minimum of maximum
+ * link bandwidth across all links reduced by currently consumed
+ * bandwidth on that link.
+ *
+ * If @include_asym is true then includes also bandwidth that can be
+ * added when the links are transitioned into asymmetric (but does not
+ * transition the links).
+ */
+static int tb_available_bandwidth(struct tb *tb, struct tb_port *src_port,
+                                struct tb_port *dst_port, int *available_up,
+                                int *available_down, bool include_asym)
+{
+       struct tb_port *port;
+       int ret;
 
-                       if (!tb_tunnel_is_dp(tunnel))
-                               continue;
+       /* Maximum possible bandwidth asymmetric Gen 4 link is 120 Gb/s */
+       *available_up = *available_down = 120000;
 
-                       if (!tb_tunnel_port_on_path(tunnel, port))
-                               continue;
+       /* Find the minimum available bandwidth over all links */
+       tb_for_each_port_on_path(src_port, dst_port, port) {
+               int max_up, max_down, consumed_up, consumed_down;
 
-                       /*
-                        * Ignore the DP tunnel between src_port and
-                        * dst_port because it is the same tunnel and we
-                        * may be re-calculating estimated bandwidth.
-                        */
-                       if (tunnel->src_port == src_port &&
-                           tunnel->dst_port == dst_port)
-                               continue;
+               if (!tb_port_is_null(port))
+                       continue;
 
-                       ret = tb_tunnel_consumed_bandwidth(tunnel,
-                                                          &dp_consumed_up,
-                                                          &dp_consumed_down);
-                       if (ret)
-                               return ret;
+               ret = tb_maximum_bandwidth(tb, src_port, dst_port, port,
+                                          &max_up, &max_down, include_asym);
+               if (ret)
+                       return ret;
 
-                       up_bw -= dp_consumed_up;
-                       down_bw -= dp_consumed_down;
-               }
+               ret = tb_consumed_usb3_pcie_bandwidth(tb, src_port, dst_port,
+                                                     port, &consumed_up,
+                                                     &consumed_down);
+               if (ret)
+                       return ret;
+               max_up -= consumed_up;
+               max_down -= consumed_down;
 
-               /*
-                * If USB3 is tunneled from the host router down to the
-                * branch leading to port we need to take USB3 consumed
-                * bandwidth into account regardless whether it actually
-                * crosses the port.
-                */
-               up_bw -= usb3_consumed_up;
-               down_bw -= usb3_consumed_down;
+               ret = tb_consumed_dp_bandwidth(tb, src_port, dst_port, port,
+                                              &consumed_up, &consumed_down);
+               if (ret)
+                       return ret;
+               max_up -= consumed_up;
+               max_down -= consumed_down;
 
-               if (up_bw < *available_up)
-                       *available_up = up_bw;
-               if (down_bw < *available_down)
-                       *available_down = down_bw;
+               if (max_up < *available_up)
+                       *available_up = max_up;
+               if (max_down < *available_down)
+                       *available_down = max_down;
        }
 
        if (*available_up < 0)
@@ -729,21 +931,21 @@ static void tb_reclaim_usb3_bandwidth(struct tb *tb, struct tb_port *src_port,
        if (!tunnel)
                return;
 
-       tb_dbg(tb, "reclaiming unused bandwidth for USB3\n");
+       tb_tunnel_dbg(tunnel, "reclaiming unused bandwidth\n");
 
        /*
         * Calculate available bandwidth for the first hop USB3 tunnel.
         * That determines the whole USB3 bandwidth for this branch.
         */
        ret = tb_available_bandwidth(tb, tunnel->src_port, tunnel->dst_port,
-                                    &available_up, &available_down);
+                                    &available_up, &available_down, false);
        if (ret) {
-               tb_warn(tb, "failed to calculate available bandwidth\n");
+               tb_tunnel_warn(tunnel, "failed to calculate available bandwidth\n");
                return;
        }
 
-       tb_dbg(tb, "available bandwidth for USB3 %d/%d Mb/s\n",
-              available_up, available_down);
+       tb_tunnel_dbg(tunnel, "available bandwidth %d/%d Mb/s\n", available_up,
+                     available_down);
 
        tb_tunnel_reclaim_available_bandwidth(tunnel, &available_up, &available_down);
 }
@@ -794,8 +996,8 @@ static int tb_tunnel_usb3(struct tb *tb, struct tb_switch *sw)
                        return ret;
        }
 
-       ret = tb_available_bandwidth(tb, down, up, &available_up,
-                                    &available_down);
+       ret = tb_available_bandwidth(tb, down, up, &available_up, &available_down,
+                                    false);
        if (ret)
                goto err_reclaim;
 
@@ -856,6 +1058,225 @@ static int tb_create_usb3_tunnels(struct tb_switch *sw)
        return 0;
 }
 
+/**
+ * tb_configure_asym() - Transition links to asymmetric if needed
+ * @tb: Domain structure
+ * @src_port: Source adapter to start the transition
+ * @dst_port: Destination adapter
+ * @requested_up: Additional bandwidth (Mb/s) required upstream
+ * @requested_down: Additional bandwidth (Mb/s) required downstream
+ *
+ * Transition links between @src_port and @dst_port into asymmetric, with
+ * three lanes in the direction from @src_port towards @dst_port and one lane
+ * in the opposite direction, if the bandwidth requirements
+ * (requested + currently consumed) on that link exceed @asym_threshold.
+ *
+ * Must be called with available >= requested over all links.
+ */
+static int tb_configure_asym(struct tb *tb, struct tb_port *src_port,
+                            struct tb_port *dst_port, int requested_up,
+                            int requested_down)
+{
+       struct tb_switch *sw;
+       bool clx, downstream;
+       struct tb_port *up;
+       int ret = 0;
+
+       if (!asym_threshold)
+               return 0;
+
+       /* Disable CL states before doing any transitions */
+       downstream = tb_port_path_direction_downstream(src_port, dst_port);
+       /* Pick up router deepest in the hierarchy */
+       if (downstream)
+               sw = dst_port->sw;
+       else
+               sw = src_port->sw;
+
+       clx = tb_disable_clx(sw);
+
+       tb_for_each_upstream_port_on_path(src_port, dst_port, up) {
+               int consumed_up, consumed_down;
+               enum tb_link_width width;
+
+               ret = tb_consumed_dp_bandwidth(tb, src_port, dst_port, up,
+                                              &consumed_up, &consumed_down);
+               if (ret)
+                       break;
+
+               if (downstream) {
+                       /*
+                        * Downstream so make sure upstream is within the 36G
+                        * (40G - guard band 10%), and the requested is above
+                        * what the threshold is.
+                        */
+                       if (consumed_up + requested_up >= TB_ASYM_MIN) {
+                               ret = -ENOBUFS;
+                               break;
+                       }
+                       /* Does consumed + requested exceed the threshold */
+                       if (consumed_down + requested_down < asym_threshold)
+                               continue;
+
+                       width = TB_LINK_WIDTH_ASYM_RX;
+               } else {
+                       /* Upstream, the opposite of above */
+                       if (consumed_down + requested_down >= TB_ASYM_MIN) {
+                               ret = -ENOBUFS;
+                               break;
+                       }
+                       if (consumed_up + requested_up < asym_threshold)
+                               continue;
+
+                       width = TB_LINK_WIDTH_ASYM_TX;
+               }
+
+               if (up->sw->link_width == width)
+                       continue;
+
+               if (!tb_port_width_supported(up, width))
+                       continue;
+
+               tb_sw_dbg(up->sw, "configuring asymmetric link\n");
+
+               /*
+                * Here requested + consumed > threshold so we need to
+                * transtion the link into asymmetric now.
+                */
+               ret = tb_switch_set_link_width(up->sw, width);
+               if (ret) {
+                       tb_sw_warn(up->sw, "failed to set link width\n");
+                       break;
+               }
+       }
+
+       /* Re-enable CL states if they were previosly enabled */
+       if (clx)
+               tb_enable_clx(sw);
+
+       return ret;
+}
+
+/**
+ * tb_configure_sym() - Transition links to symmetric if possible
+ * @tb: Domain structure
+ * @src_port: Source adapter to start the transition
+ * @dst_port: Destination adapter
+ * @requested_up: New lower bandwidth request upstream (Mb/s)
+ * @requested_down: New lower bandwidth request downstream (Mb/s)
+ *
+ * Goes over each link from @src_port to @dst_port and tries to
+ * transition the link to symmetric if the currently consumed bandwidth
+ * allows.
+ */
+static int tb_configure_sym(struct tb *tb, struct tb_port *src_port,
+                           struct tb_port *dst_port, int requested_up,
+                           int requested_down)
+{
+       struct tb_switch *sw;
+       bool clx, downstream;
+       struct tb_port *up;
+       int ret = 0;
+
+       if (!asym_threshold)
+               return 0;
+
+       /* Disable CL states before doing any transitions */
+       downstream = tb_port_path_direction_downstream(src_port, dst_port);
+       /* Pick up router deepest in the hierarchy */
+       if (downstream)
+               sw = dst_port->sw;
+       else
+               sw = src_port->sw;
+
+       clx = tb_disable_clx(sw);
+
+       tb_for_each_upstream_port_on_path(src_port, dst_port, up) {
+               int consumed_up, consumed_down;
+
+               /* Already symmetric */
+               if (up->sw->link_width <= TB_LINK_WIDTH_DUAL)
+                       continue;
+               /* Unplugged, no need to switch */
+               if (up->sw->is_unplugged)
+                       continue;
+
+               ret = tb_consumed_dp_bandwidth(tb, src_port, dst_port, up,
+                                              &consumed_up, &consumed_down);
+               if (ret)
+                       break;
+
+               if (downstream) {
+                       /*
+                        * Downstream so we want the consumed_down < threshold.
+                        * Upstream traffic should be less than 36G (40G
+                        * guard band 10%) as the link was configured asymmetric
+                        * already.
+                        */
+                       if (consumed_down + requested_down >= asym_threshold)
+                               continue;
+               } else {
+                       if (consumed_up + requested_up >= asym_threshold)
+                               continue;
+               }
+
+               if (up->sw->link_width == TB_LINK_WIDTH_DUAL)
+                       continue;
+
+               tb_sw_dbg(up->sw, "configuring symmetric link\n");
+
+               ret = tb_switch_set_link_width(up->sw, TB_LINK_WIDTH_DUAL);
+               if (ret) {
+                       tb_sw_warn(up->sw, "failed to set link width\n");
+                       break;
+               }
+       }
+
+       /* Re-enable CL states if they were previosly enabled */
+       if (clx)
+               tb_enable_clx(sw);
+
+       return ret;
+}
+
+static void tb_configure_link(struct tb_port *down, struct tb_port *up,
+                             struct tb_switch *sw)
+{
+       struct tb *tb = sw->tb;
+
+       /* Link the routers using both links if available */
+       down->remote = up;
+       up->remote = down;
+       if (down->dual_link_port && up->dual_link_port) {
+               down->dual_link_port->remote = up->dual_link_port;
+               up->dual_link_port->remote = down->dual_link_port;
+       }
+
+       /*
+        * Enable lane bonding if the link is currently two single lane
+        * links.
+        */
+       if (sw->link_width < TB_LINK_WIDTH_DUAL)
+               tb_switch_set_link_width(sw, TB_LINK_WIDTH_DUAL);
+
+       /*
+        * Device router that comes up as symmetric link is
+        * connected deeper in the hierarchy, we transition the links
+        * above into symmetric if bandwidth allows.
+        */
+       if (tb_switch_depth(sw) > 1 &&
+           tb_port_get_link_generation(up) >= 4 &&
+           up->sw->link_width == TB_LINK_WIDTH_DUAL) {
+               struct tb_port *host_port;
+
+               host_port = tb_port_at(tb_route(sw), tb->root_switch);
+               tb_configure_sym(tb, host_port, up, 0, 0);
+       }
+
+       /* Set the link configured */
+       tb_switch_configure_link(sw);
+}
+
 static void tb_scan_port(struct tb_port *port);
 
 /*
@@ -964,19 +1385,9 @@ static void tb_scan_port(struct tb_port *port)
                goto out_rpm_put;
        }
 
-       /* Link the switches using both links if available */
        upstream_port = tb_upstream_port(sw);
-       port->remote = upstream_port;
-       upstream_port->remote = port;
-       if (port->dual_link_port && upstream_port->dual_link_port) {
-               port->dual_link_port->remote = upstream_port->dual_link_port;
-               upstream_port->dual_link_port->remote = port->dual_link_port;
-       }
+       tb_configure_link(port, upstream_port, sw);
 
-       /* Enable lane bonding if supported */
-       tb_switch_lane_bonding_enable(sw);
-       /* Set the link configured */
-       tb_switch_configure_link(sw);
        /*
         * CL0s and CL1 are enabled and supported together.
         * Silently ignore CLx enabling in case CLx is not supported.
@@ -1040,6 +1451,11 @@ static void tb_deactivate_and_free_tunnel(struct tb_tunnel *tunnel)
                 * deallocated properly.
                 */
                tb_switch_dealloc_dp_resource(src_port->sw, src_port);
+               /*
+                * If bandwidth on a link is < asym_threshold
+                * transition the link to symmetric.
+                */
+               tb_configure_sym(tb, src_port, dst_port, 0, 0);
                /* Now we can allow the domain to runtime suspend again */
                pm_runtime_mark_last_busy(&dst_port->sw->dev);
                pm_runtime_put_autosuspend(&dst_port->sw->dev);
@@ -1092,7 +1508,8 @@ static void tb_free_unplugged_children(struct tb_switch *sw)
                        tb_retimer_remove_all(port);
                        tb_remove_dp_resources(port->remote->sw);
                        tb_switch_unconfigure_link(port->remote->sw);
-                       tb_switch_lane_bonding_disable(port->remote->sw);
+                       tb_switch_set_link_width(port->remote->sw,
+                                                TB_LINK_WIDTH_SINGLE);
                        tb_switch_remove(port->remote->sw);
                        port->remote = NULL;
                        if (port->dual_link_port)
@@ -1188,7 +1605,7 @@ tb_recalc_estimated_bandwidth_for_group(struct tb_bandwidth_group *group)
                        ret = tb_release_unused_usb3_bandwidth(tb,
                                first_tunnel->src_port, first_tunnel->dst_port);
                        if (ret) {
-                               tb_port_warn(in,
+                               tb_tunnel_warn(tunnel,
                                        "failed to release unused bandwidth\n");
                                break;
                        }
@@ -1196,9 +1613,9 @@ tb_recalc_estimated_bandwidth_for_group(struct tb_bandwidth_group *group)
 
                out = tunnel->dst_port;
                ret = tb_available_bandwidth(tb, in, out, &estimated_up,
-                                            &estimated_down);
+                                            &estimated_down, true);
                if (ret) {
-                       tb_port_warn(in,
+                       tb_tunnel_warn(tunnel,
                                "failed to re-calculate estimated bandwidth\n");
                        break;
                }
@@ -1209,16 +1626,18 @@ tb_recalc_estimated_bandwidth_for_group(struct tb_bandwidth_group *group)
                 *  - available bandwidth along the path
                 *  - bandwidth allocated for USB 3.x but not used.
                 */
-               tb_port_dbg(in, "re-calculated estimated bandwidth %u/%u Mb/s\n",
-                           estimated_up, estimated_down);
+               tb_tunnel_dbg(tunnel,
+                             "re-calculated estimated bandwidth %u/%u Mb/s\n",
+                             estimated_up, estimated_down);
 
-               if (in->sw->config.depth < out->sw->config.depth)
+               if (tb_port_path_direction_downstream(in, out))
                        estimated_bw = estimated_down;
                else
                        estimated_bw = estimated_up;
 
                if (usb4_dp_port_set_estimated_bandwidth(in, estimated_bw))
-                       tb_port_warn(in, "failed to update estimated bandwidth\n");
+                       tb_tunnel_warn(tunnel,
+                                      "failed to update estimated bandwidth\n");
        }
 
        if (first_tunnel)
@@ -1282,18 +1701,14 @@ static struct tb_port *tb_find_dp_out(struct tb *tb, struct tb_port *in)
        return NULL;
 }
 
-static void tb_tunnel_dp(struct tb *tb)
+static bool tb_tunnel_one_dp(struct tb *tb)
 {
        int available_up, available_down, ret, link_nr;
        struct tb_cm *tcm = tb_priv(tb);
        struct tb_port *port, *in, *out;
+       int consumed_up, consumed_down;
        struct tb_tunnel *tunnel;
 
-       if (!tb_acpi_may_tunnel_dp()) {
-               tb_dbg(tb, "DP tunneling disabled, not creating tunnel\n");
-               return;
-       }
-
        /*
         * Find pair of inactive DP IN and DP OUT adapters and then
         * establish a DP tunnel between them.
@@ -1311,22 +1726,21 @@ static void tb_tunnel_dp(struct tb *tb)
                        continue;
                }
 
-               tb_port_dbg(port, "DP IN available\n");
+               in = port;
+               tb_port_dbg(in, "DP IN available\n");
 
                out = tb_find_dp_out(tb, port);
-               if (out) {
-                       in = port;
+               if (out)
                        break;
-               }
        }
 
        if (!in) {
                tb_dbg(tb, "no suitable DP IN adapter available, not tunneling\n");
-               return;
+               return false;
        }
        if (!out) {
                tb_dbg(tb, "no suitable DP OUT adapter available, not tunneling\n");
-               return;
+               return false;
        }
 
        /*
@@ -1369,7 +1783,8 @@ static void tb_tunnel_dp(struct tb *tb)
                goto err_detach_group;
        }
 
-       ret = tb_available_bandwidth(tb, in, out, &available_up, &available_down);
+       ret = tb_available_bandwidth(tb, in, out, &available_up, &available_down,
+                                    true);
        if (ret)
                goto err_reclaim_usb;
 
@@ -1391,6 +1806,13 @@ static void tb_tunnel_dp(struct tb *tb)
        list_add_tail(&tunnel->list, &tcm->tunnel_list);
        tb_reclaim_usb3_bandwidth(tb, in, out);
 
+       /*
+        * Transition the links to asymmetric if the consumption exceeds
+        * the threshold.
+        */
+       if (!tb_tunnel_consumed_bandwidth(tunnel, &consumed_up, &consumed_down))
+               tb_configure_asym(tb, in, out, consumed_up, consumed_down);
+
        /* Update the domain with the new bandwidth estimation */
        tb_recalc_estimated_bandwidth(tb);
 
@@ -1399,7 +1821,7 @@ static void tb_tunnel_dp(struct tb *tb)
         * TMU mode to HiFi for CL0s to work.
         */
        tb_increase_tmu_accuracy(tunnel);
-       return;
+       return true;
 
 err_free:
        tb_tunnel_free(tunnel);
@@ -1414,6 +1836,19 @@ err_rpm_put:
        pm_runtime_put_autosuspend(&out->sw->dev);
        pm_runtime_mark_last_busy(&in->sw->dev);
        pm_runtime_put_autosuspend(&in->sw->dev);
+
+       return false;
+}
+
+static void tb_tunnel_dp(struct tb *tb)
+{
+       if (!tb_acpi_may_tunnel_dp()) {
+               tb_dbg(tb, "DP tunneling disabled, not creating tunnel\n");
+               return;
+       }
+
+       while (tb_tunnel_one_dp(tb))
+               ;
 }
 
 static void tb_dp_resource_unavailable(struct tb *tb, struct tb_port *port)
@@ -1701,7 +2136,8 @@ static void tb_handle_hotplug(struct work_struct *work)
                        tb_remove_dp_resources(port->remote->sw);
                        tb_switch_tmu_disable(port->remote->sw);
                        tb_switch_unconfigure_link(port->remote->sw);
-                       tb_switch_lane_bonding_disable(port->remote->sw);
+                       tb_switch_set_link_width(port->remote->sw,
+                                                TB_LINK_WIDTH_SINGLE);
                        tb_switch_remove(port->remote->sw);
                        port->remote = NULL;
                        if (port->dual_link_port)
@@ -1781,8 +2217,8 @@ static int tb_alloc_dp_bandwidth(struct tb_tunnel *tunnel, int *requested_up,
        in = tunnel->src_port;
        out = tunnel->dst_port;
 
-       tb_port_dbg(in, "bandwidth allocated currently %d/%d Mb/s\n",
-                   allocated_up, allocated_down);
+       tb_tunnel_dbg(tunnel, "bandwidth allocated currently %d/%d Mb/s\n",
+                     allocated_up, allocated_down);
 
        /*
         * If we get rounded up request from graphics side, say HBR2 x 4
@@ -1823,19 +2259,25 @@ static int tb_alloc_dp_bandwidth(struct tb_tunnel *tunnel, int *requested_up,
        else if (requested_down_corrected < 0)
                requested_down_corrected = 0;
 
-       tb_port_dbg(in, "corrected bandwidth request %d/%d Mb/s\n",
-                   requested_up_corrected, requested_down_corrected);
+       tb_tunnel_dbg(tunnel, "corrected bandwidth request %d/%d Mb/s\n",
+                     requested_up_corrected, requested_down_corrected);
 
        if ((*requested_up >= 0 && requested_up_corrected > max_up_rounded) ||
            (*requested_down >= 0 && requested_down_corrected > max_down_rounded)) {
-               tb_port_dbg(in, "bandwidth request too high (%d/%d Mb/s > %d/%d Mb/s)\n",
-                           requested_up_corrected, requested_down_corrected,
-                           max_up_rounded, max_down_rounded);
+               tb_tunnel_dbg(tunnel,
+                             "bandwidth request too high (%d/%d Mb/s > %d/%d Mb/s)\n",
+                             requested_up_corrected, requested_down_corrected,
+                             max_up_rounded, max_down_rounded);
                return -ENOBUFS;
        }
 
        if ((*requested_up >= 0 && requested_up_corrected <= allocated_up) ||
            (*requested_down >= 0 && requested_down_corrected <= allocated_down)) {
+               /*
+                * If bandwidth on a link is < asym_threshold transition
+                * the link to symmetric.
+                */
+               tb_configure_sym(tb, in, out, *requested_up, *requested_down);
                /*
                 * If requested bandwidth is less or equal than what is
                 * currently allocated to that tunnel we simply change
@@ -1861,17 +2303,33 @@ static int tb_alloc_dp_bandwidth(struct tb_tunnel *tunnel, int *requested_up,
         * are also in the same group but we use the same function here
         * that we use with the normal bandwidth allocation).
         */
-       ret = tb_available_bandwidth(tb, in, out, &available_up, &available_down);
+       ret = tb_available_bandwidth(tb, in, out, &available_up, &available_down,
+                                    true);
        if (ret)
                goto reclaim;
 
-       tb_port_dbg(in, "bandwidth available for allocation %d/%d Mb/s\n",
-                   available_up, available_down);
+       tb_tunnel_dbg(tunnel, "bandwidth available for allocation %d/%d Mb/s\n",
+                     available_up, available_down);
 
        if ((*requested_up >= 0 && available_up >= requested_up_corrected) ||
            (*requested_down >= 0 && available_down >= requested_down_corrected)) {
+               /*
+                * If bandwidth on a link is >= asym_threshold
+                * transition the link to asymmetric.
+                */
+               ret = tb_configure_asym(tb, in, out, *requested_up,
+                                       *requested_down);
+               if (ret) {
+                       tb_configure_sym(tb, in, out, 0, 0);
+                       return ret;
+               }
+
                ret = tb_tunnel_alloc_bandwidth(tunnel, requested_up,
                                                requested_down);
+               if (ret) {
+                       tb_tunnel_warn(tunnel, "failed to allocate bandwidth\n");
+                       tb_configure_sym(tb, in, out, 0, 0);
+               }
        } else {
                ret = -ENOBUFS;
        }
@@ -1937,7 +2395,7 @@ static void tb_handle_dp_bandwidth_request(struct work_struct *work)
 
        out = tunnel->dst_port;
 
-       if (in->sw->config.depth < out->sw->config.depth) {
+       if (tb_port_path_direction_downstream(in, out)) {
                requested_up = -1;
                requested_down = requested_bw;
        } else {
@@ -1948,12 +2406,15 @@ static void tb_handle_dp_bandwidth_request(struct work_struct *work)
        ret = tb_alloc_dp_bandwidth(tunnel, &requested_up, &requested_down);
        if (ret) {
                if (ret == -ENOBUFS)
-                       tb_port_warn(in, "not enough bandwidth available\n");
+                       tb_tunnel_warn(tunnel,
+                                      "not enough bandwidth available\n");
                else
-                       tb_port_warn(in, "failed to change bandwidth allocation\n");
+                       tb_tunnel_warn(tunnel,
+                                      "failed to change bandwidth allocation\n");
        } else {
-               tb_port_dbg(in, "bandwidth allocation changed to %d/%d Mb/s\n",
-                           requested_up, requested_down);
+               tb_tunnel_dbg(tunnel,
+                             "bandwidth allocation changed to %d/%d Mb/s\n",
+                             requested_up, requested_down);
 
                /* Update other clients about the allocation change */
                tb_recalc_estimated_bandwidth(tb);
@@ -2181,7 +2642,8 @@ static void tb_restore_children(struct tb_switch *sw)
                        continue;
 
                if (port->remote) {
-                       tb_switch_lane_bonding_enable(port->remote->sw);
+                       tb_switch_set_link_width(port->remote->sw,
+                                                port->remote->sw->link_width);
                        tb_switch_configure_link(port->remote->sw);
 
                        tb_restore_children(port->remote->sw);
index d2a55ad2fd3e698045db391c14ae79d8a8d3f71e..e299e53473ae28627216dacf3d9dbdfcf2ec8239 100644 (file)
@@ -162,11 +162,6 @@ struct tb_switch_tmu {
  * switches) you need to have domain lock held.
  *
  * In USB4 terminology this structure represents a router.
- *
- * Note @link_width is not the same as whether link is bonded or not.
- * For Gen 4 links the link is also bonded when it is asymmetric. The
- * correct way to find out whether the link is bonded or not is to look
- * @bonded field of the upstream port.
  */
 struct tb_switch {
        struct device dev;
@@ -348,6 +343,7 @@ struct tb_retimer {
  *                  the path
  * @nfc_credits: Number of non-flow controlled buffers allocated for the
  *              @in_port.
+ * @pm_support: Set path PM packet support bit to 1 (for USB4 v2 routers)
  *
  * Hop configuration is always done on the IN port of a switch.
  * in_port and out_port have to be on the same switch. Packets arriving on
@@ -368,6 +364,7 @@ struct tb_path_hop {
        int next_hop_index;
        unsigned int initial_credits;
        unsigned int nfc_credits;
+       bool pm_support;
 };
 
 /**
@@ -864,6 +861,15 @@ static inline struct tb_port *tb_switch_downstream_port(struct tb_switch *sw)
        return tb_port_at(tb_route(sw), tb_switch_parent(sw));
 }
 
+/**
+ * tb_switch_depth() - Returns depth of the connected router
+ * @sw: Router
+ */
+static inline int tb_switch_depth(const struct tb_switch *sw)
+{
+       return sw->config.depth;
+}
+
 static inline bool tb_switch_is_light_ridge(const struct tb_switch *sw)
 {
        return sw->config.vendor_id == PCI_VENDOR_ID_INTEL &&
@@ -956,8 +962,7 @@ static inline bool tb_switch_is_icm(const struct tb_switch *sw)
        return !sw->config.enabled;
 }
 
-int tb_switch_lane_bonding_enable(struct tb_switch *sw);
-void tb_switch_lane_bonding_disable(struct tb_switch *sw);
+int tb_switch_set_link_width(struct tb_switch *sw, enum tb_link_width width);
 int tb_switch_configure_link(struct tb_switch *sw);
 void tb_switch_unconfigure_link(struct tb_switch *sw);
 
@@ -1001,7 +1006,6 @@ static inline bool tb_switch_tmu_is_enabled(const struct tb_switch *sw)
 bool tb_port_clx_is_enabled(struct tb_port *port, unsigned int clx);
 
 int tb_switch_clx_init(struct tb_switch *sw);
-bool tb_switch_clx_is_supported(const struct tb_switch *sw);
 int tb_switch_clx_enable(struct tb_switch *sw, unsigned int clx);
 int tb_switch_clx_disable(struct tb_switch *sw);
 
@@ -1040,6 +1044,21 @@ void tb_port_release_out_hopid(struct tb_port *port, int hopid);
 struct tb_port *tb_next_port_on_path(struct tb_port *start, struct tb_port *end,
                                     struct tb_port *prev);
 
+/**
+ * tb_port_path_direction_downstream() - Checks if path directed downstream
+ * @src: Source adapter
+ * @dst: Destination adapter
+ *
+ * Returns %true only if the specified path from source adapter (@src)
+ * to destination adapter (@dst) is directed downstream.
+ */
+static inline bool
+tb_port_path_direction_downstream(const struct tb_port *src,
+                                 const struct tb_port *dst)
+{
+       return src->sw->config.depth < dst->sw->config.depth;
+}
+
 static inline bool tb_port_use_credit_allocation(const struct tb_port *port)
 {
        return tb_port_is_null(port) && port->sw->credit_allocation;
@@ -1057,12 +1076,29 @@ static inline bool tb_port_use_credit_allocation(const struct tb_port *port)
        for ((p) = tb_next_port_on_path((src), (dst), NULL); (p);       \
             (p) = tb_next_port_on_path((src), (dst), (p)))
 
+/**
+ * tb_for_each_upstream_port_on_path() - Iterate over each upstreamm port on path
+ * @src: Source port
+ * @dst: Destination port
+ * @p: Port used as iterator
+ *
+ * Walks over each upstream lane adapter on path from @src to @dst.
+ */
+#define tb_for_each_upstream_port_on_path(src, dst, p)                 \
+       for ((p) = tb_next_port_on_path((src), (dst), NULL); (p);       \
+            (p) = tb_next_port_on_path((src), (dst), (p)))             \
+               if (!tb_port_is_null((p)) || !tb_is_upstream_port((p))) {\
+                       continue;                                       \
+               } else
+
 int tb_port_get_link_speed(struct tb_port *port);
+int tb_port_get_link_generation(struct tb_port *port);
 int tb_port_get_link_width(struct tb_port *port);
+bool tb_port_width_supported(struct tb_port *port, unsigned int width);
 int tb_port_set_link_width(struct tb_port *port, enum tb_link_width width);
 int tb_port_lane_bonding_enable(struct tb_port *port);
 void tb_port_lane_bonding_disable(struct tb_port *port);
-int tb_port_wait_for_link_width(struct tb_port *port, unsigned int width_mask,
+int tb_port_wait_for_link_width(struct tb_port *port, unsigned int width,
                                int timeout_msec);
 int tb_port_update_credits(struct tb_port *port);
 
@@ -1256,6 +1292,11 @@ int usb4_port_router_online(struct tb_port *port);
 int usb4_port_enumerate_retimers(struct tb_port *port);
 bool usb4_port_clx_supported(struct tb_port *port);
 int usb4_port_margining_caps(struct tb_port *port, u32 *caps);
+
+bool usb4_port_asym_supported(struct tb_port *port);
+int usb4_port_asym_set_link_width(struct tb_port *port, enum tb_link_width width);
+int usb4_port_asym_start(struct tb_port *port);
+
 int usb4_port_hw_margin(struct tb_port *port, unsigned int lanes,
                        unsigned int ber_level, bool timing, bool right_high,
                        u32 *results);
@@ -1283,7 +1324,6 @@ int usb4_port_retimer_nvm_read(struct tb_port *port, u8 index,
                               unsigned int address, void *buf, size_t size);
 
 int usb4_usb3_port_max_link_rate(struct tb_port *port);
-int usb4_usb3_port_actual_link_rate(struct tb_port *port);
 int usb4_usb3_port_allocated_bandwidth(struct tb_port *port, int *upstream_bw,
                                       int *downstream_bw);
 int usb4_usb3_port_allocate_bandwidth(struct tb_port *port, int *upstream_bw,
index cf9f2370878a8e027ef4c5fba659d2e7e82f411f..87e4795275fe6772e0497e8c50650d4a1835b1af 100644 (file)
@@ -346,10 +346,14 @@ struct tb_regs_port_header {
 #define LANE_ADP_CS_1                          0x01
 #define LANE_ADP_CS_1_TARGET_SPEED_MASK                GENMASK(3, 0)
 #define LANE_ADP_CS_1_TARGET_SPEED_GEN3                0xc
-#define LANE_ADP_CS_1_TARGET_WIDTH_MASK                GENMASK(9, 4)
+#define LANE_ADP_CS_1_TARGET_WIDTH_MASK                GENMASK(5, 4)
 #define LANE_ADP_CS_1_TARGET_WIDTH_SHIFT       4
 #define LANE_ADP_CS_1_TARGET_WIDTH_SINGLE      0x1
 #define LANE_ADP_CS_1_TARGET_WIDTH_DUAL                0x3
+#define LANE_ADP_CS_1_TARGET_WIDTH_ASYM_MASK   GENMASK(7, 6)
+#define LANE_ADP_CS_1_TARGET_WIDTH_ASYM_TX     0x1
+#define LANE_ADP_CS_1_TARGET_WIDTH_ASYM_RX     0x2
+#define LANE_ADP_CS_1_TARGET_WIDTH_ASYM_DUAL   0x0
 #define LANE_ADP_CS_1_CL0S_ENABLE              BIT(10)
 #define LANE_ADP_CS_1_CL1_ENABLE               BIT(11)
 #define LANE_ADP_CS_1_CL2_ENABLE               BIT(12)
@@ -382,12 +386,15 @@ struct tb_regs_port_header {
 #define PORT_CS_18_WOCS                                BIT(16)
 #define PORT_CS_18_WODS                                BIT(17)
 #define PORT_CS_18_WOU4S                       BIT(18)
+#define PORT_CS_18_CSA                         BIT(22)
+#define PORT_CS_18_TIP                         BIT(24)
 #define PORT_CS_19                             0x13
 #define PORT_CS_19_PC                          BIT(3)
 #define PORT_CS_19_PID                         BIT(4)
 #define PORT_CS_19_WOC                         BIT(16)
 #define PORT_CS_19_WOD                         BIT(17)
 #define PORT_CS_19_WOU4                                BIT(18)
+#define PORT_CS_19_START_ASYM                  BIT(24)
 
 /* Display Port adapter registers */
 #define ADP_DP_CS_0                            0x00
@@ -400,7 +407,7 @@ struct tb_regs_port_header {
 #define ADP_DP_CS_1_AUX_RX_HOPID_SHIFT         11
 #define ADP_DP_CS_2                            0x02
 #define ADP_DP_CS_2_NRD_MLC_MASK               GENMASK(2, 0)
-#define ADP_DP_CS_2_HDP                                BIT(6)
+#define ADP_DP_CS_2_HPD                                BIT(6)
 #define ADP_DP_CS_2_NRD_MLR_MASK               GENMASK(9, 7)
 #define ADP_DP_CS_2_NRD_MLR_SHIFT              7
 #define ADP_DP_CS_2_CA                         BIT(10)
@@ -417,7 +424,7 @@ struct tb_regs_port_header {
 #define ADP_DP_CS_2_ESTIMATED_BW_MASK          GENMASK(31, 24)
 #define ADP_DP_CS_2_ESTIMATED_BW_SHIFT         24
 #define ADP_DP_CS_3                            0x03
-#define ADP_DP_CS_3_HDPC                       BIT(9)
+#define ADP_DP_CS_3_HPDC                       BIT(9)
 #define DP_LOCAL_CAP                           0x04
 #define DP_REMOTE_CAP                          0x05
 /* For DP IN adapter */
@@ -484,9 +491,6 @@ struct tb_regs_port_header {
 #define ADP_USB3_CS_3                          0x03
 #define ADP_USB3_CS_3_SCALE_MASK               GENMASK(5, 0)
 #define ADP_USB3_CS_4                          0x04
-#define ADP_USB3_CS_4_ALR_MASK                 GENMASK(6, 0)
-#define ADP_USB3_CS_4_ALR_20G                  0x1
-#define ADP_USB3_CS_4_ULV                      BIT(7)
 #define ADP_USB3_CS_4_MSLR_MASK                        GENMASK(18, 12)
 #define ADP_USB3_CS_4_MSLR_SHIFT               12
 #define ADP_USB3_CS_4_MSLR_20G                 0x1
@@ -499,7 +503,8 @@ struct tb_regs_hop {
                          * out_port (on the incoming port of the next switch)
                          */
        u32 out_port:6; /* next port of the path (on the same switch) */
-       u32 initial_credits:8;
+       u32 initial_credits:7;
+       u32 pmps:1;
        u32 unknown1:6; /* set to zero */
        bool enable:1;
 
index a6810fb368600f15009aa5a8d4f0249d06160ef8..7534cd3a81f451aa090343be0c25f995aef9fe6d 100644 (file)
 #define TB_PCI_PATH_DOWN               0
 #define TB_PCI_PATH_UP                 1
 
+#define TB_PCI_PRIORITY                        3
+#define TB_PCI_WEIGHT                  1
+
 /* USB3 adapters use always HopID of 8 for both directions */
 #define TB_USB3_HOPID                  8
 
 #define TB_USB3_PATH_DOWN              0
 #define TB_USB3_PATH_UP                        1
 
+#define TB_USB3_PRIORITY               3
+#define TB_USB3_WEIGHT                 2
+
 /* DP adapters use HopID 8 for AUX and 9 for Video */
 #define TB_DP_AUX_TX_HOPID             8
 #define TB_DP_AUX_RX_HOPID             8
 #define TB_DP_AUX_PATH_OUT             1
 #define TB_DP_AUX_PATH_IN              2
 
+#define TB_DP_VIDEO_PRIORITY           1
+#define TB_DP_VIDEO_WEIGHT             1
+
+#define TB_DP_AUX_PRIORITY             2
+#define TB_DP_AUX_WEIGHT               1
+
 /* Minimum number of credits needed for PCIe path */
 #define TB_MIN_PCIE_CREDITS            6U
 /*
 /* Minimum number of credits for DMA path */
 #define TB_MIN_DMA_CREDITS             1
 
+#define TB_DMA_PRIORITY                        5
+#define TB_DMA_WEIGHT                  1
+
+/*
+ * Reserve additional bandwidth for USB 3.x and PCIe bulk traffic
+ * according to USB4 v2 Connection Manager guide. This ends up reserving
+ * 1500 Mb/s for PCIe and 3000 Mb/s for USB 3.x taking weights into
+ * account.
+ */
+#define USB4_V2_PCI_MIN_BANDWIDTH      (1500 * TB_PCI_WEIGHT)
+#define USB4_V2_USB3_MIN_BANDWIDTH     (1500 * TB_USB3_WEIGHT)
+
 static unsigned int dma_credits = TB_DMA_CREDITS;
 module_param(dma_credits, uint, 0444);
 MODULE_PARM_DESC(dma_credits, "specify custom credits for DMA tunnels (default: "
@@ -58,27 +82,6 @@ MODULE_PARM_DESC(bw_alloc_mode,
 
 static const char * const tb_tunnel_names[] = { "PCI", "DP", "DMA", "USB3" };
 
-#define __TB_TUNNEL_PRINT(level, tunnel, fmt, arg...)                   \
-       do {                                                            \
-               struct tb_tunnel *__tunnel = (tunnel);                  \
-               level(__tunnel->tb, "%llx:%u <-> %llx:%u (%s): " fmt,   \
-                     tb_route(__tunnel->src_port->sw),                 \
-                     __tunnel->src_port->port,                         \
-                     tb_route(__tunnel->dst_port->sw),                 \
-                     __tunnel->dst_port->port,                         \
-                     tb_tunnel_names[__tunnel->type],                  \
-                     ## arg);                                          \
-       } while (0)
-
-#define tb_tunnel_WARN(tunnel, fmt, arg...) \
-       __TB_TUNNEL_PRINT(tb_WARN, tunnel, fmt, ##arg)
-#define tb_tunnel_warn(tunnel, fmt, arg...) \
-       __TB_TUNNEL_PRINT(tb_warn, tunnel, fmt, ##arg)
-#define tb_tunnel_info(tunnel, fmt, arg...) \
-       __TB_TUNNEL_PRINT(tb_info, tunnel, fmt, ##arg)
-#define tb_tunnel_dbg(tunnel, fmt, arg...) \
-       __TB_TUNNEL_PRINT(tb_dbg, tunnel, fmt, ##arg)
-
 static inline unsigned int tb_usable_credits(const struct tb_port *port)
 {
        return port->total_credits - port->ctl_credits;
@@ -131,6 +134,16 @@ static unsigned int tb_available_credits(const struct tb_port *port,
        return credits > 0 ? credits : 0;
 }
 
+static void tb_init_pm_support(struct tb_path_hop *hop)
+{
+       struct tb_port *out_port = hop->out_port;
+       struct tb_port *in_port = hop->in_port;
+
+       if (tb_port_is_null(in_port) && tb_port_is_null(out_port) &&
+           usb4_switch_version(in_port->sw) >= 2)
+               hop->pm_support = true;
+}
+
 static struct tb_tunnel *tb_tunnel_alloc(struct tb *tb, size_t npaths,
                                         enum tb_tunnel_type type)
 {
@@ -156,11 +169,11 @@ static struct tb_tunnel *tb_tunnel_alloc(struct tb *tb, size_t npaths,
 
 static int tb_pci_set_ext_encapsulation(struct tb_tunnel *tunnel, bool enable)
 {
+       struct tb_port *port = tb_upstream_port(tunnel->dst_port->sw);
        int ret;
 
        /* Only supported of both routers are at least USB4 v2 */
-       if (usb4_switch_version(tunnel->src_port->sw) < 2 ||
-           usb4_switch_version(tunnel->dst_port->sw) < 2)
+       if (tb_port_get_link_generation(port) < 4)
                return 0;
 
        ret = usb4_pci_port_set_ext_encapsulation(tunnel->src_port, enable);
@@ -234,8 +247,8 @@ static int tb_pci_init_path(struct tb_path *path)
        path->egress_shared_buffer = TB_PATH_NONE;
        path->ingress_fc_enable = TB_PATH_ALL;
        path->ingress_shared_buffer = TB_PATH_NONE;
-       path->priority = 3;
-       path->weight = 1;
+       path->priority = TB_PCI_PRIORITY;
+       path->weight = TB_PCI_WEIGHT;
        path->drop_packages = 0;
 
        tb_path_for_each_hop(path, hop) {
@@ -376,6 +389,51 @@ err_free:
        return NULL;
 }
 
+/**
+ * tb_tunnel_reserved_pci() - Amount of bandwidth to reserve for PCIe
+ * @port: Lane 0 adapter
+ * @reserved_up: Upstream bandwidth in Mb/s to reserve
+ * @reserved_down: Downstream bandwidth in Mb/s to reserve
+ *
+ * Can be called to any connected lane 0 adapter to find out how much
+ * bandwidth needs to be left in reserve for possible PCIe bulk traffic.
+ * Returns true if there is something to be reserved and writes the
+ * amount to @reserved_down/@reserved_up. Otherwise returns false and
+ * does not touch the parameters.
+ */
+bool tb_tunnel_reserved_pci(struct tb_port *port, int *reserved_up,
+                           int *reserved_down)
+{
+       if (WARN_ON_ONCE(!port->remote))
+               return false;
+
+       if (!tb_acpi_may_tunnel_pcie())
+               return false;
+
+       if (tb_port_get_link_generation(port) < 4)
+               return false;
+
+       /* Must have PCIe adapters */
+       if (tb_is_upstream_port(port)) {
+               if (!tb_switch_find_port(port->sw, TB_TYPE_PCIE_UP))
+                       return false;
+               if (!tb_switch_find_port(port->remote->sw, TB_TYPE_PCIE_DOWN))
+                       return false;
+       } else {
+               if (!tb_switch_find_port(port->sw, TB_TYPE_PCIE_DOWN))
+                       return false;
+               if (!tb_switch_find_port(port->remote->sw, TB_TYPE_PCIE_UP))
+                       return false;
+       }
+
+       *reserved_up = USB4_V2_PCI_MIN_BANDWIDTH;
+       *reserved_down = USB4_V2_PCI_MIN_BANDWIDTH;
+
+       tb_port_dbg(port, "reserving %u/%u Mb/s for PCIe\n", *reserved_up,
+                   *reserved_down);
+       return true;
+}
+
 static bool tb_dp_is_usb4(const struct tb_switch *sw)
 {
        /* Titan Ridge DP adapters need the same treatment as USB4 */
@@ -614,8 +672,9 @@ static int tb_dp_xchg_caps(struct tb_tunnel *tunnel)
 
        in_rate = tb_dp_cap_get_rate(in_dp_cap);
        in_lanes = tb_dp_cap_get_lanes(in_dp_cap);
-       tb_port_dbg(in, "maximum supported bandwidth %u Mb/s x%u = %u Mb/s\n",
-                   in_rate, in_lanes, tb_dp_bandwidth(in_rate, in_lanes));
+       tb_tunnel_dbg(tunnel,
+                     "DP IN maximum supported bandwidth %u Mb/s x%u = %u Mb/s\n",
+                     in_rate, in_lanes, tb_dp_bandwidth(in_rate, in_lanes));
 
        /*
         * If the tunnel bandwidth is limited (max_bw is set) then see
@@ -624,10 +683,11 @@ static int tb_dp_xchg_caps(struct tb_tunnel *tunnel)
        out_rate = tb_dp_cap_get_rate(out_dp_cap);
        out_lanes = tb_dp_cap_get_lanes(out_dp_cap);
        bw = tb_dp_bandwidth(out_rate, out_lanes);
-       tb_port_dbg(out, "maximum supported bandwidth %u Mb/s x%u = %u Mb/s\n",
-                   out_rate, out_lanes, bw);
+       tb_tunnel_dbg(tunnel,
+                     "DP OUT maximum supported bandwidth %u Mb/s x%u = %u Mb/s\n",
+                     out_rate, out_lanes, bw);
 
-       if (in->sw->config.depth < out->sw->config.depth)
+       if (tb_port_path_direction_downstream(in, out))
                max_bw = tunnel->max_down;
        else
                max_bw = tunnel->max_up;
@@ -639,13 +699,14 @@ static int tb_dp_xchg_caps(struct tb_tunnel *tunnel)
                                             out_rate, out_lanes, &new_rate,
                                             &new_lanes);
                if (ret) {
-                       tb_port_info(out, "not enough bandwidth for DP tunnel\n");
+                       tb_tunnel_info(tunnel, "not enough bandwidth\n");
                        return ret;
                }
 
                new_bw = tb_dp_bandwidth(new_rate, new_lanes);
-               tb_port_dbg(out, "bandwidth reduced to %u Mb/s x%u = %u Mb/s\n",
-                           new_rate, new_lanes, new_bw);
+               tb_tunnel_dbg(tunnel,
+                             "bandwidth reduced to %u Mb/s x%u = %u Mb/s\n",
+                             new_rate, new_lanes, new_bw);
 
                /*
                 * Set new rate and number of lanes before writing it to
@@ -662,7 +723,7 @@ static int tb_dp_xchg_caps(struct tb_tunnel *tunnel)
         */
        if (tb_route(out->sw) && tb_switch_is_titan_ridge(out->sw)) {
                out_dp_cap |= DP_COMMON_CAP_LTTPR_NS;
-               tb_port_dbg(out, "disabling LTTPR\n");
+               tb_tunnel_dbg(tunnel, "disabling LTTPR\n");
        }
 
        return tb_port_write(in, &out_dp_cap, TB_CFG_PORT,
@@ -712,8 +773,8 @@ static int tb_dp_bandwidth_alloc_mode_enable(struct tb_tunnel *tunnel)
        lanes = min(in_lanes, out_lanes);
        tmp = tb_dp_bandwidth(rate, lanes);
 
-       tb_port_dbg(in, "non-reduced bandwidth %u Mb/s x%u = %u Mb/s\n", rate,
-                   lanes, tmp);
+       tb_tunnel_dbg(tunnel, "non-reduced bandwidth %u Mb/s x%u = %u Mb/s\n",
+                     rate, lanes, tmp);
 
        ret = usb4_dp_port_set_nrd(in, rate, lanes);
        if (ret)
@@ -728,15 +789,15 @@ static int tb_dp_bandwidth_alloc_mode_enable(struct tb_tunnel *tunnel)
        rate = min(in_rate, out_rate);
        tmp = tb_dp_bandwidth(rate, lanes);
 
-       tb_port_dbg(in,
-                   "maximum bandwidth through allocation mode %u Mb/s x%u = %u Mb/s\n",
-                   rate, lanes, tmp);
+       tb_tunnel_dbg(tunnel,
+                     "maximum bandwidth through allocation mode %u Mb/s x%u = %u Mb/s\n",
+                     rate, lanes, tmp);
 
        for (granularity = 250; tmp / granularity > 255 && granularity <= 1000;
             granularity *= 2)
                ;
 
-       tb_port_dbg(in, "granularity %d Mb/s\n", granularity);
+       tb_tunnel_dbg(tunnel, "granularity %d Mb/s\n", granularity);
 
        /*
         * Returns -EINVAL if granularity above is outside of the
@@ -751,12 +812,12 @@ static int tb_dp_bandwidth_alloc_mode_enable(struct tb_tunnel *tunnel)
         * max_up/down fields. For discovery we just read what the
         * estimation was set to.
         */
-       if (in->sw->config.depth < out->sw->config.depth)
+       if (tb_port_path_direction_downstream(in, out))
                estimated_bw = tunnel->max_down;
        else
                estimated_bw = tunnel->max_up;
 
-       tb_port_dbg(in, "estimated bandwidth %d Mb/s\n", estimated_bw);
+       tb_tunnel_dbg(tunnel, "estimated bandwidth %d Mb/s\n", estimated_bw);
 
        ret = usb4_dp_port_set_estimated_bandwidth(in, estimated_bw);
        if (ret)
@@ -767,7 +828,7 @@ static int tb_dp_bandwidth_alloc_mode_enable(struct tb_tunnel *tunnel)
        if (ret)
                return ret;
 
-       tb_port_dbg(in, "bandwidth allocation mode enabled\n");
+       tb_tunnel_dbg(tunnel, "bandwidth allocation mode enabled\n");
        return 0;
 }
 
@@ -788,7 +849,7 @@ static int tb_dp_init(struct tb_tunnel *tunnel)
        if (!usb4_dp_port_bandwidth_mode_supported(in))
                return 0;
 
-       tb_port_dbg(in, "bandwidth allocation mode supported\n");
+       tb_tunnel_dbg(tunnel, "bandwidth allocation mode supported\n");
 
        ret = usb4_dp_port_set_cm_id(in, tb->index);
        if (ret)
@@ -805,7 +866,7 @@ static void tb_dp_deinit(struct tb_tunnel *tunnel)
                return;
        if (usb4_dp_port_bandwidth_mode_enabled(in)) {
                usb4_dp_port_set_cm_bandwidth_mode_supported(in, false);
-               tb_port_dbg(in, "bandwidth allocation mode disabled\n");
+               tb_tunnel_dbg(tunnel, "bandwidth allocation mode disabled\n");
        }
 }
 
@@ -921,10 +982,7 @@ static int tb_dp_bandwidth_mode_consumed_bandwidth(struct tb_tunnel *tunnel,
        if (allocated_bw == max_bw)
                allocated_bw = ret;
 
-       tb_port_dbg(in, "consumed bandwidth through allocation mode %d Mb/s\n",
-                   allocated_bw);
-
-       if (in->sw->config.depth < out->sw->config.depth) {
+       if (tb_port_path_direction_downstream(in, out)) {
                *consumed_up = 0;
                *consumed_down = allocated_bw;
        } else {
@@ -959,7 +1017,7 @@ static int tb_dp_allocated_bandwidth(struct tb_tunnel *tunnel, int *allocated_up
                if (allocated_bw == max_bw)
                        allocated_bw = ret;
 
-               if (in->sw->config.depth < out->sw->config.depth) {
+               if (tb_port_path_direction_downstream(in, out)) {
                        *allocated_up = 0;
                        *allocated_down = allocated_bw;
                } else {
@@ -987,7 +1045,7 @@ static int tb_dp_alloc_bandwidth(struct tb_tunnel *tunnel, int *alloc_up,
        if (ret < 0)
                return ret;
 
-       if (in->sw->config.depth < out->sw->config.depth) {
+       if (tb_port_path_direction_downstream(in, out)) {
                tmp = min(*alloc_down, max_bw);
                ret = usb4_dp_port_allocate_bandwidth(in, tmp);
                if (ret)
@@ -1006,9 +1064,6 @@ static int tb_dp_alloc_bandwidth(struct tb_tunnel *tunnel, int *alloc_up,
        /* Now we can use BW mode registers to figure out the bandwidth */
        /* TODO: need to handle discovery too */
        tunnel->bw_mode = true;
-
-       tb_port_dbg(in, "allocated bandwidth through allocation mode %d Mb/s\n",
-                   tmp);
        return 0;
 }
 
@@ -1035,8 +1090,7 @@ static int tb_dp_read_dprx(struct tb_tunnel *tunnel, u32 *rate, u32 *lanes,
                        *rate = tb_dp_cap_get_rate(val);
                        *lanes = tb_dp_cap_get_lanes(val);
 
-                       tb_port_dbg(in, "consumed bandwidth through DPRX %d Mb/s\n",
-                                   tb_dp_bandwidth(*rate, *lanes));
+                       tb_tunnel_dbg(tunnel, "DPRX read done\n");
                        return 0;
                }
                usleep_range(100, 150);
@@ -1073,9 +1127,6 @@ static int tb_dp_read_cap(struct tb_tunnel *tunnel, unsigned int cap, u32 *rate,
 
        *rate = tb_dp_cap_get_rate(val);
        *lanes = tb_dp_cap_get_lanes(val);
-
-       tb_port_dbg(in, "bandwidth from %#x capability %d Mb/s\n", cap,
-                   tb_dp_bandwidth(*rate, *lanes));
        return 0;
 }
 
@@ -1092,7 +1143,7 @@ static int tb_dp_maximum_bandwidth(struct tb_tunnel *tunnel, int *max_up,
        if (ret < 0)
                return ret;
 
-       if (in->sw->config.depth < tunnel->dst_port->sw->config.depth) {
+       if (tb_port_path_direction_downstream(in, tunnel->dst_port)) {
                *max_up = 0;
                *max_down = ret;
        } else {
@@ -1150,7 +1201,7 @@ static int tb_dp_consumed_bandwidth(struct tb_tunnel *tunnel, int *consumed_up,
                return 0;
        }
 
-       if (in->sw->config.depth < tunnel->dst_port->sw->config.depth) {
+       if (tb_port_path_direction_downstream(in, tunnel->dst_port)) {
                *consumed_up = 0;
                *consumed_down = tb_dp_bandwidth(rate, lanes);
        } else {
@@ -1172,7 +1223,7 @@ static void tb_dp_init_aux_credits(struct tb_path_hop *hop)
                hop->initial_credits = 1;
 }
 
-static void tb_dp_init_aux_path(struct tb_path *path)
+static void tb_dp_init_aux_path(struct tb_path *path, bool pm_support)
 {
        struct tb_path_hop *hop;
 
@@ -1180,11 +1231,14 @@ static void tb_dp_init_aux_path(struct tb_path *path)
        path->egress_shared_buffer = TB_PATH_NONE;
        path->ingress_fc_enable = TB_PATH_ALL;
        path->ingress_shared_buffer = TB_PATH_NONE;
-       path->priority = 2;
-       path->weight = 1;
+       path->priority = TB_DP_AUX_PRIORITY;
+       path->weight = TB_DP_AUX_WEIGHT;
 
-       tb_path_for_each_hop(path, hop)
+       tb_path_for_each_hop(path, hop) {
                tb_dp_init_aux_credits(hop);
+               if (pm_support)
+                       tb_init_pm_support(hop);
+       }
 }
 
 static int tb_dp_init_video_credits(struct tb_path_hop *hop)
@@ -1216,7 +1270,7 @@ static int tb_dp_init_video_credits(struct tb_path_hop *hop)
        return 0;
 }
 
-static int tb_dp_init_video_path(struct tb_path *path)
+static int tb_dp_init_video_path(struct tb_path *path, bool pm_support)
 {
        struct tb_path_hop *hop;
 
@@ -1224,8 +1278,8 @@ static int tb_dp_init_video_path(struct tb_path *path)
        path->egress_shared_buffer = TB_PATH_NONE;
        path->ingress_fc_enable = TB_PATH_NONE;
        path->ingress_shared_buffer = TB_PATH_NONE;
-       path->priority = 1;
-       path->weight = 1;
+       path->priority = TB_DP_VIDEO_PRIORITY;
+       path->weight = TB_DP_VIDEO_WEIGHT;
 
        tb_path_for_each_hop(path, hop) {
                int ret;
@@ -1233,6 +1287,8 @@ static int tb_dp_init_video_path(struct tb_path *path)
                ret = tb_dp_init_video_credits(hop);
                if (ret)
                        return ret;
+               if (pm_support)
+                       tb_init_pm_support(hop);
        }
 
        return 0;
@@ -1253,8 +1309,9 @@ static void tb_dp_dump(struct tb_tunnel *tunnel)
        rate = tb_dp_cap_get_rate(dp_cap);
        lanes = tb_dp_cap_get_lanes(dp_cap);
 
-       tb_port_dbg(in, "maximum supported bandwidth %u Mb/s x%u = %u Mb/s\n",
-                   rate, lanes, tb_dp_bandwidth(rate, lanes));
+       tb_tunnel_dbg(tunnel,
+                     "DP IN maximum supported bandwidth %u Mb/s x%u = %u Mb/s\n",
+                     rate, lanes, tb_dp_bandwidth(rate, lanes));
 
        out = tunnel->dst_port;
 
@@ -1265,8 +1322,9 @@ static void tb_dp_dump(struct tb_tunnel *tunnel)
        rate = tb_dp_cap_get_rate(dp_cap);
        lanes = tb_dp_cap_get_lanes(dp_cap);
 
-       tb_port_dbg(out, "maximum supported bandwidth %u Mb/s x%u = %u Mb/s\n",
-                   rate, lanes, tb_dp_bandwidth(rate, lanes));
+       tb_tunnel_dbg(tunnel,
+                     "DP OUT maximum supported bandwidth %u Mb/s x%u = %u Mb/s\n",
+                     rate, lanes, tb_dp_bandwidth(rate, lanes));
 
        if (tb_port_read(in, &dp_cap, TB_CFG_PORT,
                         in->cap_adap + DP_REMOTE_CAP, 1))
@@ -1275,8 +1333,8 @@ static void tb_dp_dump(struct tb_tunnel *tunnel)
        rate = tb_dp_cap_get_rate(dp_cap);
        lanes = tb_dp_cap_get_lanes(dp_cap);
 
-       tb_port_dbg(in, "reduced bandwidth %u Mb/s x%u = %u Mb/s\n",
-                   rate, lanes, tb_dp_bandwidth(rate, lanes));
+       tb_tunnel_dbg(tunnel, "reduced bandwidth %u Mb/s x%u = %u Mb/s\n",
+                     rate, lanes, tb_dp_bandwidth(rate, lanes));
 }
 
 /**
@@ -1322,7 +1380,7 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in,
                goto err_free;
        }
        tunnel->paths[TB_DP_VIDEO_PATH_OUT] = path;
-       if (tb_dp_init_video_path(tunnel->paths[TB_DP_VIDEO_PATH_OUT]))
+       if (tb_dp_init_video_path(tunnel->paths[TB_DP_VIDEO_PATH_OUT], false))
                goto err_free;
 
        path = tb_path_discover(in, TB_DP_AUX_TX_HOPID, NULL, -1, NULL, "AUX TX",
@@ -1330,14 +1388,14 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in,
        if (!path)
                goto err_deactivate;
        tunnel->paths[TB_DP_AUX_PATH_OUT] = path;
-       tb_dp_init_aux_path(tunnel->paths[TB_DP_AUX_PATH_OUT]);
+       tb_dp_init_aux_path(tunnel->paths[TB_DP_AUX_PATH_OUT], false);
 
        path = tb_path_discover(tunnel->dst_port, -1, in, TB_DP_AUX_RX_HOPID,
                                &port, "AUX RX", alloc_hopid);
        if (!path)
                goto err_deactivate;
        tunnel->paths[TB_DP_AUX_PATH_IN] = path;
-       tb_dp_init_aux_path(tunnel->paths[TB_DP_AUX_PATH_IN]);
+       tb_dp_init_aux_path(tunnel->paths[TB_DP_AUX_PATH_IN], false);
 
        /* Validate that the tunnel is complete */
        if (!tb_port_is_dpout(tunnel->dst_port)) {
@@ -1392,6 +1450,7 @@ struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in,
        struct tb_tunnel *tunnel;
        struct tb_path **paths;
        struct tb_path *path;
+       bool pm_support;
 
        if (WARN_ON(!in->cap_adap || !out->cap_adap))
                return NULL;
@@ -1413,26 +1472,27 @@ struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in,
        tunnel->max_down = max_down;
 
        paths = tunnel->paths;
+       pm_support = usb4_switch_version(in->sw) >= 2;
 
        path = tb_path_alloc(tb, in, TB_DP_VIDEO_HOPID, out, TB_DP_VIDEO_HOPID,
                             link_nr, "Video");
        if (!path)
                goto err_free;
-       tb_dp_init_video_path(path);
+       tb_dp_init_video_path(path, pm_support);
        paths[TB_DP_VIDEO_PATH_OUT] = path;
 
        path = tb_path_alloc(tb, in, TB_DP_AUX_TX_HOPID, out,
                             TB_DP_AUX_TX_HOPID, link_nr, "AUX TX");
        if (!path)
                goto err_free;
-       tb_dp_init_aux_path(path);
+       tb_dp_init_aux_path(path, pm_support);
        paths[TB_DP_AUX_PATH_OUT] = path;
 
        path = tb_path_alloc(tb, out, TB_DP_AUX_RX_HOPID, in,
                             TB_DP_AUX_RX_HOPID, link_nr, "AUX RX");
        if (!path)
                goto err_free;
-       tb_dp_init_aux_path(path);
+       tb_dp_init_aux_path(path, pm_support);
        paths[TB_DP_AUX_PATH_IN] = path;
 
        return tunnel;
@@ -1497,8 +1557,8 @@ static int tb_dma_init_rx_path(struct tb_path *path, unsigned int credits)
        path->ingress_fc_enable = TB_PATH_ALL;
        path->egress_shared_buffer = TB_PATH_NONE;
        path->ingress_shared_buffer = TB_PATH_NONE;
-       path->priority = 5;
-       path->weight = 1;
+       path->priority = TB_DMA_PRIORITY;
+       path->weight = TB_DMA_WEIGHT;
        path->clear_fc = true;
 
        /*
@@ -1531,8 +1591,8 @@ static int tb_dma_init_tx_path(struct tb_path *path, unsigned int credits)
        path->ingress_fc_enable = TB_PATH_ALL;
        path->egress_shared_buffer = TB_PATH_NONE;
        path->ingress_shared_buffer = TB_PATH_NONE;
-       path->priority = 5;
-       path->weight = 1;
+       path->priority = TB_DMA_PRIORITY;
+       path->weight = TB_DMA_WEIGHT;
        path->clear_fc = true;
 
        tb_path_for_each_hop(path, hop) {
@@ -1758,14 +1818,23 @@ static int tb_usb3_activate(struct tb_tunnel *tunnel, bool activate)
 static int tb_usb3_consumed_bandwidth(struct tb_tunnel *tunnel,
                int *consumed_up, int *consumed_down)
 {
-       int pcie_enabled = tb_acpi_may_tunnel_pcie();
+       struct tb_port *port = tb_upstream_port(tunnel->dst_port->sw);
+       int pcie_weight = tb_acpi_may_tunnel_pcie() ? TB_PCI_WEIGHT : 0;
 
        /*
         * PCIe tunneling, if enabled, affects the USB3 bandwidth so
         * take that it into account here.
         */
-       *consumed_up = tunnel->allocated_up * (3 + pcie_enabled) / 3;
-       *consumed_down = tunnel->allocated_down * (3 + pcie_enabled) / 3;
+       *consumed_up = tunnel->allocated_up *
+               (TB_USB3_WEIGHT + pcie_weight) / TB_USB3_WEIGHT;
+       *consumed_down = tunnel->allocated_down *
+               (TB_USB3_WEIGHT + pcie_weight) / TB_USB3_WEIGHT;
+
+       if (tb_port_get_link_generation(port) >= 4) {
+               *consumed_up = max(*consumed_up, USB4_V2_USB3_MIN_BANDWIDTH);
+               *consumed_down = max(*consumed_down, USB4_V2_USB3_MIN_BANDWIDTH);
+       }
+
        return 0;
 }
 
@@ -1790,17 +1859,10 @@ static void tb_usb3_reclaim_available_bandwidth(struct tb_tunnel *tunnel,
 {
        int ret, max_rate, allocate_up, allocate_down;
 
-       ret = usb4_usb3_port_actual_link_rate(tunnel->src_port);
+       ret = tb_usb3_max_link_rate(tunnel->dst_port, tunnel->src_port);
        if (ret < 0) {
-               tb_tunnel_warn(tunnel, "failed to read actual link rate\n");
+               tb_tunnel_warn(tunnel, "failed to read maximum link rate\n");
                return;
-       } else if (!ret) {
-               /* Use maximum link rate if the link valid is not set */
-               ret = tb_usb3_max_link_rate(tunnel->dst_port, tunnel->src_port);
-               if (ret < 0) {
-                       tb_tunnel_warn(tunnel, "failed to read maximum link rate\n");
-                       return;
-               }
        }
 
        /*
@@ -1871,8 +1933,8 @@ static void tb_usb3_init_path(struct tb_path *path)
        path->egress_shared_buffer = TB_PATH_NONE;
        path->ingress_fc_enable = TB_PATH_ALL;
        path->ingress_shared_buffer = TB_PATH_NONE;
-       path->priority = 3;
-       path->weight = 3;
+       path->priority = TB_USB3_PRIORITY;
+       path->weight = TB_USB3_WEIGHT;
        path->drop_packages = 0;
 
        tb_path_for_each_hop(path, hop)
@@ -2387,3 +2449,8 @@ void tb_tunnel_reclaim_available_bandwidth(struct tb_tunnel *tunnel,
                tunnel->reclaim_available_bandwidth(tunnel, available_up,
                                                    available_down);
 }
+
+const char *tb_tunnel_type_name(const struct tb_tunnel *tunnel)
+{
+       return tb_tunnel_names[tunnel->type];
+}
index bf690f7beeeeba554afabbe7674596c3eb315ebd..b4cff5482112d3a3e97758565765607d226327a0 100644 (file)
@@ -80,6 +80,8 @@ struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down,
                                         bool alloc_hopid);
 struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up,
                                      struct tb_port *down);
+bool tb_tunnel_reserved_pci(struct tb_port *port, int *reserved_up,
+                           int *reserved_down);
 struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in,
                                        bool alloc_hopid);
 struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in,
@@ -137,5 +139,27 @@ static inline bool tb_tunnel_is_usb3(const struct tb_tunnel *tunnel)
        return tunnel->type == TB_TUNNEL_USB3;
 }
 
-#endif
+const char *tb_tunnel_type_name(const struct tb_tunnel *tunnel);
+
+#define __TB_TUNNEL_PRINT(level, tunnel, fmt, arg...)                   \
+       do {                                                            \
+               struct tb_tunnel *__tunnel = (tunnel);                  \
+               level(__tunnel->tb, "%llx:%u <-> %llx:%u (%s): " fmt,   \
+                     tb_route(__tunnel->src_port->sw),                 \
+                     __tunnel->src_port->port,                         \
+                     tb_route(__tunnel->dst_port->sw),                 \
+                     __tunnel->dst_port->port,                         \
+                     tb_tunnel_type_name(__tunnel),                    \
+                     ## arg);                                          \
+       } while (0)
 
+#define tb_tunnel_WARN(tunnel, fmt, arg...) \
+       __TB_TUNNEL_PRINT(tb_WARN, tunnel, fmt, ##arg)
+#define tb_tunnel_warn(tunnel, fmt, arg...) \
+       __TB_TUNNEL_PRINT(tb_warn, tunnel, fmt, ##arg)
+#define tb_tunnel_info(tunnel, fmt, arg...) \
+       __TB_TUNNEL_PRINT(tb_info, tunnel, fmt, ##arg)
+#define tb_tunnel_dbg(tunnel, fmt, arg...) \
+       __TB_TUNNEL_PRINT(tb_dbg, tunnel, fmt, ##arg)
+
+#endif
index 05ddb224c4649171a20319fb2de1b6bad822ed17..4277733d0021bfb924090952f941568208a67f01 100644 (file)
@@ -1454,6 +1454,112 @@ bool usb4_port_clx_supported(struct tb_port *port)
        return !!(val & PORT_CS_18_CPS);
 }
 
+/**
+ * usb4_port_asym_supported() - If the port supports asymmetric link
+ * @port: USB4 port
+ *
+ * Checks if the port and the cable supports asymmetric link and returns
+ * %true in that case.
+ */
+bool usb4_port_asym_supported(struct tb_port *port)
+{
+       u32 val;
+
+       if (!port->cap_usb4)
+               return false;
+
+       if (tb_port_read(port, &val, TB_CFG_PORT, port->cap_usb4 + PORT_CS_18, 1))
+               return false;
+
+       return !!(val & PORT_CS_18_CSA);
+}
+
+/**
+ * usb4_port_asym_set_link_width() - Set link width to asymmetric or symmetric
+ * @port: USB4 port
+ * @width: Asymmetric width to configure
+ *
+ * Sets USB4 port link width to @width. Can be called for widths where
+ * usb4_port_asym_width_supported() returned @true.
+ */
+int usb4_port_asym_set_link_width(struct tb_port *port, enum tb_link_width width)
+{
+       u32 val;
+       int ret;
+
+       if (!port->cap_phy)
+               return -EINVAL;
+
+       ret = tb_port_read(port, &val, TB_CFG_PORT,
+                          port->cap_phy + LANE_ADP_CS_1, 1);
+       if (ret)
+               return ret;
+
+       val &= ~LANE_ADP_CS_1_TARGET_WIDTH_ASYM_MASK;
+       switch (width) {
+       case TB_LINK_WIDTH_DUAL:
+               val |= FIELD_PREP(LANE_ADP_CS_1_TARGET_WIDTH_ASYM_MASK,
+                                 LANE_ADP_CS_1_TARGET_WIDTH_ASYM_DUAL);
+               break;
+       case TB_LINK_WIDTH_ASYM_TX:
+               val |= FIELD_PREP(LANE_ADP_CS_1_TARGET_WIDTH_ASYM_MASK,
+                                 LANE_ADP_CS_1_TARGET_WIDTH_ASYM_TX);
+               break;
+       case TB_LINK_WIDTH_ASYM_RX:
+               val |= FIELD_PREP(LANE_ADP_CS_1_TARGET_WIDTH_ASYM_MASK,
+                                 LANE_ADP_CS_1_TARGET_WIDTH_ASYM_RX);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return tb_port_write(port, &val, TB_CFG_PORT,
+                            port->cap_phy + LANE_ADP_CS_1, 1);
+}
+
+/**
+ * usb4_port_asym_start() - Start symmetry change and wait for completion
+ * @port: USB4 port
+ *
+ * Start symmetry change of the link to asymmetric or symmetric
+ * (according to what was previously set in tb_port_set_link_width().
+ * Wait for completion of the change.
+ *
+ * Returns %0 in case of success, %-ETIMEDOUT if case of timeout or
+ * a negative errno in case of a failure.
+ */
+int usb4_port_asym_start(struct tb_port *port)
+{
+       int ret;
+       u32 val;
+
+       ret = tb_port_read(port, &val, TB_CFG_PORT,
+                          port->cap_usb4 + PORT_CS_19, 1);
+       if (ret)
+               return ret;
+
+       val &= ~PORT_CS_19_START_ASYM;
+       val |= FIELD_PREP(PORT_CS_19_START_ASYM, 1);
+
+       ret = tb_port_write(port, &val, TB_CFG_PORT,
+                           port->cap_usb4 + PORT_CS_19, 1);
+       if (ret)
+               return ret;
+
+       /*
+        * Wait for PORT_CS_19_START_ASYM to be 0. This means the USB4
+        * port started the symmetry transition.
+        */
+       ret = usb4_port_wait_for_bit(port, port->cap_usb4 + PORT_CS_19,
+                                    PORT_CS_19_START_ASYM, 0, 1000);
+       if (ret)
+               return ret;
+
+       /* Then wait for the transtion to be completed */
+       return usb4_port_wait_for_bit(port, port->cap_usb4 + PORT_CS_18,
+                                     PORT_CS_18_TIP, 0, 5000);
+}
+
 /**
  * usb4_port_margining_caps() - Read USB4 port marginig capabilities
  * @port: USB4 port
@@ -1946,35 +2052,6 @@ int usb4_usb3_port_max_link_rate(struct tb_port *port)
        return usb4_usb3_port_max_bandwidth(port, ret);
 }
 
-/**
- * usb4_usb3_port_actual_link_rate() - Established USB3 link rate
- * @port: USB3 adapter port
- *
- * Return actual established link rate of a USB3 adapter in Mb/s. If the
- * link is not up returns %0 and negative errno in case of failure.
- */
-int usb4_usb3_port_actual_link_rate(struct tb_port *port)
-{
-       int ret, lr;
-       u32 val;
-
-       if (!tb_port_is_usb3_down(port) && !tb_port_is_usb3_up(port))
-               return -EINVAL;
-
-       ret = tb_port_read(port, &val, TB_CFG_PORT,
-                          port->cap_adap + ADP_USB3_CS_4, 1);
-       if (ret)
-               return ret;
-
-       if (!(val & ADP_USB3_CS_4_ULV))
-               return 0;
-
-       lr = val & ADP_USB3_CS_4_ALR_MASK;
-       ret = lr == ADP_USB3_CS_4_ALR_20G ? 20000 : 10000;
-
-       return usb4_usb3_port_max_bandwidth(port, ret);
-}
-
 static int usb4_usb3_port_cm_request(struct tb_port *port, bool request)
 {
        int ret;
index 7f33bcc315f27bf0cde170cb8f53fbc500e86754..abf8c6cdea9ea53a40f2115525913ed5ba440f59 100644 (file)
@@ -91,6 +91,16 @@ config USB_PCI
          If you have such a device you may say N here and PCI related code
          will not be built in the USB driver.
 
+config USB_PCI_AMD
+       bool "AMD PCI USB host support"
+       depends on USB_PCI && HAS_IOPORT
+       default X86 || MACH_LOONGSON64 || PPC_PASEMI
+       help
+         Enable workarounds for USB implementation quirks in SB600/SB700/SB800
+         and later south bridge implementations. These are common on x86 PCs
+         with AMD CPUs but rarely used elsewhere, with the exception of a few
+         powerpc and mips desktop machines.
+
 if USB
 
 source "drivers/usb/core/Kconfig"
index 6332a6b5dce605f2f05dd70afda9a5b259e9f770..6ba5adced61cde25e16ac4c285b15759ccaabb5f 100644 (file)
@@ -109,7 +109,6 @@ int c67x00_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status);
 void c67x00_endpoint_disable(struct usb_hcd *hcd,
                             struct usb_host_endpoint *ep);
 
-void c67x00_hcd_msg_received(struct c67x00_sie *sie, u16 msg);
 void c67x00_sched_kick(struct c67x00_hcd *c67x00);
 int c67x00_sched_start_scheduler(struct c67x00_hcd *c67x00);
 void c67x00_sched_stop_scheduler(struct c67x00_hcd *c67x00);
index f0ca865cce2a095660b817f632deb57c89646784..ad617b7455b9c3e26bb1dee98fccbb6ad0c11e2e 100644 (file)
@@ -131,8 +131,6 @@ static inline const char *cdnsp_trb_type_string(u8 type)
                return "Endpoint Not ready";
        case TRB_HALT_ENDPOINT:
                return "Halt Endpoint";
-       case TRB_FLUSH_ENDPOINT:
-               return "FLush Endpoint";
        default:
                return "UNKNOWN";
        }
@@ -328,7 +326,6 @@ static inline const char *cdnsp_decode_trb(char *str, size_t size, u32 field0,
                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),
index 4b67749edb997490b3d6a37991fc76f807c36145..4a3f0f95825698f0524cace5c06bfcf27f763149 100644 (file)
@@ -1024,10 +1024,8 @@ static int cdnsp_gadget_ep_disable(struct usb_ep *ep)
        pep->ep_state |= EP_DIS_IN_RROGRESS;
 
        /* Endpoint was unconfigured by Reset Device command. */
-       if (!(pep->ep_state & EP_UNCONFIGURED)) {
+       if (!(pep->ep_state & EP_UNCONFIGURED))
                cdnsp_cmd_stop_ep(pdev, pep);
-               cdnsp_cmd_flush_ep(pdev, pep);
-       }
 
        /* Remove all queued USB requests. */
        while (!list_empty(&pep->pending_list)) {
@@ -1424,8 +1422,6 @@ static void cdnsp_stop(struct cdnsp_device *pdev)
 {
        u32 temp;
 
-       cdnsp_cmd_flush_ep(pdev, &pdev->eps[0]);
-
        /* Remove internally queued request for ep0. */
        if (!list_empty(&pdev->eps[0].pending_list)) {
                struct cdnsp_request *req;
index e1b5801fdddf8c5cb76d081f3fb1cfb505ffa36e..dbee6f08527773d6cdebb9f26748e2cea871197d 100644 (file)
@@ -1128,8 +1128,6 @@ union cdnsp_trb {
 #define TRB_HALT_ENDPOINT      54
 /* Doorbell Overflow Event. */
 #define TRB_DRB_OVERFLOW       57
-/* Flush Endpoint Command. */
-#define TRB_FLUSH_ENDPOINT     58
 
 #define TRB_TYPE_LINK(x)       (((x) & TRB_TYPE_BITMASK) == TRB_TYPE(TRB_LINK))
 #define TRB_TYPE_LINK_LE32(x)  (((x) & cpu_to_le32(TRB_TYPE_BITMASK)) == \
@@ -1539,8 +1537,6 @@ void cdnsp_queue_configure_endpoint(struct cdnsp_device *pdev,
 void cdnsp_queue_reset_ep(struct cdnsp_device *pdev, unsigned int ep_index);
 void cdnsp_queue_halt_endpoint(struct cdnsp_device *pdev,
                               unsigned int ep_index);
-void cdnsp_queue_flush_endpoint(struct cdnsp_device *pdev,
-                               unsigned int ep_index);
 void cdnsp_force_header_wakeup(struct cdnsp_device *pdev, int intf_num);
 void cdnsp_queue_reset_device(struct cdnsp_device *pdev);
 void cdnsp_queue_new_dequeue_state(struct cdnsp_device *pdev,
@@ -1574,7 +1570,6 @@ void cdnsp_irq_reset(struct cdnsp_device *pdev);
 int cdnsp_halt_endpoint(struct cdnsp_device *pdev,
                        struct cdnsp_ep *pep, int value);
 int cdnsp_cmd_stop_ep(struct cdnsp_device *pdev, struct cdnsp_ep *pep);
-int cdnsp_cmd_flush_ep(struct cdnsp_device *pdev, struct cdnsp_ep *pep);
 void cdnsp_setup_analyze(struct cdnsp_device *pdev);
 int cdnsp_status_stage(struct cdnsp_device *pdev);
 int cdnsp_reset_device(struct cdnsp_device *pdev);
index 07f6068342d4604720cdf79d849919d0f34e6b97..af981778382df71d6c109e3a2f7a83b0b3fb2d6a 100644 (file)
@@ -2123,19 +2123,6 @@ ep_stopped:
        return ret;
 }
 
-int cdnsp_cmd_flush_ep(struct cdnsp_device *pdev, struct cdnsp_ep *pep)
-{
-       int ret;
-
-       cdnsp_queue_flush_endpoint(pdev, pep->idx);
-       cdnsp_ring_cmd_db(pdev);
-       ret = cdnsp_wait_for_cmd_compl(pdev);
-
-       trace_cdnsp_handle_cmd_flush_ep(pep->out_ctx);
-
-       return ret;
-}
-
 /*
  * The transfer burst count field of the isochronous TRB defines the number of
  * bursts that are required to move all packets in this TD. Only SuperSpeed
@@ -2465,17 +2452,6 @@ void cdnsp_queue_halt_endpoint(struct cdnsp_device *pdev, unsigned int ep_index)
                            EP_ID_FOR_TRB(ep_index));
 }
 
-/*
- * Queue a flush endpoint request on the command ring.
- */
-void  cdnsp_queue_flush_endpoint(struct cdnsp_device *pdev,
-                                unsigned int ep_index)
-{
-       cdnsp_queue_command(pdev, 0, 0, 0, TRB_TYPE(TRB_FLUSH_ENDPOINT) |
-                           SLOT_ID_FOR_TRB(pdev->slot_id) |
-                           EP_ID_FOR_TRB(ep_index));
-}
-
 void cdnsp_force_header_wakeup(struct cdnsp_device *pdev, int intf_num)
 {
        u32 lo, mid;
index c815824a0b2d9c0d1a07a29853fd6d748ad072dd..bab45bc623612d3d1bb7ac9d4332b111eba67469 100644 (file)
@@ -43,6 +43,10 @@ config USB_CHIPIDEA_MSM
        tristate "Enable MSM hsusb glue driver" if EXPERT
        default USB_CHIPIDEA
 
+config USB_CHIPIDEA_NPCM
+       tristate "Enable NPCM hsusb glue driver" if EXPERT
+       default USB_CHIPIDEA
+
 config USB_CHIPIDEA_IMX
        tristate "Enable i.MX USB glue driver" if EXPERT
        depends on OF
index 71afeab97e837741eedb3db7243dc0e281711c4c..718cb24603dd3e74f246afa03dfe7ad61a86fc2c 100644 (file)
@@ -13,6 +13,7 @@ ci_hdrc-$(CONFIG_USB_OTG_FSM)         += otg_fsm.o
 
 obj-$(CONFIG_USB_CHIPIDEA_GENERIC)     += ci_hdrc_usb2.o
 obj-$(CONFIG_USB_CHIPIDEA_MSM)         += ci_hdrc_msm.o
+obj-$(CONFIG_USB_CHIPIDEA_NPCM)                += ci_hdrc_npcm.o
 obj-$(CONFIG_USB_CHIPIDEA_PCI)         += ci_hdrc_pci.o
 obj-$(CONFIG_USB_CHIPIDEA_IMX)         += usbmisc_imx.o ci_hdrc_imx.o
 obj-$(CONFIG_USB_CHIPIDEA_TEGRA)       += ci_hdrc_tegra.o
diff --git a/drivers/usb/chipidea/ci_hdrc_npcm.c b/drivers/usb/chipidea/ci_hdrc_npcm.c
new file mode 100644 (file)
index 0000000..e4a191e
--- /dev/null
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2023 Nuvoton Technology corporation.
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/usb/chipidea.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/reset-controller.h>
+#include <linux/of.h>
+
+#include "ci.h"
+
+struct npcm_udc_data {
+       struct platform_device  *ci;
+       struct clk              *core_clk;
+       struct ci_hdrc_platform_data pdata;
+};
+
+static int npcm_udc_notify_event(struct ci_hdrc *ci, unsigned event)
+{
+       struct device *dev = ci->dev->parent;
+
+       switch (event) {
+       case CI_HDRC_CONTROLLER_RESET_EVENT:
+               /* clear all mode bits */
+               hw_write(ci, OP_USBMODE, 0xffffffff, 0x0);
+               break;
+       default:
+               dev_dbg(dev, "unknown ci_hdrc event (%d)\n",event);
+               break;
+       }
+
+       return 0;
+}
+
+static int npcm_udc_probe(struct platform_device *pdev)
+{
+       int ret;
+       struct npcm_udc_data *ci;
+       struct platform_device *plat_ci;
+       struct device *dev = &pdev->dev;
+
+       ci = devm_kzalloc(&pdev->dev, sizeof(*ci), GFP_KERNEL);
+       if (!ci)
+               return -ENOMEM;
+       platform_set_drvdata(pdev, ci);
+
+       ci->core_clk = devm_clk_get_optional(dev, NULL);
+       if (IS_ERR(ci->core_clk))
+               return PTR_ERR(ci->core_clk);
+
+       ret = clk_prepare_enable(ci->core_clk);
+       if (ret)
+               return dev_err_probe(dev, ret, "failed to enable the clock: %d\n", ret);
+
+       ci->pdata.name = dev_name(dev);
+       ci->pdata.capoffset = DEF_CAPOFFSET;
+       ci->pdata.flags = CI_HDRC_REQUIRES_ALIGNED_DMA |
+               CI_HDRC_FORCE_VBUS_ACTIVE_ALWAYS;
+       ci->pdata.phy_mode = USBPHY_INTERFACE_MODE_UTMI;
+       ci->pdata.notify_event = npcm_udc_notify_event;
+
+       plat_ci = ci_hdrc_add_device(dev, pdev->resource, pdev->num_resources,
+                                    &ci->pdata);
+       if (IS_ERR(plat_ci)) {
+               ret = PTR_ERR(plat_ci);
+               dev_err(dev, "failed to register HDRC NPCM device: %d\n", ret);
+               goto clk_err;
+       }
+
+       pm_runtime_no_callbacks(dev);
+       pm_runtime_enable(dev);
+
+       return 0;
+
+clk_err:
+       clk_disable_unprepare(ci->core_clk);
+       return ret;
+}
+
+static int npcm_udc_remove(struct platform_device *pdev)
+{
+       struct npcm_udc_data *ci = platform_get_drvdata(pdev);
+
+       pm_runtime_disable(&pdev->dev);
+       ci_hdrc_remove_device(ci->ci);
+       clk_disable_unprepare(ci->core_clk);
+
+       return 0;
+}
+
+static const struct of_device_id npcm_udc_dt_match[] = {
+       { .compatible = "nuvoton,npcm750-udc", },
+       { .compatible = "nuvoton,npcm845-udc", },
+       { }
+};
+MODULE_DEVICE_TABLE(of, npcm_udc_dt_match);
+
+static struct platform_driver npcm_udc_driver = {
+       .probe = npcm_udc_probe,
+       .remove = npcm_udc_remove,
+       .driver = {
+               .name = "npcm_udc",
+               .of_match_table = npcm_udc_dt_match,
+       },
+};
+
+module_platform_driver(npcm_udc_driver);
+
+MODULE_DESCRIPTION("NPCM USB device controller driver");
+MODULE_AUTHOR("Tomer Maimon <tomer.maimon@nuvoton.com>");
+MODULE_LICENSE("GPL v2");
index 8e78bf643e25c3c90fe853be7d3dd56867d12cee..2cc305803217d8010d5543a56df2310de60c187e 100644 (file)
@@ -293,14 +293,12 @@ static int tegra_usb_probe(struct platform_device *pdev)
        usb->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "nvidia,phy", 0);
        if (IS_ERR(usb->phy))
                return dev_err_probe(&pdev->dev, PTR_ERR(usb->phy),
-                                    "failed to get PHY\n");
+                                    "failed to get PHY");
 
        usb->clk = devm_clk_get(&pdev->dev, NULL);
-       if (IS_ERR(usb->clk)) {
-               err = PTR_ERR(usb->clk);
-               dev_err(&pdev->dev, "failed to get clock: %d\n", err);
-               return err;
-       }
+       if (IS_ERR(usb->clk))
+               return dev_err_probe(&pdev->dev, PTR_ERR(usb->clk),
+                                    "failed to get clock");
 
        err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
        if (err)
@@ -316,7 +314,7 @@ static int tegra_usb_probe(struct platform_device *pdev)
 
        err = tegra_usb_reset_controller(&pdev->dev);
        if (err) {
-               dev_err(&pdev->dev, "failed to reset controller: %d\n", err);
+               dev_err_probe(&pdev->dev, err, "failed to reset controller");
                goto fail_power_off;
        }
 
@@ -347,8 +345,8 @@ static int tegra_usb_probe(struct platform_device *pdev)
        usb->dev = ci_hdrc_add_device(&pdev->dev, pdev->resource,
                                      pdev->num_resources, &usb->data);
        if (IS_ERR(usb->dev)) {
-               err = PTR_ERR(usb->dev);
-               dev_err(&pdev->dev, "failed to add HDRC device: %d\n", err);
+               err = dev_err_probe(&pdev->dev, PTR_ERR(usb->dev),
+                                   "failed to add HDRC device");
                goto phy_shutdown;
        }
 
index 1321ee67f3b82edfb0c1a92e983dfe56b75d9bb1..97379f653b06295f19beed9478899f90a1dfd4e4 100644 (file)
@@ -9,9 +9,9 @@
 #include <linux/dma-mapping.h>
 #include <linux/module.h>
 #include <linux/of.h>
-#include <linux/of_platform.h>
 #include <linux/phy/phy.h>
 #include <linux/platform_device.h>
+#include <linux/property.h>
 #include <linux/usb/chipidea.h>
 #include <linux/usb/hcd.h>
 #include <linux/usb/ulpi.h>
@@ -51,8 +51,8 @@ static int ci_hdrc_usb2_probe(struct platform_device *pdev)
        struct device *dev = &pdev->dev;
        struct ci_hdrc_usb2_priv *priv;
        struct ci_hdrc_platform_data *ci_pdata = dev_get_platdata(dev);
+       const struct ci_hdrc_platform_data *data;
        int ret;
-       const struct of_device_id *match;
 
        if (!ci_pdata) {
                ci_pdata = devm_kmalloc(dev, sizeof(*ci_pdata), GFP_KERNEL);
@@ -61,11 +61,10 @@ static int ci_hdrc_usb2_probe(struct platform_device *pdev)
                *ci_pdata = ci_default_pdata;   /* struct copy */
        }
 
-       match = of_match_device(ci_hdrc_usb2_of_match, &pdev->dev);
-       if (match && match->data) {
+       data = device_get_match_data(&pdev->dev);
+       if (data)
                /* struct copy */
-               *ci_pdata = *(struct ci_hdrc_platform_data *)match->data;
-       }
+               *ci_pdata = *data;
 
        priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
        if (!priv)
@@ -120,7 +119,7 @@ static struct platform_driver ci_hdrc_usb2_driver = {
        .remove_new = ci_hdrc_usb2_remove,
        .driver = {
                .name           = "chipidea-usb2",
-               .of_match_table = of_match_ptr(ci_hdrc_usb2_of_match),
+               .of_match_table = ci_hdrc_usb2_of_match,
        },
 };
 module_platform_driver(ci_hdrc_usb2_driver);
index 08af26b762a2d6c083c6a52af4dcd4e93285f5ae..0cce192083701e5635b7079fb7dd1d124f4e29aa 100644 (file)
@@ -30,8 +30,7 @@ struct ehci_ci_priv {
 };
 
 struct ci_hdrc_dma_aligned_buffer {
-       void *kmalloc_ptr;
-       void *old_xfer_buffer;
+       void *original_buffer;
        u8 data[];
 };
 
@@ -380,59 +379,52 @@ static int ci_ehci_bus_suspend(struct usb_hcd *hcd)
        return 0;
 }
 
-static void ci_hdrc_free_dma_aligned_buffer(struct urb *urb)
+static void ci_hdrc_free_dma_aligned_buffer(struct urb *urb, bool copy_back)
 {
        struct ci_hdrc_dma_aligned_buffer *temp;
-       size_t length;
 
        if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER))
                return;
+       urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER;
 
        temp = container_of(urb->transfer_buffer,
                            struct ci_hdrc_dma_aligned_buffer, data);
+       urb->transfer_buffer = temp->original_buffer;
+
+       if (copy_back && usb_urb_dir_in(urb)) {
+               size_t length;
 
-       if (usb_urb_dir_in(urb)) {
                if (usb_pipeisoc(urb->pipe))
                        length = urb->transfer_buffer_length;
                else
                        length = urb->actual_length;
 
-               memcpy(temp->old_xfer_buffer, temp->data, length);
+               memcpy(temp->original_buffer, temp->data, length);
        }
-       urb->transfer_buffer = temp->old_xfer_buffer;
-       kfree(temp->kmalloc_ptr);
 
-       urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER;
+       kfree(temp);
 }
 
 static int ci_hdrc_alloc_dma_aligned_buffer(struct urb *urb, gfp_t mem_flags)
 {
-       struct ci_hdrc_dma_aligned_buffer *temp, *kmalloc_ptr;
-       const unsigned int ci_hdrc_usb_dma_align = 32;
-       size_t kmalloc_size;
+       struct ci_hdrc_dma_aligned_buffer *temp;
 
-       if (urb->num_sgs || urb->sg || urb->transfer_buffer_length == 0 ||
-           !((uintptr_t)urb->transfer_buffer & (ci_hdrc_usb_dma_align - 1)))
+       if (urb->num_sgs || urb->sg || urb->transfer_buffer_length == 0)
+               return 0;
+       if (IS_ALIGNED((uintptr_t)urb->transfer_buffer, 4)
+           && IS_ALIGNED(urb->transfer_buffer_length, 4))
                return 0;
 
-       /* Allocate a buffer with enough padding for alignment */
-       kmalloc_size = urb->transfer_buffer_length +
-                      sizeof(struct ci_hdrc_dma_aligned_buffer) +
-                      ci_hdrc_usb_dma_align - 1;
-
-       kmalloc_ptr = kmalloc(kmalloc_size, mem_flags);
-       if (!kmalloc_ptr)
+       temp = kmalloc(sizeof(*temp) + ALIGN(urb->transfer_buffer_length, 4), mem_flags);
+       if (!temp)
                return -ENOMEM;
 
-       /* Position our struct dma_aligned_buffer such that data is aligned */
-       temp = PTR_ALIGN(kmalloc_ptr + 1, ci_hdrc_usb_dma_align) - 1;
-       temp->kmalloc_ptr = kmalloc_ptr;
-       temp->old_xfer_buffer = urb->transfer_buffer;
        if (usb_urb_dir_out(urb))
                memcpy(temp->data, urb->transfer_buffer,
                       urb->transfer_buffer_length);
-       urb->transfer_buffer = temp->data;
 
+       temp->original_buffer = urb->transfer_buffer;
+       urb->transfer_buffer = temp->data;
        urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER;
 
        return 0;
@@ -449,7 +441,7 @@ static int ci_hdrc_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
 
        ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags);
        if (ret)
-               ci_hdrc_free_dma_aligned_buffer(urb);
+               ci_hdrc_free_dma_aligned_buffer(urb, false);
 
        return ret;
 }
@@ -457,7 +449,7 @@ static int ci_hdrc_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
 static void ci_hdrc_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb)
 {
        usb_hcd_unmap_urb_for_dma(hcd, urb);
-       ci_hdrc_free_dma_aligned_buffer(urb);
+       ci_hdrc_free_dma_aligned_buffer(urb, true);
 }
 
 #ifdef CONFIG_PM_SLEEP
index f5490f2a5b6bca6b725c596d7536c2b548547d4b..647e98f4e35110fd587fc8e2757eca266adf9411 100644 (file)
@@ -130,8 +130,11 @@ enum ci_role ci_otg_role(struct ci_hdrc *ci)
 
 void ci_handle_vbus_change(struct ci_hdrc *ci)
 {
-       if (!ci->is_otg)
+       if (!ci->is_otg) {
+               if (ci->platdata->flags & CI_HDRC_FORCE_VBUS_ACTIVE_ALWAYS)
+                       usb_gadget_vbus_connect(&ci->gadget);
                return;
+       }
 
        if (hw_read_otgsc(ci, OTGSC_BSV) && !ci->vbus_active)
                usb_gadget_vbus_connect(&ci->gadget);
index 990280688b254d913d843bde17dae0a8be203778..ee3156f49533874cd6e6b4c697ae0871ea745dfa 100644 (file)
@@ -206,8 +206,7 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct hc_driver *driver)
                goto free_irq_vectors;
        }
 
-       hcd->amd_resume_bug = (usb_hcd_amd_remote_wakeup_quirk(dev) &&
-                       driver->flags & (HCD_USB11 | HCD_USB3)) ? 1 : 0;
+       hcd->amd_resume_bug = usb_hcd_amd_resume_bug(dev, driver);
 
        if (driver->flags & HCD_MEMORY) {
                /* EHCI, OHCI */
index 0ff47eeffb490985f62725d8d42d43fb9ecd5b26..b4584a0cd484562633488d1552f60281d527bd6a 100644 (file)
@@ -2274,6 +2274,8 @@ void usb_disconnect(struct usb_device **pdev)
                 */
                if (!test_and_set_bit(port1, hub->child_usage_bits))
                        pm_runtime_get_sync(&port_dev->dev);
+
+               typec_deattach(port_dev->connector, &udev->dev);
        }
 
        usb_remove_ep_devs(&udev->ep0);
@@ -2620,6 +2622,8 @@ int usb_new_device(struct usb_device *udev)
 
                if (!test_and_set_bit(port1, hub->child_usage_bits))
                        pm_runtime_get_sync(&port_dev->dev);
+
+               typec_attach(port_dev->connector, &udev->dev);
        }
 
        (void) usb_create_ep_devs(&udev->dev, &udev->ep0, udev);
index d44dd7f6623ee6e778f7f4b3ba0fc578ddd1b848..43ce21c96a511455b17bea7274354a743b54af10 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/usb.h>
 #include <linux/usb/ch11.h>
 #include <linux/usb/hcd.h>
+#include <linux/usb/typec.h>
 #include "usb.h"
 
 struct usb_hub {
@@ -82,6 +83,7 @@ struct usb_hub {
  * @dev: generic device interface
  * @port_owner: port's owner
  * @peer: related usb2 and usb3 ports (share the same connector)
+ * @connector: USB Type-C connector
  * @req: default pm qos request for hubs without port power control
  * @connect_type: port's connect type
  * @state: device state of the usb device attached to the port
@@ -100,6 +102,7 @@ struct usb_port {
        struct device dev;
        struct usb_dev_state *port_owner;
        struct usb_port *peer;
+       struct typec_connector *connector;
        struct dev_pm_qos_request *req;
        enum usb_port_connect_type connect_type;
        enum usb_device_state state;
index 77be0dc28da9a6b08d7427762ebe76333fc65f65..c628c1abc90711cb9b8e652a0d903a6359c968bc 100644 (file)
@@ -653,6 +653,7 @@ static void find_and_link_peer(struct usb_hub *hub, int port1)
 
 static int connector_bind(struct device *dev, struct device *connector, void *data)
 {
+       struct usb_port *port_dev = to_usb_port(dev);
        int ret;
 
        ret = sysfs_create_link(&dev->kobj, &connector->kobj, "connector");
@@ -660,16 +661,30 @@ static int connector_bind(struct device *dev, struct device *connector, void *da
                return ret;
 
        ret = sysfs_create_link(&connector->kobj, &dev->kobj, dev_name(dev));
-       if (ret)
+       if (ret) {
                sysfs_remove_link(&dev->kobj, "connector");
+               return ret;
+       }
+
+       port_dev->connector = data;
+
+       /*
+        * If there is already USB device connected to the port, letting the
+        * Type-C connector know about it immediately.
+        */
+       if (port_dev->child)
+               typec_attach(port_dev->connector, &port_dev->child->dev);
 
-       return ret;
+       return 0;
 }
 
 static void connector_unbind(struct device *dev, struct device *connector, void *data)
 {
+       struct usb_port *port_dev = to_usb_port(dev);
+
        sysfs_remove_link(&connector->kobj, dev_name(dev));
        sysfs_remove_link(&dev->kobj, "connector");
+       port_dev->connector = NULL;
 }
 
 static const struct component_ops connector_ops = {
@@ -698,6 +713,7 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
        set_bit(port1, hub->power_bits);
        port_dev->dev.parent = hub->intfdev;
        if (hub_is_superspeed(hdev)) {
+               port_dev->is_superspeed = 1;
                port_dev->usb3_lpm_u1_permit = 1;
                port_dev->usb3_lpm_u2_permit = 1;
                port_dev->dev.groups = port_dev_usb3_group;
@@ -705,8 +721,6 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
                port_dev->dev.groups = port_dev_group;
        port_dev->dev.type = &usb_port_device_type;
        port_dev->dev.driver = &usb_port_driver;
-       if (hub_is_superspeed(hub->hdev))
-               port_dev->is_superspeed = 1;
        dev_set_name(&port_dev->dev, "%s-port%d", dev_name(&hub->hdev->dev),
                        port1);
        mutex_init(&port_dev->status_lock);
index 657f1f659ffaf83d0d0d96953d4017ff55f1da3b..35c7a4df8e71754f88d36f2f551d2e1e9eecef56 100644 (file)
@@ -4769,8 +4769,8 @@ fail3:
        if (qh_allocated && qh->channel && qh->channel->qh == qh)
                qh->channel->qh = NULL;
 fail2:
-       spin_unlock_irqrestore(&hsotg->lock, flags);
        urb->hcpriv = NULL;
+       spin_unlock_irqrestore(&hsotg->lock, flags);
        kfree(qtd);
 fail1:
        if (qh_allocated) {
index 93f52e371cddb6ecb2fef3b068858a39df9c2578..fb03162ae9b764d97d7df31ec1e158b564898076 100644 (file)
@@ -5,7 +5,7 @@
 
 #include <linux/kernel.h>
 #include <linux/module.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
 #include <linux/usb/of.h>
 #include <linux/pci_ids.h>
 #include <linux/pci.h>
@@ -968,26 +968,17 @@ typedef void (*set_params_cb)(struct dwc2_hsotg *data);
 
 int dwc2_init_params(struct dwc2_hsotg *hsotg)
 {
-       const struct of_device_id *match;
        set_params_cb set_params;
 
        dwc2_set_default_params(hsotg);
        dwc2_get_device_properties(hsotg);
 
-       match = of_match_device(dwc2_of_match_table, hsotg->dev);
-       if (match && match->data) {
-               set_params = match->data;
+       set_params = device_get_match_data(hsotg->dev);
+       if (set_params) {
                set_params(hsotg);
-       } else if (!match) {
-               const struct acpi_device_id *amatch;
-               const struct pci_device_id *pmatch = NULL;
-
-               amatch = acpi_match_device(dwc2_acpi_match, hsotg->dev);
-               if (amatch && amatch->driver_data) {
-                       set_params = (set_params_cb)amatch->driver_data;
-                       set_params(hsotg);
-               } else if (!amatch)
-                       pmatch = pci_match_id(dwc2_pci_ids, to_pci_dev(hsotg->dev->parent));
+       } else {
+               const struct pci_device_id *pmatch =
+                       pci_match_id(dwc2_pci_ids, to_pci_dev(hsotg->dev->parent));
 
                if (pmatch && pmatch->driver_data) {
                        set_params = (set_params_cb)pmatch->driver_data;
index 98efcbb76c882147f17981ed61dcb360f50c1739..5fc27b20df6301c27b71b9fec2be9353f40776a7 100644 (file)
@@ -178,4 +178,15 @@ config USB_DWC3_OCTEON
          Only the host mode is currently supported.
          Say 'Y' or 'M' here if you have one such device.
 
+config USB_DWC3_RTK
+       tristate "Realtek DWC3 Platform Driver"
+       depends on OF && ARCH_REALTEK
+       default USB_DWC3
+       select USB_ROLE_SWITCH
+       help
+         RTK DHC RTD SoCs with DesignWare Core USB3 IP inside,
+         and IP Core configured for USB 2.0 and USB 3.0 in host
+         or dual-role mode.
+         Say 'Y' or 'M' if you have such device.
+
 endif
index fe1493d4bbe58e5c4de0adf477e023ac386cd236..124eda2522d9c1f4caab222ec9770d0deaf655fc 100644 (file)
@@ -55,3 +55,4 @@ obj-$(CONFIG_USB_DWC3_QCOM)           += dwc3-qcom.o
 obj-$(CONFIG_USB_DWC3_IMX8MP)          += dwc3-imx8mp.o
 obj-$(CONFIG_USB_DWC3_XILINX)          += dwc3-xilinx.o
 obj-$(CONFIG_USB_DWC3_OCTEON)          += dwc3-octeon.o
+obj-$(CONFIG_USB_DWC3_RTK)             += dwc3-rtk.o
index 343d2570189ff918234635365d15c3cb17bda0c4..0328c86ef80613a8e3296c3cab8ee6a94a96604d 100644 (file)
@@ -854,8 +854,20 @@ static int dwc3_clk_enable(struct dwc3 *dwc)
        if (ret)
                goto disable_ref_clk;
 
+       ret = clk_prepare_enable(dwc->utmi_clk);
+       if (ret)
+               goto disable_susp_clk;
+
+       ret = clk_prepare_enable(dwc->pipe_clk);
+       if (ret)
+               goto disable_utmi_clk;
+
        return 0;
 
+disable_utmi_clk:
+       clk_disable_unprepare(dwc->utmi_clk);
+disable_susp_clk:
+       clk_disable_unprepare(dwc->susp_clk);
 disable_ref_clk:
        clk_disable_unprepare(dwc->ref_clk);
 disable_bus_clk:
@@ -865,6 +877,8 @@ disable_bus_clk:
 
 static void dwc3_clk_disable(struct dwc3 *dwc)
 {
+       clk_disable_unprepare(dwc->pipe_clk);
+       clk_disable_unprepare(dwc->utmi_clk);
        clk_disable_unprepare(dwc->susp_clk);
        clk_disable_unprepare(dwc->ref_clk);
        clk_disable_unprepare(dwc->bus_clk);
@@ -1094,6 +1108,111 @@ static void dwc3_set_power_down_clk_scale(struct dwc3 *dwc)
        }
 }
 
+static void dwc3_config_threshold(struct dwc3 *dwc)
+{
+       u32 reg;
+       u8 rx_thr_num;
+       u8 rx_maxburst;
+       u8 tx_thr_num;
+       u8 tx_maxburst;
+
+       /*
+        * Must config both number of packets and max burst settings to enable
+        * RX and/or TX threshold.
+        */
+       if (!DWC3_IP_IS(DWC3) && dwc->dr_mode == USB_DR_MODE_HOST) {
+               rx_thr_num = dwc->rx_thr_num_pkt_prd;
+               rx_maxburst = dwc->rx_max_burst_prd;
+               tx_thr_num = dwc->tx_thr_num_pkt_prd;
+               tx_maxburst = dwc->tx_max_burst_prd;
+
+               if (rx_thr_num && rx_maxburst) {
+                       reg = dwc3_readl(dwc->regs, DWC3_GRXTHRCFG);
+                       reg |= DWC31_RXTHRNUMPKTSEL_PRD;
+
+                       reg &= ~DWC31_RXTHRNUMPKT_PRD(~0);
+                       reg |= DWC31_RXTHRNUMPKT_PRD(rx_thr_num);
+
+                       reg &= ~DWC31_MAXRXBURSTSIZE_PRD(~0);
+                       reg |= DWC31_MAXRXBURSTSIZE_PRD(rx_maxburst);
+
+                       dwc3_writel(dwc->regs, DWC3_GRXTHRCFG, reg);
+               }
+
+               if (tx_thr_num && tx_maxburst) {
+                       reg = dwc3_readl(dwc->regs, DWC3_GTXTHRCFG);
+                       reg |= DWC31_TXTHRNUMPKTSEL_PRD;
+
+                       reg &= ~DWC31_TXTHRNUMPKT_PRD(~0);
+                       reg |= DWC31_TXTHRNUMPKT_PRD(tx_thr_num);
+
+                       reg &= ~DWC31_MAXTXBURSTSIZE_PRD(~0);
+                       reg |= DWC31_MAXTXBURSTSIZE_PRD(tx_maxburst);
+
+                       dwc3_writel(dwc->regs, DWC3_GTXTHRCFG, reg);
+               }
+       }
+
+       rx_thr_num = dwc->rx_thr_num_pkt;
+       rx_maxburst = dwc->rx_max_burst;
+       tx_thr_num = dwc->tx_thr_num_pkt;
+       tx_maxburst = dwc->tx_max_burst;
+
+       if (DWC3_IP_IS(DWC3)) {
+               if (rx_thr_num && rx_maxburst) {
+                       reg = dwc3_readl(dwc->regs, DWC3_GRXTHRCFG);
+                       reg |= DWC3_GRXTHRCFG_PKTCNTSEL;
+
+                       reg &= ~DWC3_GRXTHRCFG_RXPKTCNT(~0);
+                       reg |= DWC3_GRXTHRCFG_RXPKTCNT(rx_thr_num);
+
+                       reg &= ~DWC3_GRXTHRCFG_MAXRXBURSTSIZE(~0);
+                       reg |= DWC3_GRXTHRCFG_MAXRXBURSTSIZE(rx_maxburst);
+
+                       dwc3_writel(dwc->regs, DWC3_GRXTHRCFG, reg);
+               }
+
+               if (tx_thr_num && tx_maxburst) {
+                       reg = dwc3_readl(dwc->regs, DWC3_GTXTHRCFG);
+                       reg |= DWC3_GTXTHRCFG_PKTCNTSEL;
+
+                       reg &= ~DWC3_GTXTHRCFG_TXPKTCNT(~0);
+                       reg |= DWC3_GTXTHRCFG_TXPKTCNT(tx_thr_num);
+
+                       reg &= ~DWC3_GTXTHRCFG_MAXTXBURSTSIZE(~0);
+                       reg |= DWC3_GTXTHRCFG_MAXTXBURSTSIZE(tx_maxburst);
+
+                       dwc3_writel(dwc->regs, DWC3_GTXTHRCFG, reg);
+               }
+       } else {
+               if (rx_thr_num && rx_maxburst) {
+                       reg = dwc3_readl(dwc->regs, DWC3_GRXTHRCFG);
+                       reg |= DWC31_GRXTHRCFG_PKTCNTSEL;
+
+                       reg &= ~DWC31_GRXTHRCFG_RXPKTCNT(~0);
+                       reg |= DWC31_GRXTHRCFG_RXPKTCNT(rx_thr_num);
+
+                       reg &= ~DWC31_GRXTHRCFG_MAXRXBURSTSIZE(~0);
+                       reg |= DWC31_GRXTHRCFG_MAXRXBURSTSIZE(rx_maxburst);
+
+                       dwc3_writel(dwc->regs, DWC3_GRXTHRCFG, reg);
+               }
+
+               if (tx_thr_num && tx_maxburst) {
+                       reg = dwc3_readl(dwc->regs, DWC3_GTXTHRCFG);
+                       reg |= DWC31_GTXTHRCFG_PKTCNTSEL;
+
+                       reg &= ~DWC31_GTXTHRCFG_TXPKTCNT(~0);
+                       reg |= DWC31_GTXTHRCFG_TXPKTCNT(tx_thr_num);
+
+                       reg &= ~DWC31_GTXTHRCFG_MAXTXBURSTSIZE(~0);
+                       reg |= DWC31_GTXTHRCFG_MAXTXBURSTSIZE(tx_maxburst);
+
+                       dwc3_writel(dwc->regs, DWC3_GTXTHRCFG, reg);
+               }
+       }
+}
+
 /**
  * dwc3_core_init - Low-level initialization of DWC3 Core
  * @dwc: Pointer to our controller context structure
@@ -1246,42 +1365,7 @@ static int dwc3_core_init(struct dwc3 *dwc)
                dwc3_writel(dwc->regs, DWC3_GUCTL1, reg);
        }
 
-       /*
-        * Must config both number of packets and max burst settings to enable
-        * RX and/or TX threshold.
-        */
-       if (!DWC3_IP_IS(DWC3) && dwc->dr_mode == USB_DR_MODE_HOST) {
-               u8 rx_thr_num = dwc->rx_thr_num_pkt_prd;
-               u8 rx_maxburst = dwc->rx_max_burst_prd;
-               u8 tx_thr_num = dwc->tx_thr_num_pkt_prd;
-               u8 tx_maxburst = dwc->tx_max_burst_prd;
-
-               if (rx_thr_num && rx_maxburst) {
-                       reg = dwc3_readl(dwc->regs, DWC3_GRXTHRCFG);
-                       reg |= DWC31_RXTHRNUMPKTSEL_PRD;
-
-                       reg &= ~DWC31_RXTHRNUMPKT_PRD(~0);
-                       reg |= DWC31_RXTHRNUMPKT_PRD(rx_thr_num);
-
-                       reg &= ~DWC31_MAXRXBURSTSIZE_PRD(~0);
-                       reg |= DWC31_MAXRXBURSTSIZE_PRD(rx_maxburst);
-
-                       dwc3_writel(dwc->regs, DWC3_GRXTHRCFG, reg);
-               }
-
-               if (tx_thr_num && tx_maxburst) {
-                       reg = dwc3_readl(dwc->regs, DWC3_GTXTHRCFG);
-                       reg |= DWC31_TXTHRNUMPKTSEL_PRD;
-
-                       reg &= ~DWC31_TXTHRNUMPKT_PRD(~0);
-                       reg |= DWC31_TXTHRNUMPKT_PRD(tx_thr_num);
-
-                       reg &= ~DWC31_MAXTXBURSTSIZE_PRD(~0);
-                       reg |= DWC31_MAXTXBURSTSIZE_PRD(tx_maxburst);
-
-                       dwc3_writel(dwc->regs, DWC3_GTXTHRCFG, reg);
-               }
-       }
+       dwc3_config_threshold(dwc);
 
        return 0;
 
@@ -1417,6 +1501,10 @@ static void dwc3_get_properties(struct dwc3 *dwc)
        u8                      lpm_nyet_threshold;
        u8                      tx_de_emphasis;
        u8                      hird_threshold;
+       u8                      rx_thr_num_pkt = 0;
+       u8                      rx_max_burst = 0;
+       u8                      tx_thr_num_pkt = 0;
+       u8                      tx_max_burst = 0;
        u8                      rx_thr_num_pkt_prd = 0;
        u8                      rx_max_burst_prd = 0;
        u8                      tx_thr_num_pkt_prd = 0;
@@ -1479,6 +1567,14 @@ static void dwc3_get_properties(struct dwc3 *dwc)
                                "snps,usb2-lpm-disable");
        dwc->usb2_gadget_lpm_disable = device_property_read_bool(dev,
                                "snps,usb2-gadget-lpm-disable");
+       device_property_read_u8(dev, "snps,rx-thr-num-pkt",
+                               &rx_thr_num_pkt);
+       device_property_read_u8(dev, "snps,rx-max-burst",
+                               &rx_max_burst);
+       device_property_read_u8(dev, "snps,tx-thr-num-pkt",
+                               &tx_thr_num_pkt);
+       device_property_read_u8(dev, "snps,tx-max-burst",
+                               &tx_max_burst);
        device_property_read_u8(dev, "snps,rx-thr-num-pkt-prd",
                                &rx_thr_num_pkt_prd);
        device_property_read_u8(dev, "snps,rx-max-burst-prd",
@@ -1560,6 +1656,12 @@ static void dwc3_get_properties(struct dwc3 *dwc)
 
        dwc->hird_threshold = hird_threshold;
 
+       dwc->rx_thr_num_pkt = rx_thr_num_pkt;
+       dwc->rx_max_burst = rx_max_burst;
+
+       dwc->tx_thr_num_pkt = tx_thr_num_pkt;
+       dwc->tx_max_burst = tx_max_burst;
+
        dwc->rx_thr_num_pkt_prd = rx_thr_num_pkt_prd;
        dwc->rx_max_burst_prd = rx_max_burst_prd;
 
@@ -1785,6 +1887,20 @@ static int dwc3_get_clocks(struct dwc3 *dwc)
                }
        }
 
+       /* specific to Rockchip RK3588 */
+       dwc->utmi_clk = devm_clk_get_optional(dev, "utmi");
+       if (IS_ERR(dwc->utmi_clk)) {
+               return dev_err_probe(dev, PTR_ERR(dwc->utmi_clk),
+                               "could not get utmi clock\n");
+       }
+
+       /* specific to Rockchip RK3588 */
+       dwc->pipe_clk = devm_clk_get_optional(dev, "pipe");
+       if (IS_ERR(dwc->pipe_clk)) {
+               return dev_err_probe(dev, PTR_ERR(dwc->pipe_clk),
+                               "could not get pipe clock\n");
+       }
+
        return 0;
 }
 
index a69ac67d89fe687c1633368ce1b88a95c96b8e39..efe6caf4d0e873ef030ef55bf0ecbec52b8c91e0 100644 (file)
 #define DWC3_GRXTHRCFG_RXPKTCNT(n) (((n) & 0xf) << 24)
 #define DWC3_GRXTHRCFG_PKTCNTSEL BIT(29)
 
+/* Global TX Threshold Configuration Register */
+#define DWC3_GTXTHRCFG_MAXTXBURSTSIZE(n) (((n) & 0xff) << 16)
+#define DWC3_GTXTHRCFG_TXPKTCNT(n) (((n) & 0xf) << 24)
+#define DWC3_GTXTHRCFG_PKTCNTSEL BIT(29)
+
 /* Global RX Threshold Configuration Register for DWC_usb31 only */
 #define DWC31_GRXTHRCFG_MAXRXBURSTSIZE(n)      (((n) & 0x1f) << 16)
 #define DWC31_GRXTHRCFG_RXPKTCNT(n)            (((n) & 0x1f) << 21)
@@ -991,6 +996,8 @@ struct dwc3_scratchpad_array {
  * @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
+ * @utmi_clk: clock used for USB2 PHY communication
+ * @pipe_clk: clock used for USB3 PHY communication
  * @reset: reset control
  * @regs: base address for our registers
  * @regs_size: address space size
@@ -1045,6 +1052,10 @@ struct dwc3_scratchpad_array {
  * @test_mode_nr: test feature selector
  * @lpm_nyet_threshold: LPM NYET response threshold
  * @hird_threshold: HIRD threshold
+ * @rx_thr_num_pkt: USB receive packet count
+ * @rx_max_burst: max USB receive burst size
+ * @tx_thr_num_pkt: USB transmit packet count
+ * @tx_max_burst: max USB transmit burst size
  * @rx_thr_num_pkt_prd: periodic ESS receive packet count
  * @rx_max_burst_prd: max periodic ESS receive burst size
  * @tx_thr_num_pkt_prd: periodic ESS transmit packet count
@@ -1106,6 +1117,8 @@ struct dwc3_scratchpad_array {
  *                     instances in park mode.
  * @parkmode_disable_hs_quirk: set if we need to disable all HishSpeed
  *                     instances in park mode.
+ * @gfladj_refclk_lpm_sel: set if we need to enable SOF/ITP counter
+ *                          running based on ref_clk
  * @tx_de_emphasis_quirk: set if we enable Tx de-emphasis quirk
  * @tx_de_emphasis: Tx de-emphasis value
  *     0       - -6dB de-emphasis
@@ -1156,6 +1169,8 @@ struct dwc3 {
        struct clk              *bus_clk;
        struct clk              *ref_clk;
        struct clk              *susp_clk;
+       struct clk              *utmi_clk;
+       struct clk              *pipe_clk;
 
        struct reset_control    *reset;
 
@@ -1273,6 +1288,10 @@ struct dwc3 {
        u8                      test_mode_nr;
        u8                      lpm_nyet_threshold;
        u8                      hird_threshold;
+       u8                      rx_thr_num_pkt;
+       u8                      rx_max_burst;
+       u8                      tx_thr_num_pkt;
+       u8                      tx_max_burst;
        u8                      rx_thr_num_pkt_prd;
        u8                      rx_max_burst_prd;
        u8                      tx_thr_num_pkt_prd;
diff --git a/drivers/usb/dwc3/dwc3-rtk.c b/drivers/usb/dwc3/dwc3-rtk.c
new file mode 100644 (file)
index 0000000..590028e
--- /dev/null
@@ -0,0 +1,475 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * dwc3-rtk.c - Realtek DWC3 Specific Glue layer
+ *
+ * Copyright (C) 2023 Realtek Semiconductor Corporation
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/suspend.h>
+#include <linux/sys_soc.h>
+#include <linux/usb/otg.h>
+#include <linux/usb/of.h>
+#include <linux/usb/role.h>
+
+#include "core.h"
+
+#define WRAP_CTR_REG  0x0
+#define DISABLE_MULTI_REQ BIT(1)
+#define DESC_R2W_MULTI_DISABLE BIT(9)
+#define FORCE_PIPE3_PHY_STATUS_TO_0 BIT(13)
+
+#define WRAP_USB2_PHY_UTMI_REG 0x8
+#define TXHSVM_EN BIT(3)
+
+#define WRAP_PHY_PIPE_REG 0xC
+#define RESET_DISABLE_PIPE3_P0 BIT(0)
+#define CLOCK_ENABLE_FOR_PIPE3_PCLK BIT(1)
+
+#define WRAP_USB_HMAC_CTR0_REG 0x60
+#define U3PORT_DIS BIT(8)
+
+#define WRAP_USB2_PHY_REG  0x70
+#define USB2_PHY_EN_PHY_PLL_PORT0 BIT(12)
+#define USB2_PHY_EN_PHY_PLL_PORT1 BIT(13)
+#define USB2_PHY_SWITCH_MASK 0x707
+#define USB2_PHY_SWITCH_DEVICE 0x0
+#define USB2_PHY_SWITCH_HOST 0x606
+
+#define WRAP_APHY_REG 0x128
+#define USB3_MBIAS_ENABLE BIT(1)
+
+/* pm control */
+#define WRAP_USB_DBUS_PWR_CTRL_REG 0x160
+#define USB_DBUS_PWR_CTRL_REG 0x0
+#define DBUS_PWR_CTRL_EN BIT(0)
+
+struct dwc3_rtk {
+       struct device *dev;
+       void __iomem *regs;
+       size_t regs_size;
+       void __iomem *pm_base;
+
+       struct dwc3 *dwc;
+
+       enum usb_role cur_role;
+       struct usb_role_switch *role_switch;
+};
+
+static void switch_usb2_role(struct dwc3_rtk *rtk, enum usb_role role)
+{
+       void __iomem *reg;
+       int val;
+
+       reg = rtk->regs + WRAP_USB2_PHY_REG;
+       val = ~USB2_PHY_SWITCH_MASK & readl(reg);
+
+       switch (role) {
+       case USB_ROLE_DEVICE:
+               writel(USB2_PHY_SWITCH_DEVICE | val, reg);
+               break;
+       case USB_ROLE_HOST:
+               writel(USB2_PHY_SWITCH_HOST | val, reg);
+               break;
+       default:
+               dev_dbg(rtk->dev, "%s: role=%d\n", __func__, role);
+               break;
+       }
+}
+
+static void switch_dwc3_role(struct dwc3_rtk *rtk, enum usb_role role)
+{
+       if (!rtk->dwc->role_sw)
+               return;
+
+       usb_role_switch_set_role(rtk->dwc->role_sw, role);
+}
+
+static enum usb_role dwc3_rtk_get_role(struct dwc3_rtk *rtk)
+{
+       enum usb_role role;
+
+       role = rtk->cur_role;
+
+       if (rtk->dwc && rtk->dwc->role_sw)
+               role = usb_role_switch_get_role(rtk->dwc->role_sw);
+       else
+               dev_dbg(rtk->dev, "%s not usb_role_switch role=%d\n", __func__, role);
+
+       return role;
+}
+
+static void dwc3_rtk_set_role(struct dwc3_rtk *rtk, enum usb_role role)
+{
+       rtk->cur_role = role;
+
+       switch_dwc3_role(rtk, role);
+       mdelay(10);
+       switch_usb2_role(rtk, role);
+}
+
+#if IS_ENABLED(CONFIG_USB_ROLE_SWITCH)
+static int dwc3_usb_role_switch_set(struct usb_role_switch *sw, enum usb_role role)
+{
+       struct dwc3_rtk *rtk = usb_role_switch_get_drvdata(sw);
+
+       dwc3_rtk_set_role(rtk, role);
+
+       return 0;
+}
+
+static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw)
+{
+       struct dwc3_rtk *rtk = usb_role_switch_get_drvdata(sw);
+
+       return dwc3_rtk_get_role(rtk);
+}
+
+static int dwc3_rtk_setup_role_switch(struct dwc3_rtk *rtk)
+{
+       struct usb_role_switch_desc dwc3_role_switch = {NULL};
+
+       dwc3_role_switch.name = dev_name(rtk->dev);
+       dwc3_role_switch.driver_data = rtk;
+       dwc3_role_switch.allow_userspace_control = true;
+       dwc3_role_switch.fwnode = dev_fwnode(rtk->dev);
+       dwc3_role_switch.set = dwc3_usb_role_switch_set;
+       dwc3_role_switch.get = dwc3_usb_role_switch_get;
+       rtk->role_switch = usb_role_switch_register(rtk->dev, &dwc3_role_switch);
+       if (IS_ERR(rtk->role_switch))
+               return PTR_ERR(rtk->role_switch);
+
+       return 0;
+}
+
+static int dwc3_rtk_remove_role_switch(struct dwc3_rtk *rtk)
+{
+       if (rtk->role_switch)
+               usb_role_switch_unregister(rtk->role_switch);
+
+       rtk->role_switch = NULL;
+
+       return 0;
+}
+#else
+#define dwc3_rtk_setup_role_switch(x) 0
+#define dwc3_rtk_remove_role_switch(x) 0
+#endif
+
+static const char *const speed_names[] = {
+       [USB_SPEED_UNKNOWN] = "UNKNOWN",
+       [USB_SPEED_LOW] = "low-speed",
+       [USB_SPEED_FULL] = "full-speed",
+       [USB_SPEED_HIGH] = "high-speed",
+       [USB_SPEED_WIRELESS] = "wireless",
+       [USB_SPEED_SUPER] = "super-speed",
+       [USB_SPEED_SUPER_PLUS] = "super-speed-plus",
+};
+
+static enum usb_device_speed __get_dwc3_maximum_speed(struct device_node *np)
+{
+       struct device_node *dwc3_np;
+       const char *maximum_speed;
+       int ret;
+
+       dwc3_np = of_get_compatible_child(np, "snps,dwc3");
+       if (!dwc3_np)
+               return USB_SPEED_UNKNOWN;
+
+       ret = of_property_read_string(dwc3_np, "maximum-speed", &maximum_speed);
+       if (ret < 0)
+               return USB_SPEED_UNKNOWN;
+
+       ret = match_string(speed_names, ARRAY_SIZE(speed_names), maximum_speed);
+
+       return (ret < 0) ? USB_SPEED_UNKNOWN : ret;
+}
+
+static int dwc3_rtk_init(struct dwc3_rtk *rtk)
+{
+       struct device *dev = rtk->dev;
+       void __iomem *reg;
+       int val;
+       enum usb_device_speed maximum_speed;
+       const struct soc_device_attribute rtk_soc_kylin_a00[] = {
+               { .family = "Realtek Kylin", .revision = "A00", },
+               { /* empty */ } };
+       const struct soc_device_attribute rtk_soc_hercules[] = {
+               { .family = "Realtek Hercules", }, { /* empty */ } };
+       const struct soc_device_attribute rtk_soc_thor[] = {
+               { .family = "Realtek Thor", }, { /* empty */ } };
+
+       if (soc_device_match(rtk_soc_kylin_a00)) {
+               reg = rtk->regs + WRAP_CTR_REG;
+               val = readl(reg);
+               writel(DISABLE_MULTI_REQ | val, reg);
+               dev_info(dev, "[bug fixed] 1295/1296 A00: add workaround to disable multiple request for D-Bus");
+       }
+
+       if (soc_device_match(rtk_soc_hercules)) {
+               reg = rtk->regs + WRAP_USB2_PHY_REG;
+               val = readl(reg);
+               writel(USB2_PHY_EN_PHY_PLL_PORT1 | val, reg);
+               dev_info(dev, "[bug fixed] 1395 add workaround to disable usb2 port 2 suspend!");
+       }
+
+       reg = rtk->regs + WRAP_USB2_PHY_UTMI_REG;
+       val = readl(reg);
+       writel(TXHSVM_EN | val, reg);
+
+       maximum_speed = __get_dwc3_maximum_speed(dev->of_node);
+       if (maximum_speed != USB_SPEED_UNKNOWN && maximum_speed <= USB_SPEED_HIGH) {
+               if (soc_device_match(rtk_soc_thor)) {
+                       reg = rtk->regs + WRAP_USB_HMAC_CTR0_REG;
+                       val = readl(reg);
+                       writel(U3PORT_DIS | val, reg);
+               } else {
+                       reg = rtk->regs + WRAP_CTR_REG;
+                       val = readl(reg);
+                       writel(FORCE_PIPE3_PHY_STATUS_TO_0 | val, reg);
+
+                       reg = rtk->regs + WRAP_PHY_PIPE_REG;
+                       val = ~CLOCK_ENABLE_FOR_PIPE3_PCLK & readl(reg);
+                       writel(RESET_DISABLE_PIPE3_P0 | val, reg);
+
+                       reg =  rtk->regs + WRAP_USB_HMAC_CTR0_REG;
+                       val = readl(reg);
+                       writel(U3PORT_DIS | val, reg);
+
+                       reg = rtk->regs + WRAP_APHY_REG;
+                       val = readl(reg);
+                       writel(~USB3_MBIAS_ENABLE & val, reg);
+
+                       dev_dbg(rtk->dev, "%s: disable usb 3.0 phy\n", __func__);
+               }
+       }
+
+       reg = rtk->regs + WRAP_CTR_REG;
+       val = readl(reg);
+       writel(DESC_R2W_MULTI_DISABLE | val, reg);
+
+       /* Set phy Dp/Dm initial state to host mode to avoid the Dp glitch */
+       reg = rtk->regs + WRAP_USB2_PHY_REG;
+       val = ~USB2_PHY_SWITCH_MASK & readl(reg);
+       writel(USB2_PHY_SWITCH_HOST | val, reg);
+
+       if (rtk->pm_base) {
+               reg = rtk->pm_base + USB_DBUS_PWR_CTRL_REG;
+               val = DBUS_PWR_CTRL_EN | readl(reg);
+               writel(val, reg);
+       }
+
+       return 0;
+}
+
+static int dwc3_rtk_probe_dwc3_core(struct dwc3_rtk *rtk)
+{
+       struct device *dev = rtk->dev;
+       struct device_node *node = dev->of_node;
+       struct platform_device *dwc3_pdev;
+       struct device *dwc3_dev;
+       struct device_node *dwc3_node;
+       enum usb_dr_mode dr_mode;
+       int ret = 0;
+
+       ret = dwc3_rtk_init(rtk);
+       if (ret)
+               return -EINVAL;
+
+       ret = of_platform_populate(node, NULL, NULL, dev);
+       if (ret) {
+               dev_err(dev, "failed to add dwc3 core\n");
+               return ret;
+       }
+
+       dwc3_node = of_get_compatible_child(node, "snps,dwc3");
+       if (!dwc3_node) {
+               dev_err(dev, "failed to find dwc3 core node\n");
+               ret = -ENODEV;
+               goto depopulate;
+       }
+
+       dwc3_pdev = of_find_device_by_node(dwc3_node);
+       if (!dwc3_pdev) {
+               dev_err(dev, "failed to find dwc3 core platform_device\n");
+               ret = -ENODEV;
+               goto err_node_put;
+       }
+
+       dwc3_dev = &dwc3_pdev->dev;
+       rtk->dwc = platform_get_drvdata(dwc3_pdev);
+       if (!rtk->dwc) {
+               dev_err(dev, "failed to find dwc3 core\n");
+               ret = -ENODEV;
+               goto err_pdev_put;
+       }
+
+       dr_mode = usb_get_dr_mode(dwc3_dev);
+       if (dr_mode != rtk->dwc->dr_mode) {
+               dev_info(dev, "dts set dr_mode=%d, but dwc3 set dr_mode=%d\n",
+                        dr_mode, rtk->dwc->dr_mode);
+               dr_mode = rtk->dwc->dr_mode;
+       }
+
+       switch (dr_mode) {
+       case USB_DR_MODE_PERIPHERAL:
+               rtk->cur_role = USB_ROLE_DEVICE;
+               break;
+       case USB_DR_MODE_HOST:
+               rtk->cur_role = USB_ROLE_HOST;
+               break;
+       default:
+               dev_dbg(rtk->dev, "%s: dr_mode=%d\n", __func__, dr_mode);
+               break;
+       }
+
+       if (device_property_read_bool(dwc3_dev, "usb-role-switch")) {
+               ret = dwc3_rtk_setup_role_switch(rtk);
+               if (ret) {
+                       dev_err(dev, "dwc3_rtk_setup_role_switch fail=%d\n", ret);
+                       goto err_pdev_put;
+               }
+               rtk->cur_role = dwc3_rtk_get_role(rtk);
+       }
+
+       switch_usb2_role(rtk, rtk->cur_role);
+
+       return 0;
+
+err_pdev_put:
+       platform_device_put(dwc3_pdev);
+err_node_put:
+       of_node_put(dwc3_node);
+depopulate:
+       of_platform_depopulate(dev);
+
+       return ret;
+}
+
+static int dwc3_rtk_probe(struct platform_device *pdev)
+{
+       struct dwc3_rtk *rtk;
+       struct device *dev = &pdev->dev;
+       struct resource *res;
+       void __iomem *regs;
+       int ret = 0;
+
+       rtk = devm_kzalloc(dev, sizeof(*rtk), GFP_KERNEL);
+       if (!rtk) {
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       platform_set_drvdata(pdev, rtk);
+
+       rtk->dev = dev;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!res) {
+               dev_err(dev, "missing memory resource\n");
+               ret = -ENODEV;
+               goto out;
+       }
+
+       regs = devm_ioremap_resource(dev, res);
+       if (IS_ERR(regs)) {
+               ret = PTR_ERR(regs);
+               goto out;
+       }
+
+       rtk->regs = regs;
+       rtk->regs_size = resource_size(res);
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+       if (res) {
+               rtk->pm_base = devm_ioremap_resource(dev, res);
+               if (IS_ERR(rtk->pm_base)) {
+                       ret = PTR_ERR(rtk->pm_base);
+                       goto out;
+               }
+       }
+
+       ret = dwc3_rtk_probe_dwc3_core(rtk);
+
+out:
+       return ret;
+}
+
+static void dwc3_rtk_remove(struct platform_device *pdev)
+{
+       struct dwc3_rtk *rtk = platform_get_drvdata(pdev);
+
+       rtk->dwc = NULL;
+
+       dwc3_rtk_remove_role_switch(rtk);
+
+       of_platform_depopulate(rtk->dev);
+}
+
+static void dwc3_rtk_shutdown(struct platform_device *pdev)
+{
+       struct dwc3_rtk *rtk = platform_get_drvdata(pdev);
+
+       of_platform_depopulate(rtk->dev);
+}
+
+static const struct of_device_id rtk_dwc3_match[] = {
+       { .compatible = "realtek,rtd-dwc3" },
+       {},
+};
+MODULE_DEVICE_TABLE(of, rtk_dwc3_match);
+
+#ifdef CONFIG_PM_SLEEP
+static int dwc3_rtk_suspend(struct device *dev)
+{
+       return 0;
+}
+
+static int dwc3_rtk_resume(struct device *dev)
+{
+       struct dwc3_rtk *rtk = dev_get_drvdata(dev);
+
+       dwc3_rtk_init(rtk);
+
+       switch_usb2_role(rtk, rtk->cur_role);
+
+       /* runtime set active to reflect active state. */
+       pm_runtime_disable(dev);
+       pm_runtime_set_active(dev);
+       pm_runtime_enable(dev);
+
+       return 0;
+}
+
+static const struct dev_pm_ops dwc3_rtk_dev_pm_ops = {
+       SET_SYSTEM_SLEEP_PM_OPS(dwc3_rtk_suspend, dwc3_rtk_resume)
+};
+
+#define DEV_PM_OPS     (&dwc3_rtk_dev_pm_ops)
+#else
+#define DEV_PM_OPS     NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static struct platform_driver dwc3_rtk_driver = {
+       .probe          = dwc3_rtk_probe,
+       .remove_new     = dwc3_rtk_remove,
+       .driver         = {
+               .name   = "rtk-dwc3",
+               .of_match_table = rtk_dwc3_match,
+               .pm     = DEV_PM_OPS,
+       },
+       .shutdown       = dwc3_rtk_shutdown,
+};
+
+module_platform_driver(dwc3_rtk_driver);
+
+MODULE_AUTHOR("Stanley Chang <stanley_chang@realtek.com>");
+MODULE_DESCRIPTION("DesignWare USB3 Realtek Glue Layer");
+MODULE_ALIAS("platform:rtk-dwc3");
+MODULE_LICENSE("GPL");
+MODULE_SOFTDEP("pre: phy_rtk_usb2 phy_rtk_usb3");
index 19307d24f3a06b75f20532522d42fed6ea350cea..5b7e92f476de928a6fe38e4494089b8c521b143d 100644 (file)
@@ -32,9 +32,6 @@
 #define XLNX_USB_TRAFFIC_ROUTE_CONFIG          0x005C
 #define XLNX_USB_TRAFFIC_ROUTE_FPD             0x1
 
-/* Versal USB Reset ID */
-#define VERSAL_USB_RESET_ID                    0xC104036
-
 #define XLNX_USB_FPD_PIPE_CLK                  0x7c
 #define PIPE_CLK_DESELECT                      1
 #define PIPE_CLK_SELECT                                0
@@ -72,20 +69,23 @@ static void dwc3_xlnx_mask_phy_rst(struct dwc3_xlnx *priv_data, bool mask)
 static int dwc3_xlnx_init_versal(struct dwc3_xlnx *priv_data)
 {
        struct device           *dev = priv_data->dev;
+       struct reset_control    *crst;
        int                     ret;
 
+       crst = devm_reset_control_get_exclusive(dev, NULL);
+       if (IS_ERR(crst))
+               return dev_err_probe(dev, PTR_ERR(crst), "failed to get reset signal\n");
+
        dwc3_xlnx_mask_phy_rst(priv_data, false);
 
        /* Assert and De-assert reset */
-       ret = zynqmp_pm_reset_assert(VERSAL_USB_RESET_ID,
-                                    PM_RESET_ACTION_ASSERT);
+       ret = reset_control_assert(crst);
        if (ret < 0) {
                dev_err_probe(dev, ret, "failed to assert Reset\n");
                return ret;
        }
 
-       ret = zynqmp_pm_reset_assert(VERSAL_USB_RESET_ID,
-                                    PM_RESET_ACTION_RELEASE);
+       ret = reset_control_deassert(crst);
        if (ret < 0) {
                dev_err_probe(dev, ret, "failed to De-assert Reset\n");
                return ret;
index e6ab8cc225ffdc8cac3c9ba115afd2fc78d77d92..cc0ed29a4adc07b62fa412802c2b42495e5c22d8 100644 (file)
@@ -1410,7 +1410,7 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f)
        struct usb_composite_dev *cdev = c->cdev;
        struct f_ncm            *ncm = func_to_ncm(f);
        struct usb_string       *us;
-       int                     status;
+       int                     status = 0;
        struct usb_ep           *ep;
        struct f_ncm_opts       *ncm_opts;
 
@@ -1428,22 +1428,17 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f)
                f->os_desc_table[0].os_desc = &ncm_opts->ncm_os_desc;
        }
 
-       /*
-        * in drivers/usb/gadget/configfs.c:configfs_composite_bind()
-        * configurations are bound in sequence with list_for_each_entry,
-        * in each configuration its functions are bound in sequence
-        * with list_for_each_entry, so we assume no race condition
-        * with regard to ncm_opts->bound access
-        */
-       if (!ncm_opts->bound) {
-               mutex_lock(&ncm_opts->lock);
-               gether_set_gadget(ncm_opts->net, cdev->gadget);
+       mutex_lock(&ncm_opts->lock);
+       gether_set_gadget(ncm_opts->net, cdev->gadget);
+       if (!ncm_opts->bound)
                status = gether_register_netdev(ncm_opts->net);
-               mutex_unlock(&ncm_opts->lock);
-               if (status)
-                       goto fail;
-               ncm_opts->bound = true;
-       }
+       mutex_unlock(&ncm_opts->lock);
+
+       if (status)
+               goto fail;
+
+       ncm_opts->bound = true;
+
        us = usb_gstrings_attach(cdev, ncm_strings,
                                 ARRAY_SIZE(ncm_string_defs));
        if (IS_ERR(us)) {
index 0219cd79493a73714b95f5c1156f5ff9c6216f77..f9a0f07a7476be709adf2736f1ff72ca8a1e2257 100644 (file)
@@ -212,7 +212,7 @@ static struct uac2_input_terminal_descriptor io_in_it_desc = {
 
        .bDescriptorSubtype = UAC_INPUT_TERMINAL,
        /* .bTerminalID = DYNAMIC */
-       .wTerminalType = cpu_to_le16(UAC_INPUT_TERMINAL_MICROPHONE),
+       /* .wTerminalType = DYNAMIC */
        .bAssocTerminal = 0,
        /* .bCSourceID = DYNAMIC */
        .iChannelNames = 0,
@@ -240,7 +240,7 @@ static struct uac2_output_terminal_descriptor io_out_ot_desc = {
 
        .bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
        /* .bTerminalID = DYNAMIC */
-       .wTerminalType = cpu_to_le16(UAC_OUTPUT_TERMINAL_SPEAKER),
+       /* .wTerminalType = DYNAMIC */
        .bAssocTerminal = 0,
        /* .bSourceID = DYNAMIC */
        /* .bCSourceID = DYNAMIC */
@@ -977,6 +977,9 @@ static void setup_descriptor(struct f_uac2_opts *opts)
                iad_desc.bInterfaceCount++;
        }
 
+       io_in_it_desc.wTerminalType = cpu_to_le16(opts->c_terminal_type);
+       io_out_ot_desc.wTerminalType = cpu_to_le16(opts->p_terminal_type);
+
        setup_headers(opts, fs_audio_desc, USB_SPEED_FULL);
        setup_headers(opts, hs_audio_desc, USB_SPEED_HIGH);
        setup_headers(opts, ss_audio_desc, USB_SPEED_SUPER);
@@ -2095,6 +2098,9 @@ UAC2_ATTRIBUTE(s16, c_volume_res);
 UAC2_ATTRIBUTE(u32, fb_max);
 UAC2_ATTRIBUTE_STRING(function_name);
 
+UAC2_ATTRIBUTE(s16, p_terminal_type);
+UAC2_ATTRIBUTE(s16, c_terminal_type);
+
 static struct configfs_attribute *f_uac2_attrs[] = {
        &f_uac2_opts_attr_p_chmask,
        &f_uac2_opts_attr_p_srate,
@@ -2122,6 +2128,9 @@ static struct configfs_attribute *f_uac2_attrs[] = {
 
        &f_uac2_opts_attr_function_name,
 
+       &f_uac2_opts_attr_p_terminal_type,
+       &f_uac2_opts_attr_c_terminal_type,
+
        NULL,
 };
 
@@ -2180,6 +2189,9 @@ static struct usb_function_instance *afunc_alloc_inst(void)
 
        snprintf(opts->function_name, sizeof(opts->function_name), "Source/Sink");
 
+       opts->p_terminal_type = UAC2_DEF_P_TERM_TYPE;
+       opts->c_terminal_type = UAC2_DEF_C_TERM_TYPE;
+
        return &opts->func_inst;
 }
 
index faa398109431fca37fbd5e5309fe4e58aa75878e..786379f1b7b72457a6bc9e303e47b7f62c6eecae 100644 (file)
@@ -516,6 +516,7 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed)
        void *mem;
 
        switch (speed) {
+       case USB_SPEED_SUPER_PLUS:
        case USB_SPEED_SUPER:
                uvc_control_desc = uvc->desc.ss_control;
                uvc_streaming_cls = uvc->desc.ss_streaming;
@@ -564,7 +565,8 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed)
                bytes += uvc_interrupt_ep.bLength + uvc_interrupt_cs_ep.bLength;
                n_desc += 2;
 
-               if (speed == USB_SPEED_SUPER) {
+               if (speed == USB_SPEED_SUPER ||
+                   speed == USB_SPEED_SUPER_PLUS) {
                        bytes += uvc_ss_interrupt_comp.bLength;
                        n_desc += 1;
                }
@@ -619,7 +621,8 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed)
 
        if (uvc->enable_interrupt_ep) {
                UVC_COPY_DESCRIPTOR(mem, dst, &uvc_interrupt_ep);
-               if (speed == USB_SPEED_SUPER)
+               if (speed == USB_SPEED_SUPER ||
+                   speed == USB_SPEED_SUPER_PLUS)
                        UVC_COPY_DESCRIPTOR(mem, dst, &uvc_ss_interrupt_comp);
 
                UVC_COPY_DESCRIPTOR(mem, dst, &uvc_interrupt_cs_ep);
@@ -795,6 +798,13 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f)
                goto error;
        }
 
+       f->ssp_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_SUPER_PLUS);
+       if (IS_ERR(f->ssp_descriptors)) {
+               ret = PTR_ERR(f->ssp_descriptors);
+               f->ssp_descriptors = NULL;
+               goto error;
+       }
+
        /* Preallocate control endpoint request. */
        uvc->control_req = usb_ep_alloc_request(cdev->gadget->ep0, GFP_KERNEL);
        uvc->control_buf = kmalloc(UVC_MAX_REQUEST_SIZE, GFP_KERNEL);
index 4bb0553da6585761110c9fb8107241a0aeb8522a..9d1c40c152d86448a63017575931553240b1bd7c 100644 (file)
@@ -1200,7 +1200,7 @@ void gether_disconnect(struct gether *link)
 
        DBG(dev, "%s\n", __func__);
 
-       netif_stop_queue(dev->net);
+       netif_device_detach(dev->net);
        netif_carrier_off(dev->net);
 
        /* disable endpoints, forcing (synchronous) completion
index 0510c9bad58dbd8851eca814eca76a071b27e775..5e81bdd6c5fbab1075704cb4cb0cdfe4d58c86b2 100644 (file)
 #define UAC2_DEF_REQ_NUM 2
 #define UAC2_DEF_INT_REQ_NUM   10
 
+#define UAC2_DEF_P_TERM_TYPE 0x301
+       /* UAC_OUTPUT_TERMINAL_SPEAKER */
+#define UAC2_DEF_C_TERM_TYPE 0x201
+       /* UAC_INPUT_TERMINAL_MICROPHONE*/
+
 struct f_uac2_opts {
        struct usb_function_instance    func_inst;
        int                             p_chmask;
@@ -65,6 +70,9 @@ struct f_uac2_opts {
 
        char                    function_name[32];
 
+       s16                             p_terminal_type;
+       s16                             c_terminal_type;
+
        struct mutex                    lock;
        int                             refcnt;
 };
index cdc0926100fd8d9dad05ca659713ad505fe46317..03179b1880fd3e86fea528526c3cfc6ff3b4e67b 100644 (file)
 
 #include <linux/usb/gadgetfs.h>
 #include <linux/usb/gadget.h>
+#include <linux/usb/composite.h> /* for USB_GADGET_DELAYED_STATUS */
+
+/* Undef helpers from linux/usb/composite.h as gadgetfs redefines them */
+#undef DBG
+#undef ERROR
+#undef INFO
 
 
 /*
@@ -1511,7 +1517,16 @@ delegate:
                        event->u.setup = *ctrl;
                        ep0_readable (dev);
                        spin_unlock (&dev->lock);
-                       return 0;
+                       /*
+                        * Return USB_GADGET_DELAYED_STATUS as a workaround to
+                        * stop some UDC drivers (e.g. dwc3) from automatically
+                        * proceeding with the status stage for 0-length
+                        * transfers.
+                        * Should be removed once all UDC drivers are fixed to
+                        * always delay the status stage until a response is
+                        * queued to EP0.
+                        */
+                       return w_length == 0 ? USB_GADGET_DELAYED_STATUS : 0;
                }
        }
 
index e549022642e569e0b505800eab1417d9bca0472a..399fca32a8ace7e1c2962408ee2fcb74024acde9 100644 (file)
@@ -25,6 +25,7 @@
 #include <linux/usb/ch9.h>
 #include <linux/usb/ch11.h>
 #include <linux/usb/gadget.h>
+#include <linux/usb/composite.h>
 
 #include <uapi/linux/usb/raw_gadget.h>
 
@@ -64,7 +65,7 @@ static int raw_event_queue_add(struct raw_event_queue *queue,
        struct usb_raw_event *event;
 
        spin_lock_irqsave(&queue->lock, flags);
-       if (WARN_ON(queue->size >= RAW_EVENT_QUEUE_SIZE)) {
+       if (queue->size >= RAW_EVENT_QUEUE_SIZE) {
                spin_unlock_irqrestore(&queue->lock, flags);
                return -ENOMEM;
        }
@@ -310,9 +311,10 @@ static int gadget_bind(struct usb_gadget *gadget,
        dev->eps_num = i;
        spin_unlock_irqrestore(&dev->lock, flags);
 
+       dev_dbg(&gadget->dev, "gadget connected\n");
        ret = raw_queue_event(dev, USB_RAW_EVENT_CONNECT, 0, NULL);
        if (ret < 0) {
-               dev_err(&gadget->dev, "failed to queue event\n");
+               dev_err(&gadget->dev, "failed to queue connect event\n");
                set_gadget_data(gadget, NULL);
                return ret;
        }
@@ -357,20 +359,65 @@ static int gadget_setup(struct usb_gadget *gadget,
 
        ret = raw_queue_event(dev, USB_RAW_EVENT_CONTROL, sizeof(*ctrl), ctrl);
        if (ret < 0)
-               dev_err(&gadget->dev, "failed to queue event\n");
+               dev_err(&gadget->dev, "failed to queue control event\n");
        goto out;
 
 out_unlock:
        spin_unlock_irqrestore(&dev->lock, flags);
 out:
+       if (ret == 0 && ctrl->wLength == 0) {
+               /*
+                * Return USB_GADGET_DELAYED_STATUS as a workaround to stop
+                * some UDC drivers (e.g. dwc3) from automatically proceeding
+                * with the status stage for 0-length transfers.
+                * Should be removed once all UDC drivers are fixed to always
+                * delay the status stage until a response is queued to EP0.
+                */
+               return USB_GADGET_DELAYED_STATUS;
+       }
        return ret;
 }
 
-/* These are currently unused but present in case UDC driver requires them. */
-static void gadget_disconnect(struct usb_gadget *gadget) { }
-static void gadget_suspend(struct usb_gadget *gadget) { }
-static void gadget_resume(struct usb_gadget *gadget) { }
-static void gadget_reset(struct usb_gadget *gadget) { }
+static void gadget_disconnect(struct usb_gadget *gadget)
+{
+       struct raw_dev *dev = get_gadget_data(gadget);
+       int ret;
+
+       dev_dbg(&gadget->dev, "gadget disconnected\n");
+       ret = raw_queue_event(dev, USB_RAW_EVENT_DISCONNECT, 0, NULL);
+       if (ret < 0)
+               dev_err(&gadget->dev, "failed to queue disconnect event\n");
+}
+static void gadget_suspend(struct usb_gadget *gadget)
+{
+       struct raw_dev *dev = get_gadget_data(gadget);
+       int ret;
+
+       dev_dbg(&gadget->dev, "gadget suspended\n");
+       ret = raw_queue_event(dev, USB_RAW_EVENT_SUSPEND, 0, NULL);
+       if (ret < 0)
+               dev_err(&gadget->dev, "failed to queue suspend event\n");
+}
+static void gadget_resume(struct usb_gadget *gadget)
+{
+       struct raw_dev *dev = get_gadget_data(gadget);
+       int ret;
+
+       dev_dbg(&gadget->dev, "gadget resumed\n");
+       ret = raw_queue_event(dev, USB_RAW_EVENT_RESUME, 0, NULL);
+       if (ret < 0)
+               dev_err(&gadget->dev, "failed to queue resume event\n");
+}
+static void gadget_reset(struct usb_gadget *gadget)
+{
+       struct raw_dev *dev = get_gadget_data(gadget);
+       int ret;
+
+       dev_dbg(&gadget->dev, "gadget reset\n");
+       ret = raw_queue_event(dev, USB_RAW_EVENT_RESET, 0, NULL);
+       if (ret < 0)
+               dev_err(&gadget->dev, "failed to queue reset event\n");
+}
 
 /*----------------------------------------------------------------------*/
 
@@ -663,12 +710,12 @@ static int raw_process_ep0_io(struct raw_dev *dev, struct usb_raw_ep_io *io,
        if (WARN_ON(in && dev->ep0_out_pending)) {
                ret = -ENODEV;
                dev->state = STATE_DEV_FAILED;
-               goto out_done;
+               goto out_unlock;
        }
        if (WARN_ON(!in && dev->ep0_in_pending)) {
                ret = -ENODEV;
                dev->state = STATE_DEV_FAILED;
-               goto out_done;
+               goto out_unlock;
        }
 
        dev->req->buf = data;
@@ -682,8 +729,7 @@ static int raw_process_ep0_io(struct raw_dev *dev, struct usb_raw_ep_io *io,
                dev_err(&dev->gadget->dev,
                                "fail, usb_ep_queue returned %d\n", ret);
                spin_lock_irqsave(&dev->lock, flags);
-               dev->state = STATE_DEV_FAILED;
-               goto out_done;
+               goto out_queue_failed;
        }
 
        ret = wait_for_completion_interruptible(&dev->ep0_done);
@@ -692,13 +738,16 @@ static int raw_process_ep0_io(struct raw_dev *dev, struct usb_raw_ep_io *io,
                usb_ep_dequeue(dev->gadget->ep0, dev->req);
                wait_for_completion(&dev->ep0_done);
                spin_lock_irqsave(&dev->lock, flags);
-               goto out_done;
+               if (dev->ep0_status == -ECONNRESET)
+                       dev->ep0_status = -EINTR;
+               goto out_interrupted;
        }
 
        spin_lock_irqsave(&dev->lock, flags);
-       ret = dev->ep0_status;
 
-out_done:
+out_interrupted:
+       ret = dev->ep0_status;
+out_queue_failed:
        dev->ep0_urb_queued = false;
 out_unlock:
        spin_unlock_irqrestore(&dev->lock, flags);
@@ -1066,8 +1115,7 @@ static int raw_process_ep_io(struct raw_dev *dev, struct usb_raw_ep_io *io,
                dev_err(&dev->gadget->dev,
                                "fail, usb_ep_queue returned %d\n", ret);
                spin_lock_irqsave(&dev->lock, flags);
-               dev->state = STATE_DEV_FAILED;
-               goto out_done;
+               goto out_queue_failed;
        }
 
        ret = wait_for_completion_interruptible(&done);
@@ -1076,13 +1124,16 @@ static int raw_process_ep_io(struct raw_dev *dev, struct usb_raw_ep_io *io,
                usb_ep_dequeue(ep->ep, ep->req);
                wait_for_completion(&done);
                spin_lock_irqsave(&dev->lock, flags);
-               goto out_done;
+               if (ep->status == -ECONNRESET)
+                       ep->status = -EINTR;
+               goto out_interrupted;
        }
 
        spin_lock_irqsave(&dev->lock, flags);
-       ret = ep->status;
 
-out_done:
+out_interrupted:
+       ret = ep->status;
+out_queue_failed:
        ep->urb_queued = false;
 out_unlock:
        spin_unlock_irqrestore(&dev->lock, flags);
index 2ef89a442f50f129fc0acb55fca1334f3d16ed13..3916c8e2ba01f18c6afbcbda4ab20898699fd6dc 100644 (file)
@@ -1432,15 +1432,24 @@ static void ast_udc_init_hw(struct ast_udc_dev *udc)
        ast_udc_write(udc, 0, AST_UDC_EP0_CTRL);
 }
 
-static int ast_udc_remove(struct platform_device *pdev)
+static void ast_udc_remove(struct platform_device *pdev)
 {
        struct ast_udc_dev *udc = platform_get_drvdata(pdev);
        unsigned long flags;
        u32 ctrl;
 
        usb_del_gadget_udc(&udc->gadget);
-       if (udc->driver)
-               return -EBUSY;
+       if (udc->driver) {
+               /*
+                * This is broken as only some cleanup is skipped, *udev is
+                * freed and the register mapping goes away. Any further usage
+                * probably crashes. Also the device is unbound, so the skipped
+                * cleanup is never catched up later.
+                */
+               dev_alert(&pdev->dev,
+                         "Driver is busy and still going away. Fasten your seat belts!\n");
+               return;
+       }
 
        spin_lock_irqsave(&udc->lock, flags);
 
@@ -1459,8 +1468,6 @@ static int ast_udc_remove(struct platform_device *pdev)
                                  udc->ep0_buf_dma);
 
        udc->ep0_buf = NULL;
-
-       return 0;
 }
 
 static int ast_udc_probe(struct platform_device *pdev)
@@ -1581,7 +1588,7 @@ MODULE_DEVICE_TABLE(of, ast_udc_of_dt_ids);
 
 static struct platform_driver ast_udc_driver = {
        .probe                  = ast_udc_probe,
-       .remove                 = ast_udc_remove,
+       .remove_new             = ast_udc_remove,
        .driver                 = {
                .name                   = KBUILD_MODNAME,
                .of_match_table         = ast_udc_of_dt_ids,
index 922b4187004b03f93eb3ee4d4c4c2c29e3d31431..30ea4a9d5301fca5864d37f485753babc06eb0ad 100644 (file)
@@ -2000,6 +2000,7 @@ static int at91udc_resume(struct platform_device *pdev)
 #endif
 
 static struct platform_driver at91_udc_driver = {
+       .probe          = at91udc_probe,
        .remove         = at91udc_remove,
        .shutdown       = at91udc_shutdown,
        .suspend        = at91udc_suspend,
@@ -2010,7 +2011,7 @@ static struct platform_driver at91_udc_driver = {
        },
 };
 
-module_platform_driver_probe(at91_udc_driver, at91udc_probe);
+module_platform_driver(at91_udc_driver);
 
 MODULE_DESCRIPTION("AT91 udc driver");
 MODULE_AUTHOR("Thomas Rathbone, David Brownell");
index 7166d1117742a1d267c423556d3acd32bf7405a7..ded9531f141b1b94bd7505d10652f21ad0546cd5 100644 (file)
@@ -1126,12 +1126,12 @@ EXPORT_SYMBOL_GPL(usb_gadget_set_state);
 /* ------------------------------------------------------------------------- */
 
 /* Acquire connect_lock before calling this function. */
-static void usb_udc_connect_control_locked(struct usb_udc *udc) __must_hold(&udc->connect_lock)
+static int usb_udc_connect_control_locked(struct usb_udc *udc) __must_hold(&udc->connect_lock)
 {
        if (udc->vbus)
-               usb_gadget_connect_locked(udc->gadget);
+               return usb_gadget_connect_locked(udc->gadget);
        else
-               usb_gadget_disconnect_locked(udc->gadget);
+               return usb_gadget_disconnect_locked(udc->gadget);
 }
 
 static void vbus_event_work(struct work_struct *work)
@@ -1605,12 +1605,23 @@ static int gadget_bind_driver(struct device *dev)
        }
        usb_gadget_enable_async_callbacks(udc);
        udc->allow_connect = true;
-       usb_udc_connect_control_locked(udc);
+       ret = usb_udc_connect_control_locked(udc);
+       if (ret)
+               goto err_connect_control;
+
        mutex_unlock(&udc->connect_lock);
 
        kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE);
        return 0;
 
+ err_connect_control:
+       udc->allow_connect = false;
+       usb_gadget_disable_async_callbacks(udc);
+       if (gadget->irq)
+               synchronize_irq(gadget->irq);
+       usb_gadget_udc_stop_locked(udc);
+       mutex_unlock(&udc->connect_lock);
+
  err_start:
        driver->unbind(udc->gadget);
 
index 4aae86b47edfc4519d4c29b5e54b5843c08beba4..4e88681a79b6331dd7d1e3d0609366db326a924c 100644 (file)
 #include <linux/interrupt.h>
 #include <linux/io.h>
 #include <linux/moduleparam.h>
+#include <linux/of.h>
 #include <linux/of_address.h>
 #include <linux/of_irq.h>
-#include <linux/of_platform.h>
+#include <linux/platform_device.h>
 #include <linux/dma-mapping.h>
 #include <linux/usb/ch9.h>
 #include <linux/usb/gadget.h>
@@ -2471,17 +2472,12 @@ static const struct of_device_id qe_udc_match[];
 static int qe_udc_probe(struct platform_device *ofdev)
 {
        struct qe_udc *udc;
-       const struct of_device_id *match;
        struct device_node *np = ofdev->dev.of_node;
        struct qe_ep *ep;
        unsigned int ret = 0;
        unsigned int i;
        const void *prop;
 
-       match = of_match_device(qe_udc_match, &ofdev->dev);
-       if (!match)
-               return -EINVAL;
-
        prop = of_get_property(np, "mode", NULL);
        if (!prop || strcmp(prop, "peripheral"))
                return -ENODEV;
@@ -2493,7 +2489,7 @@ static int qe_udc_probe(struct platform_device *ofdev)
                return -ENOMEM;
        }
 
-       udc->soc_type = (unsigned long)match->data;
+       udc->soc_type = (unsigned long)device_get_match_data(&ofdev->dev);
        udc->usb_regs = of_iomap(np, 0);
        if (!udc->usb_regs) {
                ret = -ENOMEM;
index ee5705d336e3d62ea1277f1d75af688fe7721122..2693a10eb0c7cbc9503b4961c7471d16e6f8f17c 100644 (file)
@@ -2666,6 +2666,7 @@ static const struct platform_device_id fsl_udc_devtype[] = {
 };
 MODULE_DEVICE_TABLE(platform, fsl_udc_devtype);
 static struct platform_driver udc_driver = {
+       .probe          = fsl_udc_probe,
        .remove         = fsl_udc_remove,
        .id_table       = fsl_udc_devtype,
        /* these suspend and resume are not usb suspend and resume */
@@ -2679,7 +2680,7 @@ static struct platform_driver udc_driver = {
        },
 };
 
-module_platform_driver_probe(udc_driver, fsl_udc_probe);
+module_platform_driver(udc_driver);
 
 MODULE_DESCRIPTION(DRIVER_DESC);
 MODULE_AUTHOR(DRIVER_AUTHOR);
index bd03d475f927cd634347d440f658dbb2c159c1e8..873265634cccb7da05d0e7a456f291dc2c961b65 100644 (file)
@@ -1506,10 +1506,11 @@ clean_up:
 }
 
 static struct platform_driver fusb300_driver = {
-       .remove_new =   fusb300_remove,
-       .driver         = {
+       .probe = fusb300_probe,
+       .remove_new = fusb300_remove,
+       .driver = {
                .name = udc_name,
        },
 };
 
-module_platform_driver_probe(fusb300_driver, fusb300_probe);
+module_platform_driver(fusb300_driver);
index fe62db32dd0eb1ebf1b53ba1166824241707d6f7..a917cc9a32aba704d4c722c287972df2151a695b 100644 (file)
@@ -3254,6 +3254,7 @@ MODULE_DEVICE_TABLE(of, lpc32xx_udc_of_match);
 #endif
 
 static struct platform_driver lpc32xx_udc_driver = {
+       .probe          = lpc32xx_udc_probe,
        .remove         = lpc32xx_udc_remove,
        .shutdown       = lpc32xx_udc_shutdown,
        .suspend        = lpc32xx_udc_suspend,
@@ -3264,7 +3265,7 @@ static struct platform_driver lpc32xx_udc_driver = {
        },
 };
 
-module_platform_driver_probe(lpc32xx_udc_driver, lpc32xx_udc_probe);
+module_platform_driver(lpc32xx_udc_driver);
 
 MODULE_DESCRIPTION("LPC32XX udc driver");
 MODULE_AUTHOR("Kevin Wells <kevin.wells@nxp.com>");
index e05f45a4b56b2bf97bb16c828cea77a1ea355341..bfaa5291e6c83bd883c239a5fcd8a5447fb2878b 100644 (file)
@@ -1687,10 +1687,11 @@ clean_up:
 
 /*-------------------------------------------------------------------------*/
 static struct platform_driver m66592_driver = {
+       .probe =        m66592_probe,
        .remove_new =   m66592_remove,
        .driver         = {
                .name = udc_name,
        },
 };
 
-module_platform_driver_probe(m66592_driver, m66592_probe);
+module_platform_driver(m66592_driver);
index 51b665f15c8e898d765edf468f240574155359ca..db4a10a979f9db51c95ed08218091d06b3cf3948 100644 (file)
@@ -1964,13 +1964,14 @@ clean_up2:
 
 /*-------------------------------------------------------------------------*/
 static struct platform_driver r8a66597_driver = {
+       .probe =        r8a66597_probe,
        .remove_new =   r8a66597_remove,
        .driver         = {
                .name = udc_name,
        },
 };
 
-module_platform_driver_probe(r8a66597_driver, r8a66597_probe);
+module_platform_driver(r8a66597_driver);
 
 MODULE_DESCRIPTION("R8A66597 USB gadget driver");
 MODULE_LICENSE("GPL");
index 2665832f9addff97c4ec520cb7f7bb66de82dac0..1f9c1b1435d862895b3c3bee40760d4ceccf0a3e 100644 (file)
 #define EHCI_USBLEGCTLSTS      4               /* legacy control/status */
 #define EHCI_USBLEGCTLSTS_SOOE (1 << 13)       /* SMI on ownership change */
 
+/* ASMEDIA quirk use */
+#define ASMT_DATA_WRITE0_REG   0xF8
+#define ASMT_DATA_WRITE1_REG   0xFC
+#define ASMT_CONTROL_REG       0xE0
+#define ASMT_CONTROL_WRITE_BIT 0x02
+#define ASMT_WRITEREG_CMD      0x10423
+#define ASMT_FLOWCTL_ADDR      0xFA30
+#define ASMT_FLOWCTL_DATA      0xBA
+#define ASMT_PSEUDO_DATA       0
+
+/* Intel quirk use */
+#define USB_INTEL_XUSB2PR      0xD0
+#define USB_INTEL_USB2PRM      0xD4
+#define USB_INTEL_USB3_PSSEN   0xD8
+#define USB_INTEL_USB3PRM      0xDC
+
+#ifdef CONFIG_USB_PCI_AMD
 /* AMD quirk use */
 #define        AB_REG_BAR_LOW          0xe0
 #define        AB_REG_BAR_HIGH         0xe1
 #define        NB_PIF0_PWRDOWN_0       0x01100012
 #define        NB_PIF0_PWRDOWN_1       0x01100013
 
-#define USB_INTEL_XUSB2PR      0xD0
-#define USB_INTEL_USB2PRM      0xD4
-#define USB_INTEL_USB3_PSSEN   0xD8
-#define USB_INTEL_USB3PRM      0xDC
-
-/* ASMEDIA quirk use */
-#define ASMT_DATA_WRITE0_REG   0xF8
-#define ASMT_DATA_WRITE1_REG   0xFC
-#define ASMT_CONTROL_REG       0xE0
-#define ASMT_CONTROL_WRITE_BIT 0x02
-#define ASMT_WRITEREG_CMD      0x10423
-#define ASMT_FLOWCTL_ADDR      0xFA30
-#define ASMT_FLOWCTL_DATA      0xBA
-#define ASMT_PSEUDO_DATA       0
-
 /*
  * amd_chipset_gen values represent AMD different chipset generations
  */
@@ -458,50 +460,6 @@ void usb_amd_quirk_pll_disable(void)
 }
 EXPORT_SYMBOL_GPL(usb_amd_quirk_pll_disable);
 
-static int usb_asmedia_wait_write(struct pci_dev *pdev)
-{
-       unsigned long retry_count;
-       unsigned char value;
-
-       for (retry_count = 1000; retry_count > 0; --retry_count) {
-
-               pci_read_config_byte(pdev, ASMT_CONTROL_REG, &value);
-
-               if (value == 0xff) {
-                       dev_err(&pdev->dev, "%s: check_ready ERROR", __func__);
-                       return -EIO;
-               }
-
-               if ((value & ASMT_CONTROL_WRITE_BIT) == 0)
-                       return 0;
-
-               udelay(50);
-       }
-
-       dev_warn(&pdev->dev, "%s: check_write_ready timeout", __func__);
-       return -ETIMEDOUT;
-}
-
-void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev)
-{
-       if (usb_asmedia_wait_write(pdev) != 0)
-               return;
-
-       /* send command and address to device */
-       pci_write_config_dword(pdev, ASMT_DATA_WRITE0_REG, ASMT_WRITEREG_CMD);
-       pci_write_config_dword(pdev, ASMT_DATA_WRITE1_REG, ASMT_FLOWCTL_ADDR);
-       pci_write_config_byte(pdev, ASMT_CONTROL_REG, ASMT_CONTROL_WRITE_BIT);
-
-       if (usb_asmedia_wait_write(pdev) != 0)
-               return;
-
-       /* send data to device */
-       pci_write_config_dword(pdev, ASMT_DATA_WRITE0_REG, ASMT_FLOWCTL_DATA);
-       pci_write_config_dword(pdev, ASMT_DATA_WRITE1_REG, ASMT_PSEUDO_DATA);
-       pci_write_config_byte(pdev, ASMT_CONTROL_REG, ASMT_CONTROL_WRITE_BIT);
-}
-EXPORT_SYMBOL_GPL(usb_asmedia_modifyflowcontrol);
-
 void usb_amd_quirk_pll_enable(void)
 {
        usb_amd_quirk_pll(0);
@@ -630,7 +588,62 @@ bool usb_amd_pt_check_port(struct device *device, int port)
        return !(value & BIT(port_shift));
 }
 EXPORT_SYMBOL_GPL(usb_amd_pt_check_port);
+#endif /* CONFIG_USB_PCI_AMD */
+
+static int usb_asmedia_wait_write(struct pci_dev *pdev)
+{
+       unsigned long retry_count;
+       unsigned char value;
+
+       for (retry_count = 1000; retry_count > 0; --retry_count) {
+
+               pci_read_config_byte(pdev, ASMT_CONTROL_REG, &value);
+
+               if (value == 0xff) {
+                       dev_err(&pdev->dev, "%s: check_ready ERROR", __func__);
+                       return -EIO;
+               }
+
+               if ((value & ASMT_CONTROL_WRITE_BIT) == 0)
+                       return 0;
+
+               udelay(50);
+       }
+
+       dev_warn(&pdev->dev, "%s: check_write_ready timeout", __func__);
+       return -ETIMEDOUT;
+}
+
+void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev)
+{
+       if (usb_asmedia_wait_write(pdev) != 0)
+               return;
 
+       /* send command and address to device */
+       pci_write_config_dword(pdev, ASMT_DATA_WRITE0_REG, ASMT_WRITEREG_CMD);
+       pci_write_config_dword(pdev, ASMT_DATA_WRITE1_REG, ASMT_FLOWCTL_ADDR);
+       pci_write_config_byte(pdev, ASMT_CONTROL_REG, ASMT_CONTROL_WRITE_BIT);
+
+       if (usb_asmedia_wait_write(pdev) != 0)
+               return;
+
+       /* send data to device */
+       pci_write_config_dword(pdev, ASMT_DATA_WRITE0_REG, ASMT_FLOWCTL_DATA);
+       pci_write_config_dword(pdev, ASMT_DATA_WRITE1_REG, ASMT_PSEUDO_DATA);
+       pci_write_config_byte(pdev, ASMT_CONTROL_REG, ASMT_CONTROL_WRITE_BIT);
+}
+EXPORT_SYMBOL_GPL(usb_asmedia_modifyflowcontrol);
+
+static inline int io_type_enabled(struct pci_dev *pdev, unsigned int mask)
+{
+       u16 cmd;
+
+       return !pci_read_config_word(pdev, PCI_COMMAND, &cmd) && (cmd & mask);
+}
+
+#define mmio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_MEMORY)
+
+#if defined(CONFIG_HAS_IOPORT) && IS_ENABLED(CONFIG_USB_UHCI_HCD)
 /*
  * Make sure the controller is completely inactive, unable to
  * generate interrupts or do DMA.
@@ -712,14 +725,7 @@ reset_needed:
 }
 EXPORT_SYMBOL_GPL(uhci_check_and_reset_hc);
 
-static inline int io_type_enabled(struct pci_dev *pdev, unsigned int mask)
-{
-       u16 cmd;
-       return !pci_read_config_word(pdev, PCI_COMMAND, &cmd) && (cmd & mask);
-}
-
 #define pio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_IO)
-#define mmio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_MEMORY)
 
 static void quirk_usb_handoff_uhci(struct pci_dev *pdev)
 {
@@ -739,6 +745,12 @@ static void quirk_usb_handoff_uhci(struct pci_dev *pdev)
                uhci_check_and_reset_hc(pdev, base);
 }
 
+#else /* defined(CONFIG_HAS_IOPORT && IS_ENABLED(CONFIG_USB_UHCI_HCD) */
+
+static void quirk_usb_handoff_uhci(struct pci_dev *pdev) {}
+
+#endif /* defined(CONFIG_HAS_IOPORT && IS_ENABLED(CONFIG_USB_UHCI_HCD) */
+
 static int mmio_resource_enabled(struct pci_dev *pdev, int idx)
 {
        return pci_resource_start(pdev, idx) && mmio_enabled(pdev);
index e729de21fad7aacfcef9ce70bc94cf961f3ae46f..a5230b0b9e913a920e2b42b9f5b43ece0866ce67 100644 (file)
@@ -2,9 +2,7 @@
 #ifndef __LINUX_USB_PCI_QUIRKS_H
 #define __LINUX_USB_PCI_QUIRKS_H
 
-#ifdef CONFIG_USB_PCI
-void uhci_reset_hc(struct pci_dev *pdev, unsigned long base);
-int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base);
+#ifdef CONFIG_USB_PCI_AMD
 int usb_hcd_amd_remote_wakeup_quirk(struct pci_dev *pdev);
 bool usb_amd_hang_symptom_quirk(void);
 bool usb_amd_prefetch_quirk(void);
@@ -12,23 +10,41 @@ void usb_amd_dev_put(void);
 bool usb_amd_quirk_pll_check(void);
 void usb_amd_quirk_pll_disable(void);
 void usb_amd_quirk_pll_enable(void);
-void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev);
-void usb_enable_intel_xhci_ports(struct pci_dev *xhci_pdev);
-void usb_disable_xhci_ports(struct pci_dev *xhci_pdev);
 void sb800_prefetch(struct device *dev, int on);
 bool usb_amd_pt_check_port(struct device *device, int port);
 #else
-struct pci_dev;
+static inline bool usb_amd_hang_symptom_quirk(void)
+{
+       return false;
+};
+static inline bool usb_amd_prefetch_quirk(void)
+{
+       return false;
+}
 static inline void usb_amd_quirk_pll_disable(void) {}
 static inline void usb_amd_quirk_pll_enable(void) {}
-static inline void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev) {}
 static inline void usb_amd_dev_put(void) {}
-static inline void usb_disable_xhci_ports(struct pci_dev *xhci_pdev) {}
+static inline bool usb_amd_quirk_pll_check(void)
+{
+       return false;
+}
 static inline void sb800_prefetch(struct device *dev, int on) {}
 static inline bool usb_amd_pt_check_port(struct device *device, int port)
 {
        return false;
 }
+#endif /* CONFIG_USB_PCI_AMD */
+
+#ifdef CONFIG_USB_PCI
+void uhci_reset_hc(struct pci_dev *pdev, unsigned long base);
+int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base);
+void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev);
+void usb_enable_intel_xhci_ports(struct pci_dev *xhci_pdev);
+void usb_disable_xhci_ports(struct pci_dev *xhci_pdev);
+#else
+struct pci_dev;
+static inline void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev) {}
+static inline void usb_disable_xhci_ports(struct pci_dev *xhci_pdev) {}
 #endif  /* CONFIG_USB_PCI */
 
 #endif  /*  __LINUX_USB_PCI_QUIRKS_H  */
index 99baa60ef50fe954ede2dd593b7a66004b6647e6..6d142cd61bd6b9434360d788d4a41d51cb5a4195 100644 (file)
@@ -204,7 +204,7 @@ static void xhci_ring_dump_segment(struct seq_file *s,
        for (i = 0; i < TRBS_PER_SEGMENT; i++) {
                trb = &seg->trbs[i];
                dma = seg->dma + i * sizeof(*trb);
-               seq_printf(s, "%pad: %s\n", &dma,
+               seq_printf(s, "%2u %pad: %s\n", seg->num, &dma,
                           xhci_decode_trb(str, XHCI_MSG_MAX, le32_to_cpu(trb->generic.field[0]),
                                           le32_to_cpu(trb->generic.field[1]),
                                           le32_to_cpu(trb->generic.field[2]),
index e8af0a125f84b3bcce9ebd430df4559eaff1446c..96eb36a587382609c45a9023700d8f8d9101616a 100644 (file)
 /* true: Controller Not Ready to accept doorbell or op reg writes after reset */
 #define XHCI_STS_CNR           (1 << 11)
 
+/**
+ * struct xhci_protocol_caps
+ * @revision:          major revision, minor revision, capability ID,
+ *                     and next capability pointer.
+ * @name_string:       Four ASCII characters to say which spec this xHC
+ *                     follows, typically "USB ".
+ * @port_info:         Port offset, count, and protocol-defined information.
+ */
+struct xhci_protocol_caps {
+       u32     revision;
+       u32     name_string;
+       u32     port_info;
+};
+
+#define        XHCI_EXT_PORT_MAJOR(x)  (((x) >> 24) & 0xff)
+#define        XHCI_EXT_PORT_MINOR(x)  (((x) >> 16) & 0xff)
+#define        XHCI_EXT_PORT_PSIC(x)   (((x) >> 28) & 0x0f)
+#define        XHCI_EXT_PORT_OFF(x)    ((x) & 0xff)
+#define        XHCI_EXT_PORT_COUNT(x)  (((x) >> 8) & 0xff)
+
+#define        XHCI_EXT_PORT_PSIV(x)   (((x) >> 0) & 0x0f)
+#define        XHCI_EXT_PORT_PSIE(x)   (((x) >> 4) & 0x03)
+#define        XHCI_EXT_PORT_PLT(x)    (((x) >> 6) & 0x03)
+#define        XHCI_EXT_PORT_PFD(x)    (((x) >> 8) & 0x01)
+#define        XHCI_EXT_PORT_LP(x)     (((x) >> 14) & 0x03)
+#define        XHCI_EXT_PORT_PSIM(x)   (((x) >> 16) & 0xffff)
+
 #include <linux/io.h>
 
 /**
index 0df5d807a77e8f2ae2f15a3eed55415493c4ac3f..0980ade2a234a38ee6b8d25e674e1da12b624224 100644 (file)
@@ -1262,7 +1262,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
                        retval = -ENODEV;
                        break;
                }
-               trace_xhci_get_port_status(wIndex, temp);
+               trace_xhci_get_port_status(port, temp);
                status = xhci_get_port_status(hcd, bus_state, wIndex, temp,
                                              &flags);
                if (status == 0xffffffff)
@@ -1687,7 +1687,7 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf)
                        retval = -ENODEV;
                        break;
                }
-               trace_xhci_hub_status_data(i, temp);
+               trace_xhci_hub_status_data(ports[i], temp);
 
                if ((temp & mask) != 0 ||
                        (bus_state->port_c_suspend & 1 << i) ||
index 0a37f0d511cf53c377ac52669573086e4d5fb577..62116586848b84a160b11fcba5a4f2ba035b10c9 100644 (file)
@@ -29,6 +29,7 @@
 static struct xhci_segment *xhci_segment_alloc(struct xhci_hcd *xhci,
                                               unsigned int cycle_state,
                                               unsigned int max_packet,
+                                              unsigned int num,
                                               gfp_t flags)
 {
        struct xhci_segment *seg;
@@ -60,6 +61,7 @@ static struct xhci_segment *xhci_segment_alloc(struct xhci_hcd *xhci,
                for (i = 0; i < TRBS_PER_SEGMENT; i++)
                        seg->trbs[i].link.control = cpu_to_le32(TRB_CYCLE);
        }
+       seg->num = num;
        seg->dma = dma;
        seg->next = NULL;
 
@@ -128,7 +130,7 @@ static void xhci_link_rings(struct xhci_hcd *xhci, struct xhci_ring *ring,
                struct xhci_segment *first, struct xhci_segment *last,
                unsigned int num_segs)
 {
-       struct xhci_segment *next;
+       struct xhci_segment *next, *seg;
        bool chain_links;
 
        if (!ring || !first || !last)
@@ -144,13 +146,18 @@ static void xhci_link_rings(struct xhci_hcd *xhci, struct xhci_ring *ring,
        xhci_link_segments(last, next, ring->type, chain_links);
        ring->num_segs += num_segs;
 
-       if (ring->type != TYPE_EVENT && ring->enq_seg == ring->last_seg) {
-               ring->last_seg->trbs[TRBS_PER_SEGMENT-1].link.control
-                       &= ~cpu_to_le32(LINK_TOGGLE);
-               last->trbs[TRBS_PER_SEGMENT-1].link.control
-                       |= cpu_to_le32(LINK_TOGGLE);
+       if (ring->enq_seg == ring->last_seg) {
+               if (ring->type != TYPE_EVENT) {
+                       ring->last_seg->trbs[TRBS_PER_SEGMENT-1].link.control
+                               &= ~cpu_to_le32(LINK_TOGGLE);
+                       last->trbs[TRBS_PER_SEGMENT-1].link.control
+                               |= cpu_to_le32(LINK_TOGGLE);
+               }
                ring->last_seg = last;
        }
+
+       for (seg = last; seg != ring->last_seg; seg = seg->next)
+               seg->next->num = seg->num + 1;
 }
 
 /*
@@ -320,8 +327,9 @@ void xhci_initialize_ring_info(struct xhci_ring *ring,
 /* Allocate segments and link them for a ring */
 static int xhci_alloc_segments_for_ring(struct xhci_hcd *xhci,
                struct xhci_segment **first, struct xhci_segment **last,
-               unsigned int num_segs, unsigned int cycle_state,
-               enum xhci_ring_type type, unsigned int max_packet, gfp_t flags)
+               unsigned int num_segs, unsigned int num,
+               unsigned int cycle_state, enum xhci_ring_type type,
+               unsigned int max_packet, gfp_t flags)
 {
        struct xhci_segment *prev;
        bool chain_links;
@@ -331,16 +339,17 @@ static int xhci_alloc_segments_for_ring(struct xhci_hcd *xhci,
                         (type == TYPE_ISOC &&
                          (xhci->quirks & XHCI_AMD_0x96_HOST)));
 
-       prev = xhci_segment_alloc(xhci, cycle_state, max_packet, flags);
+       prev = xhci_segment_alloc(xhci, cycle_state, max_packet, num, flags);
        if (!prev)
                return -ENOMEM;
-       num_segs--;
+       num++;
 
        *first = prev;
-       while (num_segs > 0) {
+       while (num < num_segs) {
                struct xhci_segment     *next;
 
-               next = xhci_segment_alloc(xhci, cycle_state, max_packet, flags);
+               next = xhci_segment_alloc(xhci, cycle_state, max_packet, num,
+                                         flags);
                if (!next) {
                        prev = *first;
                        while (prev) {
@@ -353,7 +362,7 @@ static int xhci_alloc_segments_for_ring(struct xhci_hcd *xhci,
                xhci_link_segments(prev, next, type, chain_links);
 
                prev = next;
-               num_segs--;
+               num++;
        }
        xhci_link_segments(prev, *first, type, chain_links);
        *last = prev;
@@ -388,7 +397,7 @@ struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci,
                return ring;
 
        ret = xhci_alloc_segments_for_ring(xhci, &ring->first_seg,
-                       &ring->last_seg, num_segs, cycle_state, type,
+                       &ring->last_seg, num_segs, 0, cycle_state, type,
                        max_packet, flags);
        if (ret)
                goto fail;
@@ -428,7 +437,8 @@ int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring,
        int                     ret;
 
        ret = xhci_alloc_segments_for_ring(xhci, &first, &last,
-                       num_new_segs, ring->cycle_state, ring->type,
+                       num_new_segs, ring->enq_seg->num + 1,
+                       ring->cycle_state, ring->type,
                        ring->bounce_buf_len, flags);
        if (ret)
                return -ENOMEM;
@@ -1766,7 +1776,7 @@ void xhci_free_command(struct xhci_hcd *xhci,
        kfree(command);
 }
 
-int xhci_alloc_erst(struct xhci_hcd *xhci,
+static int xhci_alloc_erst(struct xhci_hcd *xhci,
                    struct xhci_ring *evt_ring,
                    struct xhci_erst *erst,
                    gfp_t flags)
@@ -1797,23 +1807,13 @@ int xhci_alloc_erst(struct xhci_hcd *xhci,
 }
 
 static void
-xhci_free_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
+xhci_remove_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
 {
-       struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
-       size_t erst_size;
-       u64 tmp64;
        u32 tmp;
 
        if (!ir)
                return;
 
-       erst_size = sizeof(struct xhci_erst_entry) * ir->erst.num_entries;
-       if (ir->erst.entries)
-               dma_free_coherent(dev, erst_size,
-                                 ir->erst.entries,
-                                 ir->erst.erst_dma_addr);
-       ir->erst.entries = NULL;
-
        /*
         * Clean out interrupter registers except ERSTBA. Clearing either the
         * low or high 32 bits of ERSTBA immediately causes the controller to
@@ -1824,14 +1824,30 @@ xhci_free_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
                tmp &= ERST_SIZE_MASK;
                writel(tmp, &ir->ir_set->erst_size);
 
-               tmp64 = xhci_read_64(xhci, &ir->ir_set->erst_dequeue);
-               tmp64 &= (u64) ERST_PTR_MASK;
-               xhci_write_64(xhci, tmp64, &ir->ir_set->erst_dequeue);
+               xhci_write_64(xhci, ERST_EHB, &ir->ir_set->erst_dequeue);
        }
+}
 
-       /* free interrrupter event ring */
+static void
+xhci_free_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
+{
+       struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
+       size_t erst_size;
+
+       if (!ir)
+               return;
+
+       erst_size = sizeof(struct xhci_erst_entry) * ir->erst.num_entries;
+       if (ir->erst.entries)
+               dma_free_coherent(dev, erst_size,
+                                 ir->erst.entries,
+                                 ir->erst.erst_dma_addr);
+       ir->erst.entries = NULL;
+
+       /* free interrupter event ring */
        if (ir->event_ring)
                xhci_ring_free(xhci, ir->event_ring);
+
        ir->event_ring = NULL;
 
        kfree(ir);
@@ -1844,6 +1860,7 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci)
 
        cancel_delayed_work_sync(&xhci->cmd_timer);
 
+       xhci_remove_interrupter(xhci, xhci->interrupter);
        xhci_free_interrupter(xhci, xhci->interrupter);
        xhci->interrupter = NULL;
        xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed primary event ring");
@@ -1933,7 +1950,6 @@ no_bw:
 
 static void xhci_set_hc_event_deq(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
 {
-       u64 temp;
        dma_addr_t deq;
 
        deq = xhci_trb_virt_to_dma(ir->event_ring->deq_seg,
@@ -1941,16 +1957,12 @@ static void xhci_set_hc_event_deq(struct xhci_hcd *xhci, struct xhci_interrupter
        if (!deq)
                xhci_warn(xhci, "WARN something wrong with SW event ring dequeue ptr.\n");
        /* Update HC event ring dequeue pointer */
-       temp = xhci_read_64(xhci, &ir->ir_set->erst_dequeue);
-       temp &= ERST_PTR_MASK;
        /* Don't clear the EHB bit (which is RW1C) because
         * there might be more events to service.
         */
-       temp &= ~ERST_EHB;
        xhci_dbg_trace(xhci, trace_xhci_dbg_init,
                       "// Write event ring dequeue pointer, preserving EHB bit");
-       xhci_write_64(xhci, ((u64) deq & (u64) ~ERST_PTR_MASK) | temp,
-                       &ir->ir_set->erst_dequeue);
+       xhci_write_64(xhci, deq & ERST_PTR_MASK, &ir->ir_set->erst_dequeue);
 }
 
 static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports,
@@ -2238,14 +2250,18 @@ xhci_alloc_interrupter(struct xhci_hcd *xhci, gfp_t flags)
 {
        struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
        struct xhci_interrupter *ir;
+       unsigned int num_segs;
        int ret;
 
        ir = kzalloc_node(sizeof(*ir), flags, dev_to_node(dev));
        if (!ir)
                return NULL;
 
-       ir->event_ring = xhci_ring_alloc(xhci, ERST_NUM_SEGS, 1, TYPE_EVENT,
-                                       0, flags);
+       num_segs = min_t(unsigned int, 1 << HCS_ERST_MAX(xhci->hcs_params2),
+                        ERST_MAX_SEGS);
+
+       ir->event_ring = xhci_ring_alloc(xhci, num_segs, 1, TYPE_EVENT, 0,
+                                        flags);
        if (!ir->event_ring) {
                xhci_warn(xhci, "Failed to allocate interrupter event ring\n");
                kfree(ir);
@@ -2281,7 +2297,7 @@ xhci_add_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir,
        /* set ERST count with the number of entries in the segment table */
        erst_size = readl(&ir->ir_set->erst_size);
        erst_size &= ERST_SIZE_MASK;
-       erst_size |= ERST_NUM_SEGS;
+       erst_size |= ir->event_ring->num_segs;
        writel(erst_size, &ir->ir_set->erst_size);
 
        erst_base = xhci_read_64(xhci, &ir->ir_set->erst_base);
index 579899eb24c15245f3f70120b9f4df1e2a1de547..5b3cd455adecc21bd3fcc70316cb4ceab8e51609 100644 (file)
 #define HS_BW_BOUNDARY 6144
 /* usb2 spec section11.18.1: at most 188 FS bytes per microframe */
 #define FS_PAYLOAD_MAX 188
+#define LS_PAYLOAD_MAX 18
+/* section 11.18.1, per fs frame */
+#define FS_BW_BOUNDARY 1157
+#define LS_BW_BOUNDARY 144
+
+/*
+ * max number of microframes for split transfer, assume extra-cs budget is 0
+ * for fs isoc in : 1 ss + 1 idle + 6 cs (roundup(1023/188))
+ */
+#define TT_MICROFRAMES_MAX     8
+/* offset from SS for fs/ls isoc/intr ep (ss + idle) */
+#define CS_OFFSET      2
 
 #define DBG_BUF_EN     64
 
@@ -237,17 +249,26 @@ static void drop_tt(struct usb_device *udev)
 
 static struct mu3h_sch_ep_info *
 create_sch_ep(struct xhci_hcd_mtk *mtk, struct usb_device *udev,
-             struct usb_host_endpoint *ep)
+             struct usb_host_endpoint *ep, struct xhci_ep_ctx *ep_ctx)
 {
        struct mu3h_sch_ep_info *sch_ep;
        struct mu3h_sch_bw_info *bw_info;
        struct mu3h_sch_tt *tt = NULL;
+       u32 len;
 
        bw_info = get_bw_info(mtk, udev, ep);
        if (!bw_info)
                return ERR_PTR(-ENODEV);
 
-       sch_ep = kzalloc(sizeof(*sch_ep), GFP_KERNEL);
+       if (is_fs_or_ls(udev->speed))
+               len = TT_MICROFRAMES_MAX;
+       else if ((udev->speed >= USB_SPEED_SUPER) &&
+                usb_endpoint_xfer_isoc(&ep->desc))
+               len = get_esit(ep_ctx);
+       else
+               len = 1;
+
+       sch_ep = kzalloc(struct_size(sch_ep, bw_budget_table, len), GFP_KERNEL);
        if (!sch_ep)
                return ERR_PTR(-ENOMEM);
 
@@ -279,7 +300,11 @@ static void setup_sch_info(struct xhci_ep_ctx *ep_ctx,
        u32 mult;
        u32 esit_pkts;
        u32 max_esit_payload;
+       u32 bw_per_microframe;
+       u32 *bwb_table;
+       int i;
 
+       bwb_table = sch_ep->bw_budget_table;
        ep_type = CTX_TO_EP_TYPE(le32_to_cpu(ep_ctx->ep_info2));
        maxpkt = MAX_PACKET_DECODED(le32_to_cpu(ep_ctx->ep_info2));
        max_burst = CTX_TO_MAX_BURST(le32_to_cpu(ep_ctx->ep_info2));
@@ -313,7 +338,7 @@ static void setup_sch_info(struct xhci_ep_ctx *ep_ctx,
                 * opportunities per microframe
                 */
                sch_ep->pkts = max_burst + 1;
-               sch_ep->bw_cost_per_microframe = maxpkt * sch_ep->pkts;
+               bwb_table[0] = maxpkt * sch_ep->pkts;
        } else if (sch_ep->speed >= USB_SPEED_SUPER) {
                /* usb3_r1 spec section4.4.7 & 4.4.8 */
                sch_ep->cs_count = 0;
@@ -330,6 +355,7 @@ static void setup_sch_info(struct xhci_ep_ctx *ep_ctx,
                if (ep_type == INT_IN_EP || ep_type == INT_OUT_EP) {
                        sch_ep->pkts = esit_pkts;
                        sch_ep->num_budget_microframes = 1;
+                       bwb_table[0] = maxpkt * sch_ep->pkts;
                }
 
                if (ep_type == ISOC_IN_EP || ep_type == ISOC_OUT_EP) {
@@ -346,18 +372,52 @@ static void setup_sch_info(struct xhci_ep_ctx *ep_ctx,
                                DIV_ROUND_UP(esit_pkts, sch_ep->pkts);
 
                        sch_ep->repeat = !!(sch_ep->num_budget_microframes > 1);
+                       bw_per_microframe = maxpkt * sch_ep->pkts;
+
+                       for (i = 0; i < sch_ep->num_budget_microframes - 1; i++)
+                               bwb_table[i] = bw_per_microframe;
+
+                       /* last one <= bw_per_microframe */
+                       bwb_table[i] = maxpkt * esit_pkts - i * bw_per_microframe;
                }
-               sch_ep->bw_cost_per_microframe = maxpkt * sch_ep->pkts;
        } else if (is_fs_or_ls(sch_ep->speed)) {
                sch_ep->pkts = 1; /* at most one packet for each microframe */
 
                /*
-                * num_budget_microframes and cs_count will be updated when
+                * @cs_count will be updated to add extra-cs when
                 * check TT for INT_OUT_EP, ISOC/INT_IN_EP type
+                * @maxpkt <= 1023;
                 */
                sch_ep->cs_count = DIV_ROUND_UP(maxpkt, FS_PAYLOAD_MAX);
                sch_ep->num_budget_microframes = sch_ep->cs_count;
-               sch_ep->bw_cost_per_microframe = min_t(u32, maxpkt, FS_PAYLOAD_MAX);
+
+               /* init budget table */
+               if (ep_type == ISOC_OUT_EP) {
+                       for (i = 0; i < sch_ep->cs_count - 1; i++)
+                               bwb_table[i] = FS_PAYLOAD_MAX;
+
+                       bwb_table[i] = maxpkt - i * FS_PAYLOAD_MAX;
+               } else if (ep_type == INT_OUT_EP) {
+                       /* only first one used (maxpkt <= 64), others zero */
+                       bwb_table[0] = maxpkt;
+               } else { /* INT_IN_EP or ISOC_IN_EP */
+                       bwb_table[0] = 0; /* start split */
+                       bwb_table[1] = 0; /* idle */
+                       /*
+                        * @cs_count will be updated according to cs position
+                        * (add 1 or 2 extra-cs), but assume only first
+                        * @num_budget_microframes elements will be used later,
+                        * although in fact it does not (extra-cs budget many receive
+                        * some data for IN ep);
+                        * @cs_count is 1 for INT_IN_EP (maxpkt <= 64);
+                        */
+                       for (i = 0; i < sch_ep->cs_count - 1; i++)
+                               bwb_table[i + CS_OFFSET] = FS_PAYLOAD_MAX;
+
+                       bwb_table[i + CS_OFFSET] = maxpkt - i * FS_PAYLOAD_MAX;
+                       /* ss + idle */
+                       sch_ep->num_budget_microframes += CS_OFFSET;
+               }
        }
 }
 
@@ -374,7 +434,7 @@ static u32 get_max_bw(struct mu3h_sch_bw_info *sch_bw,
 
                for (j = 0; j < sch_ep->num_budget_microframes; j++) {
                        k = XHCI_MTK_BW_INDEX(base + j);
-                       bw = sch_bw->bus_bw[k] + sch_ep->bw_cost_per_microframe;
+                       bw = sch_bw->bus_bw[k] + sch_ep->bw_budget_table[j];
                        if (bw > max_bw)
                                max_bw = bw;
                }
@@ -382,56 +442,152 @@ static u32 get_max_bw(struct mu3h_sch_bw_info *sch_bw,
        return max_bw;
 }
 
+/*
+ * for OUT: get first SS consumed bw;
+ * for IN: get first CS consumed bw;
+ */
+static u16 get_fs_bw(struct mu3h_sch_ep_info *sch_ep, int offset)
+{
+       struct mu3h_sch_tt *tt = sch_ep->sch_tt;
+       u16 fs_bw;
+
+       if (sch_ep->ep_type == ISOC_OUT_EP || sch_ep->ep_type == INT_OUT_EP)
+               fs_bw = tt->fs_bus_bw_out[XHCI_MTK_BW_INDEX(offset)];
+       else    /* skip ss + idle */
+               fs_bw = tt->fs_bus_bw_in[XHCI_MTK_BW_INDEX(offset + CS_OFFSET)];
+
+       return fs_bw;
+}
+
 static void update_bus_bw(struct mu3h_sch_bw_info *sch_bw,
        struct mu3h_sch_ep_info *sch_ep, bool used)
 {
-       int bw_updated;
        u32 base;
-       int i, j;
-
-       bw_updated = sch_ep->bw_cost_per_microframe * (used ? 1 : -1);
+       int i, j, k;
 
        for (i = 0; i < sch_ep->num_esit; i++) {
                base = sch_ep->offset + i * sch_ep->esit;
-               for (j = 0; j < sch_ep->num_budget_microframes; j++)
-                       sch_bw->bus_bw[XHCI_MTK_BW_INDEX(base + j)] += bw_updated;
+               for (j = 0; j < sch_ep->num_budget_microframes; j++) {
+                       k = XHCI_MTK_BW_INDEX(base + j);
+                       if (used)
+                               sch_bw->bus_bw[k] += sch_ep->bw_budget_table[j];
+                       else
+                               sch_bw->bus_bw[k] -= sch_ep->bw_budget_table[j];
+               }
        }
 }
 
-static int check_fs_bus_bw(struct mu3h_sch_ep_info *sch_ep, int offset)
+static int check_ls_budget_microframes(struct mu3h_sch_ep_info *sch_ep, int offset)
+{
+       struct mu3h_sch_tt *tt = sch_ep->sch_tt;
+       int i;
+
+       if (sch_ep->speed != USB_SPEED_LOW)
+               return 0;
+
+       if (sch_ep->ep_type == INT_OUT_EP)
+               i = XHCI_MTK_BW_INDEX(offset);
+       else if (sch_ep->ep_type == INT_IN_EP)
+               i = XHCI_MTK_BW_INDEX(offset + CS_OFFSET); /* skip ss + idle */
+       else
+               return -EINVAL;
+
+       if (tt->ls_bus_bw[i] + sch_ep->maxpkt > LS_PAYLOAD_MAX)
+               return -ESCH_BW_OVERFLOW;
+
+       return 0;
+}
+
+static int check_fs_budget_microframes(struct mu3h_sch_ep_info *sch_ep, int offset)
 {
        struct mu3h_sch_tt *tt = sch_ep->sch_tt;
        u32 tmp;
-       int base;
+       int i, k;
+
+       /*
+        * for OUT eps, will transfer exactly assigned length of data,
+        * so can't allocate more than 188 bytes;
+        * but it's not for IN eps, usually it can't receive full
+        * 188 bytes in a uframe, if it not assign full 188 bytes,
+        * can add another one;
+        */
+       for (i = 0; i < sch_ep->num_budget_microframes; i++) {
+               k = XHCI_MTK_BW_INDEX(offset + i);
+               if (sch_ep->ep_type == ISOC_OUT_EP || sch_ep->ep_type == INT_OUT_EP)
+                       tmp = tt->fs_bus_bw_out[k] + sch_ep->bw_budget_table[i];
+               else /* ep_type : ISOC IN / INTR IN */
+                       tmp = tt->fs_bus_bw_in[k];
+
+               if (tmp > FS_PAYLOAD_MAX)
+                       return -ESCH_BW_OVERFLOW;
+       }
+
+       return 0;
+}
+
+static int check_fs_budget_frames(struct mu3h_sch_ep_info *sch_ep, int offset)
+{
+       struct mu3h_sch_tt *tt = sch_ep->sch_tt;
+       u32 head, tail;
        int i, j, k;
 
+       /* bugdet scheduled may cross at most two fs frames */
+       j = XHCI_MTK_BW_INDEX(offset) / UFRAMES_PER_FRAME;
+       k = XHCI_MTK_BW_INDEX(offset + sch_ep->num_budget_microframes - 1) / UFRAMES_PER_FRAME;
+
+       if (j != k) {
+               head = tt->fs_frame_bw[j];
+               tail = tt->fs_frame_bw[k];
+       } else {
+               head = tt->fs_frame_bw[j];
+               tail = 0;
+       }
+
+       j = roundup(offset, UFRAMES_PER_FRAME);
+       for (i = 0; i < sch_ep->num_budget_microframes; i++) {
+               if ((offset + i) < j)
+                       head += sch_ep->bw_budget_table[i];
+               else
+                       tail += sch_ep->bw_budget_table[i];
+       }
+
+       if (head > FS_BW_BOUNDARY || tail > FS_BW_BOUNDARY)
+               return -ESCH_BW_OVERFLOW;
+
+       return 0;
+}
+
+static int check_fs_bus_bw(struct mu3h_sch_ep_info *sch_ep, int offset)
+{
+       int i, base;
+       int ret = 0;
+
        for (i = 0; i < sch_ep->num_esit; i++) {
                base = offset + i * sch_ep->esit;
 
-               /*
-                * Compared with hs bus, no matter what ep type,
-                * the hub will always delay one uframe to send data
-                */
-               for (j = 0; j < sch_ep->num_budget_microframes; j++) {
-                       k = XHCI_MTK_BW_INDEX(base + j);
-                       tmp = tt->fs_bus_bw[k] + sch_ep->bw_cost_per_microframe;
-                       if (tmp > FS_PAYLOAD_MAX)
-                               return -ESCH_BW_OVERFLOW;
-               }
+               ret = check_ls_budget_microframes(sch_ep, base);
+               if (ret)
+                       goto err;
+
+               ret = check_fs_budget_microframes(sch_ep, base);
+               if (ret)
+                       goto err;
+
+               ret = check_fs_budget_frames(sch_ep, base);
+               if (ret)
+                       goto err;
        }
 
-       return 0;
+err:
+       return ret;
 }
 
-static int check_sch_tt(struct mu3h_sch_ep_info *sch_ep, u32 offset)
+static int check_ss_and_cs(struct mu3h_sch_ep_info *sch_ep, u32 offset)
 {
        u32 start_ss, last_ss;
        u32 start_cs, last_cs;
 
-       if (!sch_ep->sch_tt)
-               return 0;
-
-       start_ss = offset % 8;
+       start_ss = offset % UFRAMES_PER_FRAME;
 
        if (sch_ep->ep_type == ISOC_OUT_EP) {
                last_ss = start_ss + sch_ep->cs_count - 1;
@@ -444,6 +600,7 @@ static int check_sch_tt(struct mu3h_sch_ep_info *sch_ep, u32 offset)
                        return -ESCH_SS_Y6;
 
        } else {
+               /* maxpkt <= 1023, cs <= 6 */
                u32 cs_count = DIV_ROUND_UP(sch_ep->maxpkt, FS_PAYLOAD_MAX);
 
                /*
@@ -454,44 +611,164 @@ static int check_sch_tt(struct mu3h_sch_ep_info *sch_ep, u32 offset)
                        return -ESCH_SS_Y6;
 
                /* one uframe for ss + one uframe for idle */
-               start_cs = (start_ss + 2) % 8;
+               start_cs = (start_ss + CS_OFFSET) % UFRAMES_PER_FRAME;
                last_cs = start_cs + cs_count - 1;
-
                if (last_cs > 7)
                        return -ESCH_CS_OVERFLOW;
 
+               /* add extra-cs */
+               cs_count += (last_cs == 7) ? 1 : 2;
                if (cs_count > 7)
                        cs_count = 7; /* HW limit */
 
                sch_ep->cs_count = cs_count;
-               /* ss, idle are ignored */
-               sch_ep->num_budget_microframes = cs_count;
 
-               /*
-                * if interval=1, maxp >752, num_budge_micoframe is larger
-                * than sch_ep->esit, will overstep boundary
-                */
-               if (sch_ep->num_budget_microframes > sch_ep->esit)
-                       sch_ep->num_budget_microframes = sch_ep->esit;
        }
 
+       return 0;
+}
+
+/*
+ * when isoc-out transfers 188 bytes in a uframe, and send isoc/intr's
+ * ss token in the uframe, may cause 'bit stuff error' in downstream
+ * port;
+ * when isoc-out transfer less than 188 bytes in a uframe, shall send
+ * isoc-in's ss after isoc-out's ss (but hw can't ensure the sequence,
+ * so just avoid overlap).
+ */
+static int check_isoc_ss_overlap(struct mu3h_sch_ep_info *sch_ep, u32 offset)
+{
+       struct mu3h_sch_tt *tt = sch_ep->sch_tt;
+       int base;
+       int i, j, k;
+
+       if (!tt)
+               return 0;
+
+       for (i = 0; i < sch_ep->num_esit; i++) {
+               base = offset + i * sch_ep->esit;
+
+               if (sch_ep->ep_type == ISOC_OUT_EP) {
+                       for (j = 0; j < sch_ep->num_budget_microframes; j++) {
+                               k = XHCI_MTK_BW_INDEX(base + j + CS_OFFSET);
+                               /* use cs to indicate existence of in-ss @(base+j) */
+                               if (tt->fs_bus_bw_in[k])
+                                       return -ESCH_SS_OVERLAP;
+                       }
+               } else if (sch_ep->ep_type == ISOC_IN_EP || sch_ep->ep_type == INT_IN_EP) {
+                       k = XHCI_MTK_BW_INDEX(base);
+                       /* only check IN's ss */
+                       if (tt->fs_bus_bw_out[k])
+                               return -ESCH_SS_OVERLAP;
+               }
+       }
+
+       return 0;
+}
+
+static int check_sch_tt_budget(struct mu3h_sch_ep_info *sch_ep, u32 offset)
+{
+       int ret;
+
+       ret = check_ss_and_cs(sch_ep, offset);
+       if (ret)
+               return ret;
+
+       ret = check_isoc_ss_overlap(sch_ep, offset);
+       if (ret)
+               return ret;
+
        return check_fs_bus_bw(sch_ep, offset);
 }
 
+/* allocate microframes in the ls/fs frame */
+static int alloc_sch_portion_of_frame(struct mu3h_sch_ep_info *sch_ep)
+{
+       struct mu3h_sch_bw_info *sch_bw = sch_ep->bw_info;
+       const u32 bw_boundary = get_bw_boundary(sch_ep->speed);
+       u32 bw_max, fs_bw_min;
+       u32 offset, offset_min;
+       u16 fs_bw;
+       int frames;
+       int i, j;
+       int ret;
+
+       frames = sch_ep->esit / UFRAMES_PER_FRAME;
+
+       for (i = 0; i < UFRAMES_PER_FRAME; i++) {
+               fs_bw_min = FS_PAYLOAD_MAX;
+               offset_min = XHCI_MTK_MAX_ESIT;
+
+               for (j = 0; j < frames; j++) {
+                       offset = (i + j * UFRAMES_PER_FRAME) % sch_ep->esit;
+
+                       ret = check_sch_tt_budget(sch_ep, offset);
+                       if (ret)
+                               continue;
+
+                       /* check hs bw domain */
+                       bw_max = get_max_bw(sch_bw, sch_ep, offset);
+                       if (bw_max > bw_boundary) {
+                               ret = -ESCH_BW_OVERFLOW;
+                               continue;
+                       }
+
+                       /* use best-fit between frames */
+                       fs_bw = get_fs_bw(sch_ep, offset);
+                       if (fs_bw < fs_bw_min) {
+                               fs_bw_min = fs_bw;
+                               offset_min = offset;
+                       }
+
+                       if (!fs_bw_min)
+                               break;
+               }
+
+               /* use first-fit between microframes in a frame */
+               if (offset_min < XHCI_MTK_MAX_ESIT)
+                       break;
+       }
+
+       if (offset_min == XHCI_MTK_MAX_ESIT)
+               return -ESCH_BW_OVERFLOW;
+
+       sch_ep->offset = offset_min;
+
+       return 0;
+}
+
 static void update_sch_tt(struct mu3h_sch_ep_info *sch_ep, bool used)
 {
        struct mu3h_sch_tt *tt = sch_ep->sch_tt;
-       int bw_updated;
+       u16 *fs_bus_bw;
        u32 base;
-       int i, j;
+       int i, j, k, f;
 
-       bw_updated = sch_ep->bw_cost_per_microframe * (used ? 1 : -1);
+       if (sch_ep->ep_type == ISOC_OUT_EP || sch_ep->ep_type == INT_OUT_EP)
+               fs_bus_bw = tt->fs_bus_bw_out;
+       else
+               fs_bus_bw = tt->fs_bus_bw_in;
 
        for (i = 0; i < sch_ep->num_esit; i++) {
                base = sch_ep->offset + i * sch_ep->esit;
 
-               for (j = 0; j < sch_ep->num_budget_microframes; j++)
-                       tt->fs_bus_bw[XHCI_MTK_BW_INDEX(base + j)] += bw_updated;
+               for (j = 0; j < sch_ep->num_budget_microframes; j++) {
+                       k = XHCI_MTK_BW_INDEX(base + j);
+                       f = k / UFRAMES_PER_FRAME;
+                       if (used) {
+                               if (sch_ep->speed == USB_SPEED_LOW)
+                                       tt->ls_bus_bw[k] += (u8)sch_ep->bw_budget_table[j];
+
+                               fs_bus_bw[k] += (u16)sch_ep->bw_budget_table[j];
+                               tt->fs_frame_bw[f] += (u16)sch_ep->bw_budget_table[j];
+                       } else {
+                               if (sch_ep->speed == USB_SPEED_LOW)
+                                       tt->ls_bus_bw[k] -= (u8)sch_ep->bw_budget_table[j];
+
+                               fs_bus_bw[k] -= (u16)sch_ep->bw_budget_table[j];
+                               tt->fs_frame_bw[f] -= (u16)sch_ep->bw_budget_table[j];
+                       }
+               }
        }
 
        if (used)
@@ -513,7 +790,8 @@ static int load_ep_bw(struct mu3h_sch_bw_info *sch_bw,
        return 0;
 }
 
-static int check_sch_bw(struct mu3h_sch_ep_info *sch_ep)
+/* allocate microframes for hs/ss/ssp */
+static int alloc_sch_microframes(struct mu3h_sch_ep_info *sch_ep)
 {
        struct mu3h_sch_bw_info *sch_bw = sch_ep->bw_info;
        const u32 bw_boundary = get_bw_boundary(sch_ep->speed);
@@ -521,16 +799,12 @@ static int check_sch_bw(struct mu3h_sch_ep_info *sch_ep)
        u32 worst_bw;
        u32 min_bw = ~0;
        int min_index = -1;
-       int ret = 0;
 
        /*
         * Search through all possible schedule microframes.
         * and find a microframe where its worst bandwidth is minimum.
         */
        for (offset = 0; offset < sch_ep->esit; offset++) {
-               ret = check_sch_tt(sch_ep, offset);
-               if (ret)
-                       continue;
 
                worst_bw = get_max_bw(sch_bw, sch_ep, offset);
                if (worst_bw > bw_boundary)
@@ -540,21 +814,29 @@ static int check_sch_bw(struct mu3h_sch_ep_info *sch_ep)
                        min_bw = worst_bw;
                        min_index = offset;
                }
-
-               /* use first-fit for LS/FS */
-               if (sch_ep->sch_tt && min_index >= 0)
-                       break;
-
-               if (min_bw == 0)
-                       break;
        }
 
        if (min_index < 0)
-               return ret ? ret : -ESCH_BW_OVERFLOW;
+               return -ESCH_BW_OVERFLOW;
 
        sch_ep->offset = min_index;
 
-       return load_ep_bw(sch_bw, sch_ep, true);
+       return 0;
+}
+
+static int check_sch_bw(struct mu3h_sch_ep_info *sch_ep)
+{
+       int ret;
+
+       if (sch_ep->sch_tt)
+               ret = alloc_sch_portion_of_frame(sch_ep);
+       else
+               ret = alloc_sch_microframes(sch_ep);
+
+       if (ret)
+               return ret;
+
+       return load_ep_bw(sch_ep->bw_info, sch_ep, true);
 }
 
 static void destroy_sch_ep(struct xhci_hcd_mtk *mtk, struct usb_device *udev,
@@ -651,7 +933,7 @@ static int add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
 
        xhci_dbg(xhci, "%s %s\n", __func__, decode_ep(ep, udev->speed));
 
-       sch_ep = create_sch_ep(mtk, udev, ep);
+       sch_ep = create_sch_ep(mtk, udev, ep, ep_ctx);
        if (IS_ERR_OR_NULL(sch_ep))
                return -ENOMEM;
 
index faaaf05e36ce0963a5108b2d0a997024464f023c..865b55e23b15948f68ec933395077df1f7d71960 100644 (file)
 #define XHCI_MTK_MAX_ESIT      (1 << 6)
 #define XHCI_MTK_BW_INDEX(x)   ((x) & (XHCI_MTK_MAX_ESIT - 1))
 
+#define UFRAMES_PER_FRAME      8
+#define XHCI_MTK_FRAMES_CNT    (XHCI_MTK_MAX_ESIT / UFRAMES_PER_FRAME)
+
 /**
- * @fs_bus_bw: array to keep track of bandwidth already used for FS
+ * @fs_bus_bw_out: save bandwidth used by FS/LS OUT eps in each uframes
+ * @fs_bus_bw_in: save bandwidth used by FS/LS IN eps in each uframes
+ * @ls_bus_bw: save bandwidth used by LS eps in each uframes
+ * @fs_frame_bw: save bandwidth used by FS/LS eps in each FS frames
  * @ep_list: Endpoints using this TT
  */
 struct mu3h_sch_tt {
-       u32 fs_bus_bw[XHCI_MTK_MAX_ESIT];
+       u16 fs_bus_bw_out[XHCI_MTK_MAX_ESIT];
+       u16 fs_bus_bw_in[XHCI_MTK_MAX_ESIT];
+       u8 ls_bus_bw[XHCI_MTK_MAX_ESIT];
+       u16 fs_frame_bw[XHCI_MTK_FRAMES_CNT];
        struct list_head ep_list;
 };
 
@@ -58,7 +67,6 @@ struct mu3h_sch_bw_info {
  * @num_esit: number of @esit in a period
  * @num_budget_microframes: number of continuous uframes
  *             (@repeat==1) scheduled within the interval
- * @bw_cost_per_microframe: bandwidth cost per microframe
  * @hentry: hash table entry
  * @endpoint: linked into bandwidth domain which it belongs to
  * @tt_endpoint: linked into mu3h_sch_tt's list which it belongs to
@@ -83,12 +91,12 @@ struct mu3h_sch_bw_info {
  *             times; 1: distribute the (bMaxBurst+1)*(Mult+1) packets
  *             according to @pkts and @repeat. normal mode is used by
  *             default
+ * @bw_budget_table: table to record bandwidth budget per microframe
  */
 struct mu3h_sch_ep_info {
        u32 esit;
        u32 num_esit;
        u32 num_budget_microframes;
-       u32 bw_cost_per_microframe;
        struct list_head endpoint;
        struct hlist_node hentry;
        struct list_head tt_endpoint;
@@ -108,6 +116,7 @@ struct mu3h_sch_ep_info {
        u32 pkts;
        u32 cs_count;
        u32 burst_mode;
+       u32 bw_budget_table[];
 };
 
 #define MU3C_U3_PORT_MAX 4
index b9ae5c2a25275f6394d5de172614f43a9392d8fd..95ed9404f6f8520678ccf3a220a8dad8fff82d98 100644 (file)
@@ -535,6 +535,8 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci)
        /* xHC spec requires PCI devices to support D3hot and D3cold */
        if (xhci->hci_version >= 0x120)
                xhci->quirks |= XHCI_DEFAULT_PM_RUNTIME_ALLOW;
+       else if (pdev->vendor == PCI_VENDOR_ID_AMD && xhci->hci_version >= 0x110)
+               xhci->quirks |= XHCI_DEFAULT_PM_RUNTIME_ALLOW;
 
        if (xhci->quirks & XHCI_RESET_ON_RESUME)
                xhci_dbg_trace(xhci, trace_xhci_dbg_quirks,
@@ -693,7 +695,9 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
        /* USB-2 and USB-3 roothubs initialized, allow runtime pm suspend */
        pm_runtime_put_noidle(&dev->dev);
 
-       if (xhci->quirks & XHCI_DEFAULT_PM_RUNTIME_ALLOW)
+       if (pci_choose_state(dev, PMSG_SUSPEND) == PCI_D0)
+               pm_runtime_forbid(&dev->dev);
+       else if (xhci->quirks & XHCI_DEFAULT_PM_RUNTIME_ALLOW)
                pm_runtime_allow(&dev->dev);
 
        dma_set_max_seg_size(&dev->dev, UINT_MAX);
index 28218c8f183768efac576b035c6cce15380bc447..b93161374293b3b9f272b9a9c77a282a3a5795b1 100644 (file)
@@ -458,23 +458,38 @@ static int __maybe_unused xhci_plat_resume(struct device *dev)
        int ret;
 
        if (!device_may_wakeup(dev) && (xhci->quirks & XHCI_SUSPEND_RESUME_CLKS)) {
-               clk_prepare_enable(xhci->clk);
-               clk_prepare_enable(xhci->reg_clk);
+               ret = clk_prepare_enable(xhci->clk);
+               if (ret)
+                       return ret;
+
+               ret = clk_prepare_enable(xhci->reg_clk);
+               if (ret) {
+                       clk_disable_unprepare(xhci->clk);
+                       return ret;
+               }
        }
 
        ret = xhci_priv_resume_quirk(hcd);
        if (ret)
-               return ret;
+               goto disable_clks;
 
        ret = xhci_resume(xhci, PMSG_RESUME);
        if (ret)
-               return ret;
+               goto disable_clks;
 
        pm_runtime_disable(dev);
        pm_runtime_set_active(dev);
        pm_runtime_enable(dev);
 
        return 0;
+
+disable_clks:
+       if (!device_may_wakeup(dev) && (xhci->quirks & XHCI_SUSPEND_RESUME_CLKS)) {
+               clk_disable_unprepare(xhci->clk);
+               clk_disable_unprepare(xhci->reg_clk);
+       }
+
+       return ret;
 }
 
 static int __maybe_unused xhci_plat_runtime_suspend(struct device *dev)
index 3e5dc0723a8fc2aa73348fd6c59c4dc442ea6989..f3b5e6345858c4485c321c674559c8a60f3221eb 100644 (file)
@@ -144,7 +144,7 @@ static void next_trb(struct xhci_hcd *xhci,
                struct xhci_segment **seg,
                union xhci_trb **trb)
 {
-       if (trb_is_link(*trb)) {
+       if (trb_is_link(*trb) || last_trb_on_seg(*seg, *trb)) {
                *seg = (*seg)->next;
                *trb = ((*seg)->trbs);
        } else {
@@ -450,8 +450,9 @@ static int xhci_abort_cmd_ring(struct xhci_hcd *xhci, unsigned long flags)
         * In the future we should distinguish between -ENODEV and -ETIMEDOUT
         * and try to recover a -ETIMEDOUT with a host controller reset.
         */
-       ret = xhci_handshake(&xhci->op_regs->cmd_ring,
-                       CMD_RING_RUNNING, 0, 5 * 1000 * 1000);
+       ret = xhci_handshake_check_state(xhci, &xhci->op_regs->cmd_ring,
+                       CMD_RING_RUNNING, 0, 5 * 1000 * 1000,
+                       XHCI_STATE_REMOVING);
        if (ret < 0) {
                xhci_err(xhci, "Abort failed to stop command ring: %d\n", ret);
                xhci_halt(xhci);
@@ -1879,7 +1880,6 @@ static void handle_port_status(struct xhci_hcd *xhci,
        if ((port_id <= 0) || (port_id > max_ports)) {
                xhci_warn(xhci, "Port change event with invalid port ID %d\n",
                          port_id);
-               inc_deq(xhci, ir->event_ring);
                return;
        }
 
@@ -1906,7 +1906,7 @@ static void handle_port_status(struct xhci_hcd *xhci,
        xhci_dbg(xhci, "Port change event, %d-%d, id %d, portsc: 0x%x\n",
                 hcd->self.busnum, hcd_portnum + 1, port_id, portsc);
 
-       trace_xhci_handle_port_status(hcd_portnum, portsc);
+       trace_xhci_handle_port_status(port, portsc);
 
        if (hcd->state == HC_STATE_SUSPENDED) {
                xhci_dbg(xhci, "resume root hub\n");
@@ -2007,8 +2007,6 @@ static void handle_port_status(struct xhci_hcd *xhci,
        }
 
 cleanup:
-       /* Update event ring dequeue pointer before dropping the lock */
-       inc_deq(xhci, ir->event_ring);
 
        /* Don't make the USB core poll the roothub if we got a bad port status
         * change event.  Besides, at that point we can't tell which roothub
@@ -2884,13 +2882,6 @@ cleanup:
                        trb_comp_code != COMP_MISSED_SERVICE_ERROR &&
                        trb_comp_code != COMP_NO_PING_RESPONSE_ERROR;
 
-               /*
-                * Do not update event ring dequeue pointer if we're in a loop
-                * processing missed tds.
-                */
-               if (!handling_skipped_tds)
-                       inc_deq(xhci, ir->event_ring);
-
        /*
         * If ep->skip is set, it means there are missed tds on the
         * endpoint ring need to take care of.
@@ -2922,9 +2913,7 @@ err_out:
 static int xhci_handle_event(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
 {
        union xhci_trb *event;
-       int update_ptrs = 1;
        u32 trb_type;
-       int ret;
 
        /* Event ring hasn't been allocated yet. */
        if (!ir || !ir->event_ring || !ir->event_ring->dequeue) {
@@ -2954,12 +2943,9 @@ static int xhci_handle_event(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
                break;
        case TRB_PORT_STATUS:
                handle_port_status(xhci, ir, event);
-               update_ptrs = 0;
                break;
        case TRB_TRANSFER:
-               ret = handle_tx_event(xhci, ir, &event->trans_event);
-               if (ret >= 0)
-                       update_ptrs = 0;
+               handle_tx_event(xhci, ir, &event->trans_event);
                break;
        case TRB_DEV_NOTE:
                handle_device_notification(xhci, event);
@@ -2979,9 +2965,8 @@ static int xhci_handle_event(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
                return 0;
        }
 
-       if (update_ptrs)
-               /* Update SW event ring dequeue pointer */
-               inc_deq(xhci, ir->event_ring);
+       /* Update SW event ring dequeue pointer */
+       inc_deq(xhci, ir->event_ring);
 
        /* Are there more items on the event ring?  Caller will call us again to
         * check.
@@ -3013,13 +2998,12 @@ static void xhci_update_erst_dequeue(struct xhci_hcd *xhci,
                 * Per 4.9.4, Software writes to the ERDP register shall
                 * always advance the Event Ring Dequeue Pointer value.
                 */
-               if ((temp_64 & (u64) ~ERST_PTR_MASK) ==
-                               ((u64) deq & (u64) ~ERST_PTR_MASK))
+               if ((temp_64 & ERST_PTR_MASK) == (deq & ERST_PTR_MASK))
                        return;
 
                /* Update HC event ring dequeue pointer */
-               temp_64 &= ERST_DESI_MASK;
-               temp_64 |= ((u64) deq & (u64) ~ERST_PTR_MASK);
+               temp_64 = ir->event_ring->deq_seg->num & ERST_DESI_MASK;
+               temp_64 |= deq & ERST_PTR_MASK;
        }
 
        /* Clear the event handler busy flag (RW1C) */
index d6b32f2ad90ecbc408b5a6723fdcf7c0a9c74814..ac47b1c0544a6836882970add7a7cb50ed54678f 100644 (file)
@@ -509,35 +509,38 @@ DEFINE_EVENT(xhci_log_ring, xhci_inc_deq,
 );
 
 DECLARE_EVENT_CLASS(xhci_log_portsc,
-                   TP_PROTO(u32 portnum, u32 portsc),
-                   TP_ARGS(portnum, portsc),
+                   TP_PROTO(struct xhci_port *port, u32 portsc),
+                   TP_ARGS(port, portsc),
                    TP_STRUCT__entry(
+                                    __field(u32, busnum)
                                     __field(u32, portnum)
                                     __field(u32, portsc)
                                     ),
                    TP_fast_assign(
-                                  __entry->portnum = portnum;
+                                  __entry->busnum = port->rhub->hcd->self.busnum;
+                                  __entry->portnum = port->hcd_portnum;
                                   __entry->portsc = portsc;
                                   ),
-                   TP_printk("port-%d: %s",
+                   TP_printk("port %d-%d: %s",
+                             __entry->busnum,
                              __entry->portnum,
                              xhci_decode_portsc(__get_buf(XHCI_MSG_MAX), __entry->portsc)
                              )
 );
 
 DEFINE_EVENT(xhci_log_portsc, xhci_handle_port_status,
-            TP_PROTO(u32 portnum, u32 portsc),
-            TP_ARGS(portnum, portsc)
+            TP_PROTO(struct xhci_port *port, u32 portsc),
+            TP_ARGS(port, portsc)
 );
 
 DEFINE_EVENT(xhci_log_portsc, xhci_get_port_status,
-            TP_PROTO(u32 portnum, u32 portsc),
-            TP_ARGS(portnum, portsc)
+            TP_PROTO(struct xhci_port *port, u32 portsc),
+            TP_ARGS(port, portsc)
 );
 
 DEFINE_EVENT(xhci_log_portsc, xhci_hub_status_data,
-            TP_PROTO(u32 portnum, u32 portsc),
-            TP_ARGS(portnum, portsc)
+            TP_PROTO(struct xhci_port *port, u32 portsc),
+            TP_ARGS(port, portsc)
 );
 
 DECLARE_EVENT_CLASS(xhci_log_doorbell,
index e1b1b64a07232996d7e91b5fab1ba06fb48304b6..884b0898d9c9577c4554ac9559a3a064caefd936 100644 (file)
@@ -81,6 +81,29 @@ int xhci_handshake(void __iomem *ptr, u32 mask, u32 done, u64 timeout_us)
        return ret;
 }
 
+/*
+ * xhci_handshake_check_state - same as xhci_handshake but takes an additional
+ * exit_state parameter, and bails out with an error immediately when xhc_state
+ * has exit_state flag set.
+ */
+int xhci_handshake_check_state(struct xhci_hcd *xhci, void __iomem *ptr,
+               u32 mask, u32 done, int usec, unsigned int exit_state)
+{
+       u32     result;
+       int     ret;
+
+       ret = readl_poll_timeout_atomic(ptr, result,
+                               (result & mask) == done ||
+                               result == U32_MAX ||
+                               xhci->xhc_state & exit_state,
+                               1, usec);
+
+       if (result == U32_MAX || xhci->xhc_state & exit_state)
+               return -ENODEV;
+
+       return ret;
+}
+
 /*
  * Disable interrupts and begin the xHCI halting process.
  */
@@ -201,7 +224,8 @@ int xhci_reset(struct xhci_hcd *xhci, u64 timeout_us)
        if (xhci->quirks & XHCI_INTEL_HOST)
                udelay(1000);
 
-       ret = xhci_handshake(&xhci->op_regs->command, CMD_RESET, 0, timeout_us);
+       ret = xhci_handshake_check_state(xhci, &xhci->op_regs->command,
+                               CMD_RESET, 0, timeout_us, XHCI_STATE_REMOVING);
        if (ret)
                return ret;
 
@@ -520,7 +544,7 @@ int xhci_run(struct usb_hcd *hcd)
        xhci_dbg_trace(xhci, trace_xhci_dbg_init, "xhci_run");
 
        temp_64 = xhci_read_64(xhci, &ir->ir_set->erst_dequeue);
-       temp_64 &= ~ERST_PTR_MASK;
+       temp_64 &= ERST_PTR_MASK;
        xhci_dbg_trace(xhci, trace_xhci_dbg_init,
                        "ERST deq = 64'h%0lx", (long unsigned int) temp_64);
 
@@ -968,6 +992,7 @@ int xhci_resume(struct xhci_hcd *xhci, pm_message_t msg)
        int                     retval = 0;
        bool                    comp_timer_running = false;
        bool                    pending_portevent = false;
+       bool                    suspended_usb3_devs = false;
        bool                    reinit_xhc = false;
 
        if (!hcd->state)
@@ -1115,10 +1140,17 @@ int xhci_resume(struct xhci_hcd *xhci, pm_message_t msg)
                /*
                 * Resume roothubs only if there are pending events.
                 * USB 3 devices resend U3 LFPS wake after a 100ms delay if
-                * the first wake signalling failed, give it that chance.
+                * the first wake signalling failed, give it that chance if
+                * there are suspended USB 3 devices.
                 */
+               if (xhci->usb3_rhub.bus_state.suspended_ports ||
+                   xhci->usb3_rhub.bus_state.bus_suspended)
+                       suspended_usb3_devs = true;
+
                pending_portevent = xhci_pending_portevent(xhci);
-               if (!pending_portevent && msg.event == PM_EVENT_AUTO_RESUME) {
+
+               if (suspended_usb3_devs && !pending_portevent &&
+                   msg.event == PM_EVENT_AUTO_RESUME) {
                        msleep(120);
                        pending_portevent = xhci_pending_portevent(xhci);
                }
index 3d0451f5880ab7c55fe1ca85f39fa63fbb8f44ff..3ea5c092bba7176fbbbf01aa597107178ececb31 100644 (file)
@@ -525,7 +525,7 @@ struct xhci_intr_reg {
  * a work queue (or delayed service routine)?
  */
 #define ERST_EHB               (1 << 3)
-#define ERST_PTR_MASK          (0xf)
+#define ERST_PTR_MASK          (GENMASK_ULL(63, 4))
 
 /**
  * struct xhci_run_regs
@@ -558,33 +558,6 @@ struct xhci_doorbell_array {
 #define DB_VALUE(ep, stream)   ((((ep) + 1) & 0xff) | ((stream) << 16))
 #define DB_VALUE_HOST          0x00000000
 
-/**
- * struct xhci_protocol_caps
- * @revision:          major revision, minor revision, capability ID,
- *                     and next capability pointer.
- * @name_string:       Four ASCII characters to say which spec this xHC
- *                     follows, typically "USB ".
- * @port_info:         Port offset, count, and protocol-defined information.
- */
-struct xhci_protocol_caps {
-       u32     revision;
-       u32     name_string;
-       u32     port_info;
-};
-
-#define        XHCI_EXT_PORT_MAJOR(x)  (((x) >> 24) & 0xff)
-#define        XHCI_EXT_PORT_MINOR(x)  (((x) >> 16) & 0xff)
-#define        XHCI_EXT_PORT_PSIC(x)   (((x) >> 28) & 0x0f)
-#define        XHCI_EXT_PORT_OFF(x)    ((x) & 0xff)
-#define        XHCI_EXT_PORT_COUNT(x)  (((x) >> 8) & 0xff)
-
-#define        XHCI_EXT_PORT_PSIV(x)   (((x) >> 0) & 0x0f)
-#define        XHCI_EXT_PORT_PSIE(x)   (((x) >> 4) & 0x03)
-#define        XHCI_EXT_PORT_PLT(x)    (((x) >> 6) & 0x03)
-#define        XHCI_EXT_PORT_PFD(x)    (((x) >> 8) & 0x01)
-#define        XHCI_EXT_PORT_LP(x)     (((x) >> 14) & 0x03)
-#define        XHCI_EXT_PORT_PSIM(x)   (((x) >> 16) & 0xffff)
-
 #define PLT_MASK        (0x03 << 6)
 #define PLT_SYM         (0x00 << 6)
 #define PLT_ASYM_RX     (0x02 << 6)
@@ -1545,6 +1518,7 @@ struct xhci_segment {
        union xhci_trb          *trbs;
        /* private to HCD */
        struct xhci_segment     *next;
+       unsigned int            num;
        dma_addr_t              dma;
        /* Max packet sized bounce buffer for td-fragmant alignment */
        dma_addr_t              bounce_dma;
@@ -1669,12 +1643,8 @@ struct urb_priv {
        struct  xhci_td td[] __counted_by(num_tds);
 };
 
-/*
- * Each segment table entry is 4*32bits long.  1K seems like an ok size:
- * (1K bytes * 8bytes/bit) / (4*32 bits) = 64 segment entries in the table,
- * meaning 64 ring segments.
- * Initial allocated size of the ERST, in number of entries */
-#define        ERST_NUM_SEGS   1
+/* Reasonable limit for number of Event Ring segments (spec allows 32k) */
+#define        ERST_MAX_SEGS   2
 /* Poll every 60 seconds */
 #define        POLL_TIMEOUT    60
 /* Stop endpoint command timeout (secs) for URB cancellation watchdog timer */
@@ -2078,13 +2048,8 @@ struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci,
 void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring);
 int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring,
                unsigned int num_trbs, gfp_t flags);
-int xhci_alloc_erst(struct xhci_hcd *xhci,
-               struct xhci_ring *evt_ring,
-               struct xhci_erst *erst,
-               gfp_t flags);
 void xhci_initialize_ring_info(struct xhci_ring *ring,
                        unsigned int cycle_state);
-void xhci_free_erst(struct xhci_hcd *xhci, struct xhci_erst *erst);
 void xhci_free_endpoint_ring(struct xhci_hcd *xhci,
                struct xhci_virt_device *virt_dev,
                unsigned int ep_index);
@@ -2119,6 +2084,8 @@ void xhci_free_container_ctx(struct xhci_hcd *xhci,
 /* xHCI host controller glue */
 typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *);
 int xhci_handshake(void __iomem *ptr, u32 mask, u32 done, u64 timeout_us);
+int xhci_handshake_check_state(struct xhci_hcd *xhci, void __iomem *ptr,
+               u32 mask, u32 done, int usec, unsigned int exit_state);
 void xhci_quiesce(struct xhci_hcd *xhci);
 int xhci_halt(struct xhci_hcd *xhci);
 int xhci_start(struct xhci_hcd *xhci);
index 99b15b77dfd576dace539b7166dd574fe134fcdf..c510af7baa0d6d73377826ea6f975bbe0c25c878 100644 (file)
@@ -165,6 +165,19 @@ config APPLE_MFI_FASTCHARGE
 
          It is safe to say M here.
 
+config USB_LJCA
+       tristate "Intel La Jolla Cove Adapter support"
+       select AUXILIARY_BUS
+       depends on USB && ACPI
+       help
+         This adds support for Intel La Jolla Cove USB-I2C/SPI/GPIO
+         Master Adapter (LJCA). Additional drivers such as I2C_LJCA,
+         GPIO_LJCA and SPI_LJCA must be enabled in order to use the
+         functionality of the device.
+
+         This driver can also be built as a module. If so, the module
+         will be called usb-ljca.
+
 source "drivers/usb/misc/sisusbvga/Kconfig"
 
 config USB_LD
index 1992cc284d8aba91f2229810d6b1d9e3b663ef2e..0bc732bcb162b8b6049ccc3cf706705d2ae1e260 100644 (file)
@@ -11,6 +11,7 @@ obj-$(CONFIG_USB_EMI26)                       += emi26.o
 obj-$(CONFIG_USB_EMI62)                        += emi62.o
 obj-$(CONFIG_USB_EZUSB_FX2)            += ezusb.o
 obj-$(CONFIG_APPLE_MFI_FASTCHARGE)     += apple-mfi-fastcharge.o
+obj-$(CONFIG_USB_LJCA)                 += usb-ljca.o
 obj-$(CONFIG_USB_IDMOUSE)              += idmouse.o
 obj-$(CONFIG_USB_IOWARRIOR)            += iowarrior.o
 obj-$(CONFIG_USB_ISIGHTFW)             += isight_firmware.o
index 57bbe130909480a688cd92634d7ee0396752f5dd..a341b2fbb7b44f9a02a29b6ec7972be69caf9d17 100644 (file)
@@ -240,7 +240,6 @@ static void onboard_hub_attach_usb_driver(struct work_struct *work)
 
 static int onboard_hub_probe(struct platform_device *pdev)
 {
-       const struct of_device_id *of_id;
        struct device *dev = &pdev->dev;
        struct onboard_hub *hub;
        unsigned int i;
@@ -250,11 +249,7 @@ static int onboard_hub_probe(struct platform_device *pdev)
        if (!hub)
                return -ENOMEM;
 
-       of_id = of_match_device(onboard_hub_match, &pdev->dev);
-       if (!of_id)
-               return -ENODEV;
-
-       hub->pdata = of_id->data;
+       hub->pdata = device_get_match_data(&pdev->dev);
        if (!hub->pdata)
                return -EINVAL;
 
index 2a4ab5ac0ebed94e98a10a587c0a9134ed8ccb2d..c4e24a7b92904cc060d398c55d8b396239c813f7 100644 (file)
@@ -57,6 +57,7 @@ static const struct of_device_id onboard_hub_match[] = {
        { .compatible = "usb5e3,608", .data = &genesys_gl850g_data, },
        { .compatible = "usb5e3,610", .data = &genesys_gl852g_data, },
        { .compatible = "usb5e3,620", .data = &genesys_gl852g_data, },
+       { .compatible = "usb5e3,626", .data = &genesys_gl852g_data, },
        { .compatible = "usbbda,411", .data = &realtek_rts5411_data, },
        { .compatible = "usbbda,5411", .data = &realtek_rts5411_data, },
        { .compatible = "usbbda,414", .data = &realtek_rts5411_data, },
diff --git a/drivers/usb/misc/usb-ljca.c b/drivers/usb/misc/usb-ljca.c
new file mode 100644 (file)
index 0000000..c9decd0
--- /dev/null
@@ -0,0 +1,902 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel La Jolla Cove Adapter USB driver
+ *
+ * Copyright (c) 2023, Intel Corporation.
+ */
+
+#include <linux/acpi.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/dev_printk.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/usb.h>
+#include <linux/usb/ljca.h>
+
+#include <asm/unaligned.h>
+
+/* command flags */
+#define LJCA_ACK_FLAG                  BIT(0)
+#define LJCA_RESP_FLAG                 BIT(1)
+#define LJCA_CMPL_FLAG                 BIT(2)
+
+#define LJCA_MAX_PACKET_SIZE           64u
+#define LJCA_MAX_PAYLOAD_SIZE          \
+               (LJCA_MAX_PACKET_SIZE - sizeof(struct ljca_msg))
+
+#define LJCA_WRITE_TIMEOUT_MS          200
+#define LJCA_WRITE_ACK_TIMEOUT_MS      500
+#define LJCA_ENUM_CLIENT_TIMEOUT_MS    20
+
+/* ljca client type */
+enum ljca_client_type {
+       LJCA_CLIENT_MNG = 1,
+       LJCA_CLIENT_GPIO = 3,
+       LJCA_CLIENT_I2C = 4,
+       LJCA_CLIENT_SPI = 5,
+};
+
+/* MNG client commands */
+enum ljca_mng_cmd {
+       LJCA_MNG_RESET = 2,
+       LJCA_MNG_ENUM_GPIO = 4,
+       LJCA_MNG_ENUM_I2C = 5,
+       LJCA_MNG_ENUM_SPI = 8,
+};
+
+/* ljca client acpi _ADR */
+enum ljca_client_acpi_adr {
+       LJCA_GPIO_ACPI_ADR,
+       LJCA_I2C1_ACPI_ADR,
+       LJCA_I2C2_ACPI_ADR,
+       LJCA_SPI1_ACPI_ADR,
+       LJCA_SPI2_ACPI_ADR,
+       LJCA_CLIENT_ACPI_ADR_MAX,
+};
+
+/* ljca cmd message structure */
+struct ljca_msg {
+       u8 type;
+       u8 cmd;
+       u8 flags;
+       u8 len;
+       u8 data[] __counted_by(len);
+} __packed;
+
+struct ljca_i2c_ctr_info {
+       u8 id;
+       u8 capacity;
+       u8 intr_pin;
+} __packed;
+
+struct ljca_i2c_descriptor {
+       u8 num;
+       struct ljca_i2c_ctr_info info[] __counted_by(num);
+} __packed;
+
+struct ljca_spi_ctr_info {
+       u8 id;
+       u8 capacity;
+       u8 intr_pin;
+} __packed;
+
+struct ljca_spi_descriptor {
+       u8 num;
+       struct ljca_spi_ctr_info info[] __counted_by(num);
+} __packed;
+
+struct ljca_bank_descriptor {
+       u8 bank_id;
+       u8 pin_num;
+
+       /* 1 bit for each gpio, 1 means valid */
+       __le32 valid_pins;
+} __packed;
+
+struct ljca_gpio_descriptor {
+       u8 pins_per_bank;
+       u8 bank_num;
+       struct ljca_bank_descriptor bank_desc[] __counted_by(bank_num);
+} __packed;
+
+/**
+ * struct ljca_adapter - represent a ljca adapter
+ *
+ * @intf: the usb interface for this ljca adapter
+ * @usb_dev: the usb device for this ljca adapter
+ * @dev: the specific device info of the usb interface
+ * @rx_pipe: bulk in pipe for receive data from firmware
+ * @tx_pipe: bulk out pipe for send data to firmware
+ * @rx_urb: urb used for the bulk in pipe
+ * @rx_buf: buffer used to receive command response and event
+ * @rx_len: length of rx buffer
+ * @ex_buf: external buffer to save command response
+ * @ex_buf_len: length of external buffer
+ * @actual_length: actual length of data copied to external buffer
+ * @tx_buf: buffer used to download command to firmware
+ * @tx_buf_len: length of tx buffer
+ * @lock: spinlock to protect tx_buf and ex_buf
+ * @cmd_completion: completion object as the command receives ack
+ * @mutex: mutex to avoid command download concurrently
+ * @client_list: client device list
+ * @disconnect: usb disconnect ongoing or not
+ * @reset_id: used to reset firmware
+ */
+struct ljca_adapter {
+       struct usb_interface *intf;
+       struct usb_device *usb_dev;
+       struct device *dev;
+
+       unsigned int rx_pipe;
+       unsigned int tx_pipe;
+
+       struct urb *rx_urb;
+       void *rx_buf;
+       unsigned int rx_len;
+
+       u8 *ex_buf;
+       u8 ex_buf_len;
+       u8 actual_length;
+
+       void *tx_buf;
+       u8 tx_buf_len;
+
+       spinlock_t lock;
+
+       struct completion cmd_completion;
+       struct mutex mutex;
+
+       struct list_head client_list;
+
+       bool disconnect;
+
+       u32 reset_id;
+};
+
+struct ljca_match_ids_walk_data {
+       const struct acpi_device_id *ids;
+       const char *uid;
+       struct acpi_device *adev;
+};
+
+static const struct acpi_device_id ljca_gpio_hids[] = {
+       { "INTC1074" },
+       { "INTC1096" },
+       { "INTC100B" },
+       { "INTC10D1" },
+       {},
+};
+
+static const struct acpi_device_id ljca_i2c_hids[] = {
+       { "INTC1075" },
+       { "INTC1097" },
+       { "INTC100C" },
+       { "INTC10D2" },
+       {},
+};
+
+static const struct acpi_device_id ljca_spi_hids[] = {
+       { "INTC1091" },
+       { "INTC1098" },
+       { "INTC100D" },
+       { "INTC10D3" },
+       {},
+};
+
+static void ljca_handle_event(struct ljca_adapter *adap,
+                             struct ljca_msg *header)
+{
+       struct ljca_client *client;
+
+       list_for_each_entry(client, &adap->client_list, link) {
+               /*
+                * Currently only GPIO register event callback, but
+                * firmware message structure should include id when
+                * multiple same type clients register event callback.
+                */
+               if (client->type == header->type) {
+                       unsigned long flags;
+
+                       spin_lock_irqsave(&client->event_cb_lock, flags);
+                       client->event_cb(client->context, header->cmd,
+                                        header->data, header->len);
+                       spin_unlock_irqrestore(&client->event_cb_lock, flags);
+
+                       break;
+               }
+       }
+}
+
+/* process command ack and received data if available */
+static void ljca_handle_cmd_ack(struct ljca_adapter *adap, struct ljca_msg *header)
+{
+       struct ljca_msg *tx_header = adap->tx_buf;
+       u8 ibuf_len, actual_len = 0;
+       unsigned long flags;
+       u8 *ibuf;
+
+       spin_lock_irqsave(&adap->lock, flags);
+
+       if (tx_header->type != header->type || tx_header->cmd != header->cmd) {
+               spin_unlock_irqrestore(&adap->lock, flags);
+               dev_err(adap->dev, "cmd ack mismatch error\n");
+               return;
+       }
+
+       ibuf_len = adap->ex_buf_len;
+       ibuf = adap->ex_buf;
+
+       if (ibuf && ibuf_len) {
+               actual_len = min(header->len, ibuf_len);
+
+               /* copy received data to external buffer */
+               memcpy(ibuf, header->data, actual_len);
+       }
+       /* update copied data length */
+       adap->actual_length = actual_len;
+
+       spin_unlock_irqrestore(&adap->lock, flags);
+
+       complete(&adap->cmd_completion);
+}
+
+static void ljca_recv(struct urb *urb)
+{
+       struct ljca_msg *header = urb->transfer_buffer;
+       struct ljca_adapter *adap = urb->context;
+       int ret;
+
+       switch (urb->status) {
+       case 0:
+               /* success */
+               break;
+       case -ENOENT:
+               /*
+                * directly complete the possible ongoing transfer
+                * during disconnect
+                */
+               if (adap->disconnect)
+                       complete(&adap->cmd_completion);
+               return;
+       case -ECONNRESET:
+       case -ESHUTDOWN:
+       case -EPIPE:
+               /* rx urb is terminated */
+               dev_dbg(adap->dev, "rx urb terminated with status: %d\n",
+                       urb->status);
+               return;
+       default:
+               dev_dbg(adap->dev, "rx urb error: %d\n", urb->status);
+               goto resubmit;
+       }
+
+       if (header->len + sizeof(*header) != urb->actual_length)
+               goto resubmit;
+
+       if (header->flags & LJCA_ACK_FLAG)
+               ljca_handle_cmd_ack(adap, header);
+       else
+               ljca_handle_event(adap, header);
+
+resubmit:
+       ret = usb_submit_urb(urb, GFP_ATOMIC);
+       if (ret && ret != -EPERM)
+               dev_err(adap->dev, "resubmit rx urb error %d\n", ret);
+}
+
+static int ljca_send(struct ljca_adapter *adap, u8 type, u8 cmd,
+                    const u8 *obuf, u8 obuf_len, u8 *ibuf, u8 ibuf_len,
+                    bool ack, unsigned long timeout)
+{
+       unsigned int msg_len = sizeof(struct ljca_msg) + obuf_len;
+       struct ljca_msg *header = adap->tx_buf;
+       unsigned int transferred;
+       unsigned long flags;
+       int ret;
+
+       if (adap->disconnect)
+               return -ENODEV;
+
+       if (msg_len > adap->tx_buf_len)
+               return -EINVAL;
+
+       mutex_lock(&adap->mutex);
+
+       spin_lock_irqsave(&adap->lock, flags);
+
+       header->type = type;
+       header->cmd = cmd;
+       header->len = obuf_len;
+       if (obuf)
+               memcpy(header->data, obuf, obuf_len);
+
+       header->flags = LJCA_CMPL_FLAG | (ack ? LJCA_ACK_FLAG : 0);
+
+       adap->ex_buf = ibuf;
+       adap->ex_buf_len = ibuf_len;
+       adap->actual_length = 0;
+
+       spin_unlock_irqrestore(&adap->lock, flags);
+
+       reinit_completion(&adap->cmd_completion);
+
+       ret = usb_autopm_get_interface(adap->intf);
+       if (ret < 0)
+               goto out;
+
+       ret = usb_bulk_msg(adap->usb_dev, adap->tx_pipe, header,
+                          msg_len, &transferred, LJCA_WRITE_TIMEOUT_MS);
+
+       usb_autopm_put_interface(adap->intf);
+
+       if (ret < 0)
+               goto out;
+       if (transferred != msg_len) {
+               ret = -EIO;
+               goto out;
+       }
+
+       if (ack) {
+               ret = wait_for_completion_timeout(&adap->cmd_completion,
+                                                 timeout);
+               if (!ret) {
+                       ret = -ETIMEDOUT;
+                       goto out;
+               }
+       }
+       ret = adap->actual_length;
+
+out:
+       spin_lock_irqsave(&adap->lock, flags);
+       adap->ex_buf = NULL;
+       adap->ex_buf_len = 0;
+
+       memset(header, 0, sizeof(*header));
+       spin_unlock_irqrestore(&adap->lock, flags);
+
+       mutex_unlock(&adap->mutex);
+
+       return ret;
+}
+
+int ljca_transfer(struct ljca_client *client, u8 cmd, const u8 *obuf,
+                 u8 obuf_len, u8 *ibuf, u8 ibuf_len)
+{
+       return ljca_send(client->adapter, client->type, cmd,
+                        obuf, obuf_len, ibuf, ibuf_len, true,
+                        LJCA_WRITE_ACK_TIMEOUT_MS);
+}
+EXPORT_SYMBOL_NS_GPL(ljca_transfer, LJCA);
+
+int ljca_transfer_noack(struct ljca_client *client, u8 cmd, const u8 *obuf,
+                       u8 obuf_len)
+{
+       return ljca_send(client->adapter, client->type, cmd, obuf,
+                        obuf_len, NULL, 0, false, LJCA_WRITE_ACK_TIMEOUT_MS);
+}
+EXPORT_SYMBOL_NS_GPL(ljca_transfer_noack, LJCA);
+
+int ljca_register_event_cb(struct ljca_client *client, ljca_event_cb_t event_cb,
+                          void *context)
+{
+       unsigned long flags;
+
+       if (!event_cb)
+               return -EINVAL;
+
+       spin_lock_irqsave(&client->event_cb_lock, flags);
+
+       if (client->event_cb) {
+               spin_unlock_irqrestore(&client->event_cb_lock, flags);
+               return -EALREADY;
+       }
+
+       client->event_cb = event_cb;
+       client->context = context;
+
+       spin_unlock_irqrestore(&client->event_cb_lock, flags);
+
+       return 0;
+}
+EXPORT_SYMBOL_NS_GPL(ljca_register_event_cb, LJCA);
+
+void ljca_unregister_event_cb(struct ljca_client *client)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&client->event_cb_lock, flags);
+
+       client->event_cb = NULL;
+       client->context = NULL;
+
+       spin_unlock_irqrestore(&client->event_cb_lock, flags);
+}
+EXPORT_SYMBOL_NS_GPL(ljca_unregister_event_cb, LJCA);
+
+static int ljca_match_device_ids(struct acpi_device *adev, void *data)
+{
+       struct ljca_match_ids_walk_data *wd = data;
+       const char *uid = acpi_device_uid(adev);
+
+       if (acpi_match_device_ids(adev, wd->ids))
+               return 0;
+
+       if (!wd->uid)
+               goto match;
+
+       if (!uid)
+               /*
+                * Some DSDTs have only one ACPI companion for the two I2C
+                * controllers and they don't set a UID at all (e.g. Dell
+                * Latitude 9420). On these platforms only the first I2C
+                * controller is used, so if a HID match has no UID we use
+                * "0" as the UID and assign ACPI companion to the first
+                * I2C controller.
+                */
+               uid = "0";
+       else
+               uid = strchr(uid, wd->uid[0]);
+
+       if (!uid || strcmp(uid, wd->uid))
+               return 0;
+
+match:
+       wd->adev = adev;
+
+       return 1;
+}
+
+/* bind auxiliary device to acpi device */
+static void ljca_auxdev_acpi_bind(struct ljca_adapter *adap,
+                                 struct auxiliary_device *auxdev,
+                                 u64 adr, u8 id)
+{
+       struct ljca_match_ids_walk_data wd = { 0 };
+       struct acpi_device *parent, *adev;
+       struct device *dev = adap->dev;
+       char uid[4];
+
+       parent = ACPI_COMPANION(dev);
+       if (!parent)
+               return;
+
+       /*
+        * get auxdev ACPI handle from the ACPI device directly
+        * under the parent that matches _ADR.
+        */
+       adev = acpi_find_child_device(parent, adr, false);
+       if (adev) {
+               ACPI_COMPANION_SET(&auxdev->dev, adev);
+               return;
+       }
+
+       /*
+        * _ADR is a grey area in the ACPI specification, some
+        * platforms use _HID to distinguish children devices.
+        */
+       switch (adr) {
+       case LJCA_GPIO_ACPI_ADR:
+               wd.ids = ljca_gpio_hids;
+               break;
+       case LJCA_I2C1_ACPI_ADR:
+       case LJCA_I2C2_ACPI_ADR:
+               snprintf(uid, sizeof(uid), "%d", id);
+               wd.uid = uid;
+               wd.ids = ljca_i2c_hids;
+               break;
+       case LJCA_SPI1_ACPI_ADR:
+       case LJCA_SPI2_ACPI_ADR:
+               wd.ids = ljca_spi_hids;
+               break;
+       default:
+               dev_warn(dev, "unsupported _ADR\n");
+               return;
+       }
+
+       acpi_dev_for_each_child(parent, ljca_match_device_ids, &wd);
+       if (wd.adev) {
+               ACPI_COMPANION_SET(&auxdev->dev, wd.adev);
+               return;
+       }
+
+       parent = ACPI_COMPANION(dev->parent->parent);
+       if (!parent)
+               return;
+
+       acpi_dev_for_each_child(parent, ljca_match_device_ids, &wd);
+       if (wd.adev)
+               ACPI_COMPANION_SET(&auxdev->dev, wd.adev);
+}
+
+static void ljca_auxdev_release(struct device *dev)
+{
+       struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+
+       kfree(auxdev->dev.platform_data);
+}
+
+static int ljca_new_client_device(struct ljca_adapter *adap, u8 type, u8 id,
+                                 char *name, void *data, u64 adr)
+{
+       struct auxiliary_device *auxdev;
+       struct ljca_client *client;
+       int ret;
+
+       client = kzalloc(sizeof *client, GFP_KERNEL);
+       if (!client)
+               return -ENOMEM;
+
+       client->type = type;
+       client->id = id;
+       client->adapter = adap;
+       spin_lock_init(&client->event_cb_lock);
+
+       auxdev = &client->auxdev;
+       auxdev->name = name;
+       auxdev->id = id;
+
+       auxdev->dev.parent = adap->dev;
+       auxdev->dev.platform_data = data;
+       auxdev->dev.release = ljca_auxdev_release;
+
+       ret = auxiliary_device_init(auxdev);
+       if (ret)
+               goto err_free;
+
+       ljca_auxdev_acpi_bind(adap, auxdev, adr, id);
+
+       ret = auxiliary_device_add(auxdev);
+       if (ret)
+               goto err_uninit;
+
+       list_add_tail(&client->link, &adap->client_list);
+
+       return 0;
+
+err_uninit:
+       auxiliary_device_uninit(auxdev);
+
+err_free:
+       kfree(client);
+
+       return ret;
+}
+
+static int ljca_enumerate_gpio(struct ljca_adapter *adap)
+{
+       u32 valid_pin[LJCA_MAX_GPIO_NUM / BITS_PER_TYPE(u32)];
+       struct ljca_gpio_descriptor *desc;
+       struct ljca_gpio_info *gpio_info;
+       u8 buf[LJCA_MAX_PAYLOAD_SIZE];
+       int ret, gpio_num;
+       unsigned int i;
+
+       ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_ENUM_GPIO, NULL, 0, buf,
+                       sizeof(buf), true, LJCA_ENUM_CLIENT_TIMEOUT_MS);
+       if (ret < 0)
+               return ret;
+
+       /* check firmware response */
+       desc = (struct ljca_gpio_descriptor *)buf;
+       if (ret != struct_size(desc, bank_desc, desc->bank_num))
+               return -EINVAL;
+
+       gpio_num = desc->pins_per_bank * desc->bank_num;
+       if (gpio_num > LJCA_MAX_GPIO_NUM)
+               return -EINVAL;
+
+       /* construct platform data */
+       gpio_info = kzalloc(sizeof *gpio_info, GFP_KERNEL);
+       if (!gpio_info)
+               return -ENOMEM;
+       gpio_info->num = gpio_num;
+
+       for (i = 0; i < desc->bank_num; i++)
+               valid_pin[i] = get_unaligned_le32(&desc->bank_desc[i].valid_pins);
+       bitmap_from_arr32(gpio_info->valid_pin_map, valid_pin, gpio_num);
+
+       ret = ljca_new_client_device(adap, LJCA_CLIENT_GPIO, 0, "ljca-gpio",
+                                    gpio_info, LJCA_GPIO_ACPI_ADR);
+       if (ret)
+               kfree(gpio_info);
+
+       return ret;
+}
+
+static int ljca_enumerate_i2c(struct ljca_adapter *adap)
+{
+       struct ljca_i2c_descriptor *desc;
+       struct ljca_i2c_info *i2c_info;
+       u8 buf[LJCA_MAX_PAYLOAD_SIZE];
+       unsigned int i;
+       int ret;
+
+       ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_ENUM_I2C, NULL, 0, buf,
+                       sizeof(buf), true, LJCA_ENUM_CLIENT_TIMEOUT_MS);
+       if (ret < 0)
+               return ret;
+
+       /* check firmware response */
+       desc = (struct ljca_i2c_descriptor *)buf;
+       if (ret != struct_size(desc, info, desc->num))
+               return -EINVAL;
+
+       for (i = 0; i < desc->num; i++) {
+               /* construct platform data */
+               i2c_info = kzalloc(sizeof *i2c_info, GFP_KERNEL);
+               if (!i2c_info)
+                       return -ENOMEM;
+
+               i2c_info->id = desc->info[i].id;
+               i2c_info->capacity = desc->info[i].capacity;
+               i2c_info->intr_pin = desc->info[i].intr_pin;
+
+               ret = ljca_new_client_device(adap, LJCA_CLIENT_I2C, i,
+                                            "ljca-i2c", i2c_info,
+                                            LJCA_I2C1_ACPI_ADR + i);
+               if (ret) {
+                       kfree(i2c_info);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+static int ljca_enumerate_spi(struct ljca_adapter *adap)
+{
+       struct ljca_spi_descriptor *desc;
+       struct ljca_spi_info *spi_info;
+       u8 buf[LJCA_MAX_PAYLOAD_SIZE];
+       unsigned int i;
+       int ret;
+
+       ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_ENUM_SPI, NULL, 0, buf,
+                       sizeof(buf), true, LJCA_ENUM_CLIENT_TIMEOUT_MS);
+       if (ret < 0)
+               return ret;
+
+       /* check firmware response */
+       desc = (struct ljca_spi_descriptor *)buf;
+       if (ret != struct_size(desc, info, desc->num))
+               return -EINVAL;
+
+       for (i = 0; i < desc->num; i++) {
+               /* construct platform data */
+               spi_info = kzalloc(sizeof *spi_info, GFP_KERNEL);
+               if (!spi_info)
+                       return -ENOMEM;
+
+               spi_info->id = desc->info[i].id;
+               spi_info->capacity = desc->info[i].capacity;
+
+               ret = ljca_new_client_device(adap, LJCA_CLIENT_SPI, i,
+                                            "ljca-spi", spi_info,
+                                            LJCA_SPI1_ACPI_ADR + i);
+               if (ret) {
+                       kfree(spi_info);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+static int ljca_reset_handshake(struct ljca_adapter *adap)
+{
+       __le32 reset_id = cpu_to_le32(adap->reset_id);
+       __le32 reset_id_ret = 0;
+       int ret;
+
+       adap->reset_id++;
+
+       ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_RESET, (u8 *)&reset_id,
+                       sizeof(__le32), (u8 *)&reset_id_ret, sizeof(__le32),
+                       true, LJCA_WRITE_ACK_TIMEOUT_MS);
+       if (ret < 0)
+               return ret;
+
+       if (reset_id_ret != reset_id)
+               return -EINVAL;
+
+       return 0;
+}
+
+static int ljca_enumerate_clients(struct ljca_adapter *adap)
+{
+       struct ljca_client *client, *next;
+       int ret;
+
+       ret = ljca_reset_handshake(adap);
+       if (ret)
+               goto err_kill;
+
+       ret = ljca_enumerate_gpio(adap);
+       if (ret) {
+               dev_err(adap->dev, "enumerate GPIO error\n");
+               goto err_kill;
+       }
+
+       ret = ljca_enumerate_i2c(adap);
+       if (ret) {
+               dev_err(adap->dev, "enumerate I2C error\n");
+               goto err_kill;
+       }
+
+       ret = ljca_enumerate_spi(adap);
+       if (ret) {
+               dev_err(adap->dev, "enumerate SPI error\n");
+               goto err_kill;
+       }
+
+       return 0;
+
+err_kill:
+       adap->disconnect = true;
+
+       usb_kill_urb(adap->rx_urb);
+
+       list_for_each_entry_safe_reverse(client, next, &adap->client_list, link) {
+               auxiliary_device_delete(&client->auxdev);
+               auxiliary_device_uninit(&client->auxdev);
+
+               list_del_init(&client->link);
+               kfree(client);
+       }
+
+       return ret;
+}
+
+static int ljca_probe(struct usb_interface *interface,
+                     const struct usb_device_id *id)
+{
+       struct usb_device *usb_dev = interface_to_usbdev(interface);
+       struct usb_host_interface *alt = interface->cur_altsetting;
+       struct usb_endpoint_descriptor *ep_in, *ep_out;
+       struct device *dev = &interface->dev;
+       struct ljca_adapter *adap;
+       int ret;
+
+       adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL);
+       if (!adap)
+               return -ENOMEM;
+
+       /* separate tx buffer allocation for alignment */
+       adap->tx_buf = devm_kzalloc(dev, LJCA_MAX_PACKET_SIZE, GFP_KERNEL);
+       if (!adap->tx_buf)
+               return -ENOMEM;
+       adap->tx_buf_len = LJCA_MAX_PACKET_SIZE;
+
+       mutex_init(&adap->mutex);
+       spin_lock_init(&adap->lock);
+       init_completion(&adap->cmd_completion);
+       INIT_LIST_HEAD(&adap->client_list);
+
+       adap->intf = usb_get_intf(interface);
+       adap->usb_dev = usb_dev;
+       adap->dev = dev;
+
+       /*
+        * find the first bulk in and out endpoints.
+        * ignore any others.
+        */
+       ret = usb_find_common_endpoints(alt, &ep_in, &ep_out, NULL, NULL);
+       if (ret) {
+               dev_err(dev, "bulk endpoints not found\n");
+               goto err_put;
+       }
+       adap->rx_pipe = usb_rcvbulkpipe(usb_dev, usb_endpoint_num(ep_in));
+       adap->tx_pipe = usb_sndbulkpipe(usb_dev, usb_endpoint_num(ep_out));
+
+       /* setup rx buffer */
+       adap->rx_len = usb_endpoint_maxp(ep_in);
+       adap->rx_buf = devm_kzalloc(dev, adap->rx_len, GFP_KERNEL);
+       if (!adap->rx_buf) {
+               ret = -ENOMEM;
+               goto err_put;
+       }
+
+       /* alloc rx urb */
+       adap->rx_urb = usb_alloc_urb(0, GFP_KERNEL);
+       if (!adap->rx_urb) {
+               ret = -ENOMEM;
+               goto err_put;
+       }
+       usb_fill_bulk_urb(adap->rx_urb, usb_dev, adap->rx_pipe,
+                         adap->rx_buf, adap->rx_len, ljca_recv, adap);
+
+       usb_set_intfdata(interface, adap);
+
+       /* submit rx urb before enumerate clients */
+       ret = usb_submit_urb(adap->rx_urb, GFP_KERNEL);
+       if (ret) {
+               dev_err(dev, "submit rx urb failed: %d\n", ret);
+               goto err_free;
+       }
+
+       ret = ljca_enumerate_clients(adap);
+       if (ret)
+               goto err_free;
+
+       usb_enable_autosuspend(usb_dev);
+
+       return 0;
+
+err_free:
+       usb_free_urb(adap->rx_urb);
+
+err_put:
+       usb_put_intf(adap->intf);
+
+       mutex_destroy(&adap->mutex);
+
+       return ret;
+}
+
+static void ljca_disconnect(struct usb_interface *interface)
+{
+       struct ljca_adapter *adap = usb_get_intfdata(interface);
+       struct ljca_client *client, *next;
+
+       adap->disconnect = true;
+
+       usb_kill_urb(adap->rx_urb);
+
+       list_for_each_entry_safe_reverse(client, next, &adap->client_list, link) {
+               auxiliary_device_delete(&client->auxdev);
+               auxiliary_device_uninit(&client->auxdev);
+
+               list_del_init(&client->link);
+               kfree(client);
+       }
+
+       usb_free_urb(adap->rx_urb);
+
+       usb_put_intf(adap->intf);
+
+       mutex_destroy(&adap->mutex);
+}
+
+static int ljca_suspend(struct usb_interface *interface, pm_message_t message)
+{
+       struct ljca_adapter *adap = usb_get_intfdata(interface);
+
+       usb_kill_urb(adap->rx_urb);
+
+       return 0;
+}
+
+static int ljca_resume(struct usb_interface *interface)
+{
+       struct ljca_adapter *adap = usb_get_intfdata(interface);
+
+       return usb_submit_urb(adap->rx_urb, GFP_KERNEL);
+}
+
+static const struct usb_device_id ljca_table[] = {
+       { USB_DEVICE(0x8086, 0x0b63) },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(usb, ljca_table);
+
+static struct usb_driver ljca_driver = {
+       .name = "ljca",
+       .id_table = ljca_table,
+       .probe = ljca_probe,
+       .disconnect = ljca_disconnect,
+       .suspend = ljca_suspend,
+       .resume = ljca_resume,
+       .supports_autosuspend = 1,
+};
+module_usb_driver(ljca_driver);
+
+MODULE_AUTHOR("Wentong Wu <wentong.wu@intel.com>");
+MODULE_AUTHOR("Zhifeng Wang <zhifeng.wang@intel.com>");
+MODULE_AUTHOR("Lixu Zhang <lixu.zhang@intel.com>");
+MODULE_DESCRIPTION("Intel La Jolla Cove Adapter USB driver");
+MODULE_LICENSE("GPL");
index ac0d75ac2d2fae19a9771b86806375bdc0f72094..caf65f8294db22c74f080d2c2ccd6fe4f994f9bb 100644 (file)
@@ -705,7 +705,7 @@ static int is_good_config(struct usbtest_dev *tdev, int len)
 {
        struct usb_config_descriptor    *config;
 
-       if (len < sizeof(*config))
+       if (len < (int)sizeof(*config))
                return 0;
        config = (struct usb_config_descriptor *) tdev->buf;
 
index 9812d102a005f005d94f2b7fa3d652aa9e687cb4..824904abe76ffb502aedef2df20f7fb7b684934b 100644 (file)
@@ -81,15 +81,12 @@ void mon_reader_del(struct mon_bus *mbus, struct mon_reader *r)
 static void mon_bus_submit(struct mon_bus *mbus, struct urb *urb)
 {
        unsigned long flags;
-       struct list_head *pos;
        struct mon_reader *r;
 
        spin_lock_irqsave(&mbus->lock, flags);
        mbus->cnt_events++;
-       list_for_each (pos, &mbus->r_list) {
-               r = list_entry(pos, struct mon_reader, r_link);
+       list_for_each_entry(r, &mbus->r_list, r_link)
                r->rnf_submit(r->r_data, urb);
-       }
        spin_unlock_irqrestore(&mbus->lock, flags);
 }
 
@@ -108,15 +105,12 @@ static void mon_submit(struct usb_bus *ubus, struct urb *urb)
 static void mon_bus_submit_error(struct mon_bus *mbus, struct urb *urb, int error)
 {
        unsigned long flags;
-       struct list_head *pos;
        struct mon_reader *r;
 
        spin_lock_irqsave(&mbus->lock, flags);
        mbus->cnt_events++;
-       list_for_each (pos, &mbus->r_list) {
-               r = list_entry(pos, struct mon_reader, r_link);
+       list_for_each_entry(r, &mbus->r_list, r_link)
                r->rnf_error(r->r_data, urb, error);
-       }
        spin_unlock_irqrestore(&mbus->lock, flags);
 }
 
@@ -135,15 +129,12 @@ static void mon_submit_error(struct usb_bus *ubus, struct urb *urb, int error)
 static void mon_bus_complete(struct mon_bus *mbus, struct urb *urb, int status)
 {
        unsigned long flags;
-       struct list_head *pos;
        struct mon_reader *r;
 
        spin_lock_irqsave(&mbus->lock, flags);
        mbus->cnt_events++;
-       list_for_each (pos, &mbus->r_list) {
-               r = list_entry(pos, struct mon_reader, r_link);
+       list_for_each_entry(r, &mbus->r_list, r_link)
                r->rnf_complete(r->r_data, urb, status);
-       }
        spin_unlock_irqrestore(&mbus->lock, flags);
 }
 
@@ -165,11 +156,9 @@ static void mon_complete(struct usb_bus *ubus, struct urb *urb, int status)
 static void mon_stop(struct mon_bus *mbus)
 {
        struct usb_bus *ubus;
-       struct list_head *p;
 
        if (mbus == &mon_bus0) {
-               list_for_each (p, &mon_buses) {
-                       mbus = list_entry(p, struct mon_bus, bus_link);
+               list_for_each_entry(mbus, &mon_buses, bus_link) {
                        /*
                         * We do not change nreaders here, so rely on mon_lock.
                         */
@@ -332,14 +321,12 @@ static void mon_bus0_init(void)
  */
 struct mon_bus *mon_bus_lookup(unsigned int num)
 {
-       struct list_head *p;
        struct mon_bus *mbus;
 
        if (num == 0) {
                return &mon_bus0;
        }
-       list_for_each (p, &mon_buses) {
-               mbus = list_entry(p, struct mon_bus, bus_link);
+       list_for_each_entry(mbus, &mon_buses, bus_link) {
                if (mbus->u_bus->busnum == num) {
                        return mbus;
                }
index 6f264b129243bb3e0413df716c4a71e827e49a36..6858ed9fc3b2f1276161a60554dee3491063bb3d 100644 (file)
@@ -451,7 +451,7 @@ comm_init_err:
        return ret;
 }
 
-static int mtu3_remove(struct platform_device *pdev)
+static void mtu3_remove(struct platform_device *pdev)
 {
        struct ssusb_mtk *ssusb = platform_get_drvdata(pdev);
 
@@ -469,8 +469,16 @@ static int mtu3_remove(struct platform_device *pdev)
                ssusb_gadget_exit(ssusb);
                ssusb_host_exit(ssusb);
                break;
-       default:
-               return -EINVAL;
+       case USB_DR_MODE_UNKNOWN:
+               /*
+                * This cannot happen because with dr_mode ==
+                * USB_DR_MODE_UNKNOWN, .probe() doesn't succeed and so
+                * .remove() wouldn't be called at all. However (little
+                * surprising) the compiler isn't smart enough to see that, so
+                * we explicitly have this case item to not make the compiler
+                * wail about an unhandled enumeration value.
+                */
+               break;
        }
 
        ssusb_rscs_exit(ssusb);
@@ -478,8 +486,6 @@ static int mtu3_remove(struct platform_device *pdev)
        pm_runtime_disable(&pdev->dev);
        pm_runtime_put_noidle(&pdev->dev);
        pm_runtime_set_suspended(&pdev->dev);
-
-       return 0;
 }
 
 static int resume_ip_and_ports(struct ssusb_mtk *ssusb, pm_message_t msg)
@@ -615,7 +621,7 @@ MODULE_DEVICE_TABLE(of, mtu3_of_match);
 
 static struct platform_driver mtu3_driver = {
        .probe = mtu3_probe,
-       .remove = mtu3_remove,
+       .remove_new = mtu3_remove,
        .driver = {
                .name = MTU3_DRIVER_NAME,
                .pm = DEV_PM_OPS,
index 912e32b78ac6e7460e3f20f762d5b4ddbbfff3ec..8abf3a567e30a82f0f5ed851c794907ea47b631e 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/clk.h>
 #include <linux/err.h>
 #include <linux/io.h>
+#include <linux/of.h>
 #include <linux/of_platform.h>
 #include <linux/phy/phy.h>
 #include <linux/platform_device.h>
index 98b42dc04dee8c2716db379e0b84e260d21b9920..9c7a8bbc0542ceee69ff9353499207d468f7fe98 100644 (file)
@@ -849,7 +849,7 @@ static int dsps_setup_optional_vbus_irq(struct platform_device *pdev,
 
        error = devm_request_threaded_irq(glue->dev, glue->vbus_irq,
                                          NULL, dsps_vbus_threaded_irq,
-                                         IRQF_ONESHOT,
+                                         IRQF_SHARED,
                                          "vbus", glue);
        if (error) {
                glue->vbus_irq = 0;
index d73282c0ec50174e7c37d85a0253fe11abaa55b4..4d3b49e5b87a9d7cbeeac97e3497cac2f348e1f7 100644 (file)
@@ -54,12 +54,12 @@ static int uas_find_endpoints(struct usb_host_interface *alt,
 
 static int uas_use_uas_driver(struct usb_interface *intf,
                              const struct usb_device_id *id,
-                             unsigned long *flags_ret)
+                             u64 *flags_ret)
 {
        struct usb_host_endpoint *eps[4] = { };
        struct usb_device *udev = interface_to_usbdev(intf);
        struct usb_hcd *hcd = bus_to_hcd(udev->bus);
-       unsigned long flags = id->driver_info;
+       u64 flags = id->driver_info;
        struct usb_host_interface *alt;
        int r;
 
index 2583ee9815c556f3c1c54fc4b9643fd5e33f98b5..696bb0b2359922dedf4921bf233a23560629a46e 100644 (file)
@@ -37,7 +37,7 @@ struct uas_dev_info {
        struct usb_anchor cmd_urbs;
        struct usb_anchor sense_urbs;
        struct usb_anchor data_urbs;
-       unsigned long flags;
+       u64 flags;
        int qdepth, resetting;
        unsigned cmd_pipe, status_pipe, data_in_pipe, data_out_pipe;
        unsigned use_streams:1;
@@ -988,7 +988,7 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id)
        struct Scsi_Host *shost = NULL;
        struct uas_dev_info *devinfo;
        struct usb_device *udev = interface_to_usbdev(intf);
-       unsigned long dev_flags;
+       u64 dev_flags;
 
        if (!uas_use_uas_driver(intf, id, &dev_flags))
                return -ENODEV;
index 0547daf116a26878f55b49cd63acac741cdb30f4..5df40759d77ad450acc667e1796fd1e11635d65c 100644 (file)
@@ -19,7 +19,7 @@ UNUSUAL_DEV(  0x04b4, 0x6831, 0x0000, 0x9999,
                "Cypress ISD-300LP",
                USB_SC_CYP_ATACB, USB_PR_DEVICE, NULL, 0),
 
-UNUSUAL_DEV( 0x14cd, 0x6116, 0x0160, 0x0160,
+UNUSUAL_DEV( 0x14cd, 0x6116, 0x0150, 0x0160,
                "Super Top",
                "USB 2.0  SATA BRIDGE",
                USB_SC_CYP_ATACB, USB_PR_DEVICE, NULL, 0),
index 7b36a3334fb344fbe28cda2eb5268185024ba091..d1ad6a2509ab7fe3828184a9254d83ce7c12b1cf 100644 (file)
@@ -110,17 +110,6 @@ MODULE_PARM_DESC(quirks, "supplemental list of device IDs and their quirks");
        .useTransport = use_transport,  \
 }
 
-#define UNUSUAL_VENDOR_INTF(idVendor, cl, sc, pr, \
-               vendor_name, product_name, use_protocol, use_transport, \
-               init_function, Flags) \
-{ \
-       .vendorName = vendor_name,      \
-       .productName = product_name,    \
-       .useProtocol = use_protocol,    \
-       .useTransport = use_transport,  \
-       .initFunction = init_function,  \
-}
-
 static const struct us_unusual_dev us_unusual_dev_list[] = {
 #      include "unusual_devs.h"
        { }             /* Terminating entry */
@@ -132,7 +121,6 @@ static const struct us_unusual_dev for_dynamic_ids =
 #undef UNUSUAL_DEV
 #undef COMPLIANT_DEV
 #undef USUAL_DEV
-#undef UNUSUAL_VENDOR_INTF
 
 #ifdef CONFIG_LOCKDEP
 
@@ -472,13 +460,13 @@ static int associate_dev(struct us_data *us, struct usb_interface *intf)
 #define TOLOWER(x) ((x) | 0x20)
 
 /* Adjust device flags based on the "quirks=" module parameter */
-void usb_stor_adjust_quirks(struct usb_device *udev, unsigned long *fflags)
+void usb_stor_adjust_quirks(struct usb_device *udev, u64 *fflags)
 {
        char *p;
        u16 vid = le16_to_cpu(udev->descriptor.idVendor);
        u16 pid = le16_to_cpu(udev->descriptor.idProduct);
-       unsigned f = 0;
-       unsigned int mask = (US_FL_SANE_SENSE | US_FL_BAD_SENSE |
+       u64 f = 0;
+       u64 mask = (US_FL_SANE_SENSE | US_FL_BAD_SENSE |
                        US_FL_FIX_CAPACITY | US_FL_IGNORE_UAS |
                        US_FL_CAPACITY_HEURISTICS | US_FL_IGNORE_DEVICE |
                        US_FL_NOT_LOCKABLE | US_FL_MAX_SECTORS_64 |
@@ -617,7 +605,7 @@ static int get_device_info(struct us_data *us, const struct usb_device_id *id,
                us->fflags &= ~US_FL_GO_SLOW;
 
        if (us->fflags)
-               dev_info(pdev, "Quirks match for vid %04x pid %04x: %lx\n",
+               dev_info(pdev, "Quirks match for vid %04x pid %04x: %llx\n",
                                le16_to_cpu(dev->descriptor.idVendor),
                                le16_to_cpu(dev->descriptor.idProduct),
                                us->fflags);
index fd3f326708730d21cac32708c10d712346412484..97c6196d639b4ab1846ac5d32162ca78d6146a72 100644 (file)
@@ -95,7 +95,7 @@ struct us_data {
        struct usb_interface    *pusb_intf;      /* this interface */
        const struct us_unusual_dev   *unusual_dev;
                                                /* device-filter entry     */
-       unsigned long           fflags;          /* fixed flags from filter */
+       u64                     fflags;          /* fixed flags from filter */
        unsigned long           dflags;          /* dynamic atomic bitflags */
        unsigned int            send_bulk_pipe;  /* cached pipe values */
        unsigned int            recv_bulk_pipe;
@@ -192,7 +192,7 @@ extern int usb_stor_probe2(struct us_data *us);
 extern void usb_stor_disconnect(struct usb_interface *intf);
 
 extern void usb_stor_adjust_quirks(struct usb_device *dev,
-               unsigned long *fflags);
+               u64 *fflags);
 
 #define module_usb_stor_driver(__driver, __sht, __name) \
 static int __init __driver##_init(void) \
index 529512827d8f60b6a4bca7a970a0eb62c3bc077d..a26029e43dfd072110f70c22b54fb072307eb654 100644 (file)
                    vendorName, productName, useProtocol, useTransport, \
                    initFunction, flags) \
 { USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax), \
-  .driver_info = (flags) }
+  .driver_info = (kernel_ulong_t)(flags) }
 
 #define COMPLIANT_DEV  UNUSUAL_DEV
 
 #define USUAL_DEV(useProto, useTrans) \
 { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, useProto, useTrans) }
 
-/* Define the device is matched with Vendor ID and interface descriptors */
-#define UNUSUAL_VENDOR_INTF(id_vendor, cl, sc, pr, \
-                       vendorName, productName, useProtocol, useTransport, \
-                       initFunction, flags) \
-{ \
-       .match_flags = USB_DEVICE_ID_MATCH_INT_INFO \
-                               | USB_DEVICE_ID_MATCH_VENDOR, \
-       .idVendor    = (id_vendor), \
-       .bInterfaceClass = (cl), \
-       .bInterfaceSubClass = (sc), \
-       .bInterfaceProtocol = (pr), \
-       .driver_info = (flags) \
-}
-
 const struct usb_device_id usb_storage_usb_ids[] = {
 #      include "unusual_devs.h"
        { }             /* Terminating entry */
@@ -49,7 +35,6 @@ MODULE_DEVICE_TABLE(usb, usb_storage_usb_ids);
 #undef UNUSUAL_DEV
 #undef COMPLIANT_DEV
 #undef USUAL_DEV
-#undef UNUSUAL_VENDOR_INTF
 
 /*
  * The table of devices to ignore
index aea9d2fd0e769a0e7f6d2073f7dab90c082b5158..f81bec0c7b864dc605143078ec9f1cd3e2706379 100644 (file)
@@ -86,8 +86,11 @@ static int dp_altmode_notify(struct dp_altmode *dp)
 
 static int dp_altmode_configure(struct dp_altmode *dp, u8 con)
 {
-       u32 conf = DP_CONF_SIGNALING_DP; /* Only DP signaling supported */
        u8 pin_assign = 0;
+       u32 conf;
+
+       /* DP Signalling */
+       conf = (dp->data.conf & DP_CONF_SIGNALLING_MASK) >> DP_CONF_SIGNALLING_SHIFT;
 
        switch (con) {
        case DP_STATUS_CON_DISABLED:
index 221604f933a40bf66b05bab30061b1ead9bc837d..b12a07edc71bb5499b86b4200dad4552f33e3bf8 100644 (file)
@@ -1550,8 +1550,7 @@ static void anx7411_i2c_remove(struct i2c_client *client)
        if (plat->workqueue)
                destroy_workqueue(plat->workqueue);
 
-       if (plat->spi_client)
-               i2c_unregister_device(plat->spi_client);
+       i2c_unregister_device(plat->spi_client);
 
        if (plat->typec.role_sw)
                usb_role_switch_put(plat->typec.role_sw);
index 9c1dbf3c00e0a7ee67679093ac22182e47660aa9..2e0451bd336e2bd5e424b65e1964d6ad42ebf60d 100644 (file)
@@ -13,6 +13,7 @@
 #include <linux/usb/pd_vdo.h>
 #include <linux/usb/typec_mux.h>
 #include <linux/usb/typec_retimer.h>
+#include <linux/usb.h>
 
 #include "bus.h"
 #include "class.h"
@@ -681,6 +682,33 @@ const struct device_type typec_partner_dev_type = {
        .release = typec_partner_release,
 };
 
+static void typec_partner_link_device(struct typec_partner *partner, struct device *dev)
+{
+       int ret;
+
+       ret = sysfs_create_link(&dev->kobj, &partner->dev.kobj, "typec");
+       if (ret)
+               return;
+
+       ret = sysfs_create_link(&partner->dev.kobj, &dev->kobj, dev_name(dev));
+       if (ret) {
+               sysfs_remove_link(&dev->kobj, "typec");
+               return;
+       }
+
+       if (partner->attach)
+               partner->attach(partner, dev);
+}
+
+static void typec_partner_unlink_device(struct typec_partner *partner, struct device *dev)
+{
+       sysfs_remove_link(&partner->dev.kobj, dev_name(dev));
+       sysfs_remove_link(&dev->kobj, "typec");
+
+       if (partner->deattach)
+               partner->deattach(partner, dev);
+}
+
 /**
  * typec_partner_set_identity - Report result from Discover Identity command
  * @partner: The partner updated identity values
@@ -865,6 +893,8 @@ struct typec_partner *typec_register_partner(struct typec_port *port,
        partner->num_altmodes = -1;
        partner->pd_revision = desc->pd_revision;
        partner->svdm_version = port->cap->svdm_version;
+       partner->attach = desc->attach;
+       partner->deattach = desc->deattach;
 
        if (desc->identity) {
                /*
@@ -887,6 +917,11 @@ struct typec_partner *typec_register_partner(struct typec_port *port,
                return ERR_PTR(ret);
        }
 
+       if (port->usb2_dev)
+               typec_partner_link_device(partner, port->usb2_dev);
+       if (port->usb3_dev)
+               typec_partner_link_device(partner, port->usb3_dev);
+
        return partner;
 }
 EXPORT_SYMBOL_GPL(typec_register_partner);
@@ -899,8 +934,19 @@ EXPORT_SYMBOL_GPL(typec_register_partner);
  */
 void typec_unregister_partner(struct typec_partner *partner)
 {
-       if (!IS_ERR_OR_NULL(partner))
-               device_unregister(&partner->dev);
+       struct typec_port *port;
+
+       if (IS_ERR_OR_NULL(partner))
+               return;
+
+       port = to_typec_port(partner->dev.parent);
+
+       if (port->usb2_dev)
+               typec_partner_unlink_device(partner, port->usb2_dev);
+       if (port->usb3_dev)
+               typec_partner_unlink_device(partner, port->usb3_dev);
+
+       device_unregister(&partner->dev);
 }
 EXPORT_SYMBOL_GPL(typec_unregister_partner);
 
@@ -1775,6 +1821,50 @@ static int partner_match(struct device *dev, void *data)
        return is_typec_partner(dev);
 }
 
+static struct typec_partner *typec_get_partner(struct typec_port *port)
+{
+       struct device *dev;
+
+       dev = device_find_child(&port->dev, NULL, partner_match);
+       if (!dev)
+               return NULL;
+
+       return to_typec_partner(dev);
+}
+
+static void typec_partner_attach(struct typec_connector *con, struct device *dev)
+{
+       struct typec_port *port = container_of(con, struct typec_port, con);
+       struct typec_partner *partner = typec_get_partner(port);
+       struct usb_device *udev = to_usb_device(dev);
+
+       if (udev->speed < USB_SPEED_SUPER)
+               port->usb2_dev = dev;
+       else
+               port->usb3_dev = dev;
+
+       if (partner) {
+               typec_partner_link_device(partner, dev);
+               put_device(&partner->dev);
+       }
+}
+
+static void typec_partner_deattach(struct typec_connector *con, struct device *dev)
+{
+       struct typec_port *port = container_of(con, struct typec_port, con);
+       struct typec_partner *partner = typec_get_partner(port);
+
+       if (partner) {
+               typec_partner_unlink_device(partner, dev);
+               put_device(&partner->dev);
+       }
+
+       if (port->usb2_dev == dev)
+               port->usb2_dev = NULL;
+       else if (port->usb3_dev == dev)
+               port->usb3_dev = NULL;
+}
+
 /**
  * typec_set_data_role - Report data role change
  * @port: The USB Type-C Port where the role was changed
@@ -1784,7 +1874,7 @@ static int partner_match(struct device *dev, void *data)
  */
 void typec_set_data_role(struct typec_port *port, enum typec_data_role role)
 {
-       struct device *partner_dev;
+       struct typec_partner *partner;
 
        if (port->data_role == role)
                return;
@@ -1793,14 +1883,14 @@ void typec_set_data_role(struct typec_port *port, enum typec_data_role role)
        sysfs_notify(&port->dev.kobj, NULL, "data_role");
        kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
 
-       partner_dev = device_find_child(&port->dev, NULL, partner_match);
-       if (!partner_dev)
+       partner = typec_get_partner(port);
+       if (!partner)
                return;
 
-       if (to_typec_partner(partner_dev)->identity)
-               typec_product_type_notify(partner_dev);
+       if (partner->identity)
+               typec_product_type_notify(&partner->dev);
 
-       put_device(partner_dev);
+       put_device(&partner->dev);
 }
 EXPORT_SYMBOL_GPL(typec_set_data_role);
 
@@ -2251,6 +2341,8 @@ struct typec_port *typec_register_port(struct device *parent,
        port->ops = cap->ops;
        port->port_type = cap->type;
        port->prefer_role = cap->prefer_role;
+       port->con.attach = typec_partner_attach;
+       port->con.deattach = typec_partner_deattach;
 
        device_initialize(&port->dev);
        port->dev.class = &typec_class;
index 673b2952b074900d479db8d83ef41e45263289e0..c36761ba3f5998e367c420700c60a21aeed43688 100644 (file)
@@ -8,6 +8,7 @@
 
 struct typec_mux;
 struct typec_switch;
+struct usb_device;
 
 struct typec_plug {
        struct device                   dev;
@@ -35,6 +36,9 @@ struct typec_partner {
        enum usb_pd_svdm_ver            svdm_version;
 
        struct usb_power_delivery       *pd;
+
+       void (*attach)(struct typec_partner *partner, struct device *dev);
+       void (*deattach)(struct typec_partner *partner, struct device *dev);
 };
 
 struct typec_port {
@@ -59,6 +63,18 @@ struct typec_port {
 
        const struct typec_capability   *cap;
        const struct typec_operations   *ops;
+
+       struct typec_connector          con;
+
+       /*
+        * REVISIT: Only USB devices for now. If there are others, these need to
+        * be converted into a list.
+        *
+        * NOTE: These may be registered first before the typec_partner, so they
+        * will always have to be kept here instead of struct typec_partner.
+        */
+       struct device                   *usb2_dev;
+       struct device                   *usb3_dev;
 };
 
 #define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev)
index 65da61150ba78415111dd11e6a9290205abebf16..816b9bd08355ff60bf73187a65fe985134a37710 100644 (file)
@@ -46,4 +46,14 @@ config TYPEC_MUX_NB7VPQ904M
          Say Y or M if your system has a On Semiconductor NB7VPQ904M Type-C
          redriver chip found on some devices with a Type-C port.
 
+config TYPEC_MUX_PTN36502
+       tristate "NXP PTN36502 Type-C redriver driver"
+       depends on I2C
+       depends on DRM || DRM=n
+       select DRM_PANEL_BRIDGE if DRM
+       select REGMAP_I2C
+       help
+         Say Y or M if your system has a NXP PTN36502 Type-C redriver chip
+         found on some devices with a Type-C port.
+
 endmenu
index 76196096ef4167ac14566cf02e4c76965442b85a..9d6a5557b0bde282a4eb3db4e9537ed0bef30bc4 100644 (file)
@@ -5,3 +5,4 @@ obj-$(CONFIG_TYPEC_MUX_GPIO_SBU)        += gpio-sbu-mux.o
 obj-$(CONFIG_TYPEC_MUX_PI3USB30532)    += pi3usb30532.o
 obj-$(CONFIG_TYPEC_MUX_INTEL_PMC)      += intel_pmc_mux.o
 obj-$(CONFIG_TYPEC_MUX_NB7VPQ904M)     += nb7vpq904m.o
+obj-$(CONFIG_TYPEC_MUX_PTN36502)       += ptn36502.o
index e0ee1f621abbe72c4a406db39686f0d7d728ecca..cb7cdf90cb0aaa4184602ed2dab3f4e4382901ea 100644 (file)
@@ -60,6 +60,7 @@ struct fsa4480 {
        unsigned int svid;
 
        u8 cur_enable;
+       bool swap_sbu_lanes;
 };
 
 static const struct regmap_config fsa4480_regmap_config = {
@@ -76,6 +77,9 @@ static int fsa4480_set(struct fsa4480 *fsa)
        u8 enable = FSA4480_ENABLE_DEVICE;
        u8 sel = 0;
 
+       if (fsa->swap_sbu_lanes)
+               reverse = !reverse;
+
        /* USB Mode */
        if (fsa->mode < TYPEC_STATE_MODAL ||
            (!fsa->svid && (fsa->mode == TYPEC_MODE_USB2 ||
@@ -179,12 +183,75 @@ static int fsa4480_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *st
        return ret;
 }
 
+enum {
+       NORMAL_LANE_MAPPING,
+       INVERT_LANE_MAPPING,
+};
+
+#define DATA_LANES_COUNT       2
+
+static const int supported_data_lane_mapping[][DATA_LANES_COUNT] = {
+       [NORMAL_LANE_MAPPING] = { 0, 1 },
+       [INVERT_LANE_MAPPING] = { 1, 0 },
+};
+
+static int fsa4480_parse_data_lanes_mapping(struct fsa4480 *fsa)
+{
+       struct fwnode_handle *ep;
+       u32 data_lanes[DATA_LANES_COUNT];
+       int ret, i, j;
+
+       ep = fwnode_graph_get_next_endpoint(dev_fwnode(&fsa->client->dev), NULL);
+       if (!ep)
+               return 0;
+
+       ret = fwnode_property_read_u32_array(ep, "data-lanes", data_lanes, DATA_LANES_COUNT);
+       if (ret == -EINVAL)
+               /* Property isn't here, consider default mapping */
+               goto out_done;
+       if (ret) {
+               dev_err(&fsa->client->dev, "invalid data-lanes property: %d\n", ret);
+               goto out_error;
+       }
+
+       for (i = 0; i < ARRAY_SIZE(supported_data_lane_mapping); i++) {
+               for (j = 0; j < DATA_LANES_COUNT; j++) {
+                       if (data_lanes[j] != supported_data_lane_mapping[i][j])
+                               break;
+               }
+
+               if (j == DATA_LANES_COUNT)
+                       break;
+       }
+
+       switch (i) {
+       case NORMAL_LANE_MAPPING:
+               break;
+       case INVERT_LANE_MAPPING:
+               fsa->swap_sbu_lanes = true;
+               break;
+       default:
+               dev_err(&fsa->client->dev, "invalid data-lanes mapping\n");
+               ret = -EINVAL;
+               goto out_error;
+       }
+
+out_done:
+       ret = 0;
+
+out_error:
+       fwnode_handle_put(ep);
+
+       return ret;
+}
+
 static int fsa4480_probe(struct i2c_client *client)
 {
        struct device *dev = &client->dev;
        struct typec_switch_desc sw_desc = { };
        struct typec_mux_desc mux_desc = { };
        struct fsa4480 *fsa;
+       int ret;
 
        fsa = devm_kzalloc(dev, sizeof(*fsa), GFP_KERNEL);
        if (!fsa)
@@ -193,6 +260,10 @@ static int fsa4480_probe(struct i2c_client *client)
        fsa->client = client;
        mutex_init(&fsa->lock);
 
+       ret = fsa4480_parse_data_lanes_mapping(fsa);
+       if (ret)
+               return ret;
+
        fsa->regmap = devm_regmap_init_i2c(client, &fsa4480_regmap_config);
        if (IS_ERR(fsa->regmap))
                return dev_err_probe(dev, PTR_ERR(fsa->regmap), "failed to initialize regmap\n");
index 60ed1f809130d8b35a527581677b2229e42b5112..56989a0d0f43e0476fdc6a31f87c0e2c025fff29 100644 (file)
@@ -191,6 +191,12 @@ static int hsl_orientation(struct pmc_usb_port *port)
        return port->orientation - 1;
 }
 
+static bool is_pmc_mux_tbt(struct acpi_device *adev)
+{
+       return acpi_dev_hid_uid_match(adev, "INTC1072", NULL) ||
+              acpi_dev_hid_uid_match(adev, "INTC1079", NULL);
+}
+
 static int pmc_usb_send_command(struct intel_scu_ipc_dev *ipc, u8 *msg, u32 len)
 {
        u8 response[4];
@@ -293,6 +299,24 @@ pmc_usb_mux_dp(struct pmc_usb_port *port, struct typec_mux_state *state)
        req.mode_data |= (state->mode - TYPEC_STATE_MODAL) <<
                         PMC_USB_ALTMODE_DP_MODE_SHIFT;
 
+       if (!is_pmc_mux_tbt(port->pmc->iom_adev)) {
+               u8 cable_speed = (data->conf & DP_CONF_SIGNALLING_MASK) >>
+                                 DP_CONF_SIGNALLING_SHIFT;
+
+               u8 cable_type = (data->conf & DP_CONF_CABLE_TYPE_MASK) >>
+                                DP_CONF_CABLE_TYPE_SHIFT;
+
+               req.mode_data |= PMC_USB_ALTMODE_CABLE_SPD(cable_speed);
+
+               if (cable_type == DP_CONF_CABLE_TYPE_OPTICAL)
+                       req.mode_data |= PMC_USB_ALTMODE_CABLE_TYPE;
+               else if (cable_type == DP_CONF_CABLE_TYPE_RE_TIMER)
+                       req.mode_data |= PMC_USB_ALTMODE_ACTIVE_CABLE |
+                                        PMC_USB_ALTMODE_RETIMER_CABLE;
+               else if (cable_type == DP_CONF_CABLE_TYPE_RE_DRIVER)
+                       req.mode_data |= PMC_USB_ALTMODE_ACTIVE_CABLE;
+       }
+
        ret = pmc_usb_command(port, (void *)&req, sizeof(req));
        if (ret)
                return ret;
@@ -599,6 +623,7 @@ static int pmc_usb_register_port(struct pmc_usb *pmc, int index,
        desc.driver_data = port;
        desc.name = fwnode_get_name(fwnode);
        desc.set = pmc_usb_set_role;
+       desc.allow_userspace_control = true;
 
        port->usb_sw = usb_role_switch_register(pmc->dev, &desc);
        if (IS_ERR(port->usb_sw)) {
diff --git a/drivers/usb/typec/mux/ptn36502.c b/drivers/usb/typec/mux/ptn36502.c
new file mode 100644 (file)
index 0000000..72ae38a
--- /dev/null
@@ -0,0 +1,444 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NXP PTN36502 Type-C driver
+ *
+ * Copyright (C) 2023 Luca Weiss <luca.weiss@fairphone.com>
+ *
+ * Based on NB7VPQ904M driver:
+ * Copyright (C) 2023 Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
+ */
+
+#include <drm/drm_bridge.h>
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_graph.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/usb/typec_dp.h>
+#include <linux/usb/typec_mux.h>
+#include <linux/usb/typec_retimer.h>
+
+#define PTN36502_CHIP_ID_REG                           0x00
+#define PTN36502_CHIP_ID                               0x02
+
+#define PTN36502_CHIP_REVISION_REG                     0x01
+#define PTN36502_CHIP_REVISION_BASE_MASK               GENMASK(7, 4)
+#define PTN36502_CHIP_REVISION_METAL_MASK              GENMASK(3, 0)
+
+#define PTN36502_DP_LINK_CTRL_REG                      0x06
+#define PTN36502_DP_LINK_CTRL_LANES_MASK               GENMASK(3, 2)
+#define PTN36502_DP_LINK_CTRL_LANES_2                  (2)
+#define PTN36502_DP_LINK_CTRL_LANES_4                  (3)
+#define PTN36502_DP_LINK_CTRL_LINK_RATE_MASK           GENMASK(1, 0)
+#define PTN36502_DP_LINK_CTRL_LINK_RATE_5_4GBPS                (2)
+
+/* Registers for lane 0 (0x07) to lane 3 (0x0a) have the same layout */
+#define PTN36502_DP_LANE_CTRL_REG(n)                   (0x07 + (n))
+#define PTN36502_DP_LANE_CTRL_RX_GAIN_MASK             GENMASK(6, 4)
+#define PTN36502_DP_LANE_CTRL_RX_GAIN_3DB              (2)
+#define PTN36502_DP_LANE_CTRL_TX_SWING_MASK             GENMASK(3, 2)
+#define PTN36502_DP_LANE_CTRL_TX_SWING_800MVPPD         (2)
+#define PTN36502_DP_LANE_CTRL_PRE_EMPHASIS_MASK                GENMASK(1, 0)
+#define PTN36502_DP_LANE_CTRL_PRE_EMPHASIS_3_5DB       (1)
+
+#define PTN36502_MODE_CTRL1_REG                                0x0b
+#define PTN36502_MODE_CTRL1_PLUG_ORIENT_MASK           GENMASK(5, 5)
+#define PTN36502_MODE_CTRL1_PLUG_ORIENT_REVERSE                (1)
+#define PTN36502_MODE_CTRL1_AUX_CROSSBAR_MASK          GENMASK(3, 3)
+#define PTN36502_MODE_CTRL1_AUX_CROSSBAR_SW_ON         (1)
+#define PTN36502_MODE_CTRL1_MODE_MASK                  GENMASK(2, 0)
+#define PTN36502_MODE_CTRL1_MODE_OFF                   (0)
+#define PTN36502_MODE_CTRL1_MODE_USB_ONLY              (1)
+#define PTN36502_MODE_CTRL1_MODE_USB_DP                        (2)
+#define PTN36502_MODE_CTRL1_MODE_DP                    (3)
+
+#define PTN36502_DEVICE_CTRL_REG                       0x0d
+#define PTN36502_DEVICE_CTRL_AUX_MONITORING_MASK       GENMASK(7, 7)
+#define PTN36502_DEVICE_CTRL_AUX_MONITORING_EN         (1)
+
+struct ptn36502 {
+       struct i2c_client *client;
+       struct regulator *vdd18_supply;
+       struct regmap *regmap;
+       struct typec_switch_dev *sw;
+       struct typec_retimer *retimer;
+
+       struct typec_switch *typec_switch;
+
+       struct drm_bridge bridge;
+
+       struct mutex lock; /* protect non-concurrent retimer & switch */
+
+       enum typec_orientation orientation;
+       unsigned long mode;
+       unsigned int svid;
+};
+
+static int ptn36502_set(struct ptn36502 *ptn)
+{
+       bool reverse = (ptn->orientation == TYPEC_ORIENTATION_REVERSE);
+       unsigned int ctrl1_val = 0;
+       unsigned int lane_ctrl_val = 0;
+       unsigned int link_ctrl_val = 0;
+
+       switch (ptn->mode) {
+       case TYPEC_STATE_SAFE:
+               /* Deep power saving state */
+               regmap_write(ptn->regmap, PTN36502_MODE_CTRL1_REG,
+                            FIELD_PREP(PTN36502_MODE_CTRL1_MODE_MASK,
+                                       PTN36502_MODE_CTRL1_MODE_OFF));
+               return 0;
+
+       case TYPEC_STATE_USB:
+               /*
+                * Normal Orientation (CC1)
+                * A -> USB RX
+                * B -> USB TX
+                * C -> X
+                * D -> X
+                * Flipped Orientation (CC2)
+                * A -> X
+                * B -> X
+                * C -> USB TX
+                * D -> USB RX
+                */
+
+               /* USB 3.1 Gen 1 only */
+               ctrl1_val = FIELD_PREP(PTN36502_MODE_CTRL1_MODE_MASK,
+                                      PTN36502_MODE_CTRL1_MODE_USB_ONLY);
+               if (reverse)
+                       ctrl1_val |= FIELD_PREP(PTN36502_MODE_CTRL1_PLUG_ORIENT_MASK,
+                                               PTN36502_MODE_CTRL1_PLUG_ORIENT_REVERSE);
+
+               regmap_write(ptn->regmap, PTN36502_MODE_CTRL1_REG, ctrl1_val);
+               return 0;
+
+       default:
+               if (ptn->svid != USB_TYPEC_DP_SID)
+                       return -EINVAL;
+
+               break;
+       }
+
+       /* DP Altmode Setup */
+
+       switch (ptn->mode) {
+       case TYPEC_DP_STATE_C:
+       case TYPEC_DP_STATE_E:
+               /*
+                * Normal Orientation (CC1)
+                * A -> DP3
+                * B -> DP2
+                * C -> DP1
+                * D -> DP0
+                * Flipped Orientation (CC2)
+                * A -> DP0
+                * B -> DP1
+                * C -> DP2
+                * D -> DP3
+                */
+
+               /* 4-lane DP */
+               ctrl1_val |= FIELD_PREP(PTN36502_MODE_CTRL1_MODE_MASK,
+                                       PTN36502_MODE_CTRL1_MODE_DP);
+               link_ctrl_val |= FIELD_PREP(PTN36502_DP_LINK_CTRL_LANES_MASK,
+                                           PTN36502_DP_LINK_CTRL_LANES_4);
+               break;
+
+       case TYPEC_DP_STATE_D:
+       case TYPEC_DP_STATE_F: /* State F is deprecated */
+               /*
+                * Normal Orientation (CC1)
+                * A -> USB RX
+                * B -> USB TX
+                * C -> DP1
+                * D -> DP0
+                * Flipped Orientation (CC2)
+                * A -> DP0
+                * B -> DP1
+                * C -> USB TX
+                * D -> USB RX
+                */
+
+               /* USB 3.1 Gen 1 and 2-lane DP */
+               ctrl1_val |= FIELD_PREP(PTN36502_MODE_CTRL1_MODE_MASK,
+                                       PTN36502_MODE_CTRL1_MODE_USB_DP);
+               link_ctrl_val |= FIELD_PREP(PTN36502_DP_LINK_CTRL_LANES_MASK,
+                                           PTN36502_DP_LINK_CTRL_LANES_2);
+               break;
+
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       /* Enable AUX monitoring */
+       regmap_write(ptn->regmap, PTN36502_DEVICE_CTRL_REG,
+                    FIELD_PREP(PTN36502_DEVICE_CTRL_AUX_MONITORING_MASK,
+                               PTN36502_DEVICE_CTRL_AUX_MONITORING_EN));
+
+       /* Enable AUX switch path */
+       ctrl1_val |= FIELD_PREP(PTN36502_MODE_CTRL1_AUX_CROSSBAR_MASK,
+                               PTN36502_MODE_CTRL1_AUX_CROSSBAR_SW_ON);
+       if (reverse)
+               ctrl1_val |= FIELD_PREP(PTN36502_MODE_CTRL1_PLUG_ORIENT_MASK,
+                                       PTN36502_MODE_CTRL1_PLUG_ORIENT_REVERSE);
+       regmap_write(ptn->regmap, PTN36502_MODE_CTRL1_REG, ctrl1_val);
+
+       /* DP Link rate: 5.4 Gbps (HBR2) */
+       link_ctrl_val |= FIELD_PREP(PTN36502_DP_LINK_CTRL_LINK_RATE_MASK,
+                                   PTN36502_DP_LINK_CTRL_LINK_RATE_5_4GBPS);
+       regmap_write(ptn->regmap, PTN36502_DP_LINK_CTRL_REG, link_ctrl_val);
+
+       /*
+        * For all lanes:
+        * - Rx equivalization gain: 3 dB
+        * - TX output swing control: 800 mVppd
+        * - Pre-emphasis control: 3.5 dB
+        */
+       lane_ctrl_val = FIELD_PREP(PTN36502_DP_LANE_CTRL_RX_GAIN_MASK,
+                                  PTN36502_DP_LANE_CTRL_RX_GAIN_3DB) |
+                       FIELD_PREP(PTN36502_DP_LANE_CTRL_TX_SWING_MASK,
+                                  PTN36502_DP_LANE_CTRL_TX_SWING_800MVPPD) |
+                       FIELD_PREP(PTN36502_DP_LANE_CTRL_PRE_EMPHASIS_MASK,
+                                  PTN36502_DP_LANE_CTRL_PRE_EMPHASIS_3_5DB);
+       regmap_write(ptn->regmap, PTN36502_DP_LANE_CTRL_REG(0), lane_ctrl_val);
+       regmap_write(ptn->regmap, PTN36502_DP_LANE_CTRL_REG(1), lane_ctrl_val);
+       regmap_write(ptn->regmap, PTN36502_DP_LANE_CTRL_REG(2), lane_ctrl_val);
+       regmap_write(ptn->regmap, PTN36502_DP_LANE_CTRL_REG(3), lane_ctrl_val);
+
+       return 0;
+}
+
+static int ptn36502_sw_set(struct typec_switch_dev *sw, enum typec_orientation orientation)
+{
+       struct ptn36502 *ptn = typec_switch_get_drvdata(sw);
+       int ret;
+
+       ret = typec_switch_set(ptn->typec_switch, orientation);
+       if (ret)
+               return ret;
+
+       mutex_lock(&ptn->lock);
+
+       if (ptn->orientation != orientation) {
+               ptn->orientation = orientation;
+
+               ret = ptn36502_set(ptn);
+       }
+
+       mutex_unlock(&ptn->lock);
+
+       return ret;
+}
+
+static int ptn36502_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state)
+{
+       struct ptn36502 *ptn = typec_retimer_get_drvdata(retimer);
+       int ret = 0;
+
+       mutex_lock(&ptn->lock);
+
+       if (ptn->mode != state->mode) {
+               ptn->mode = state->mode;
+
+               if (state->alt)
+                       ptn->svid = state->alt->svid;
+               else
+                       ptn->svid = 0; // No SVID
+
+               ret = ptn36502_set(ptn);
+       }
+
+       mutex_unlock(&ptn->lock);
+
+       return ret;
+}
+
+static int ptn36502_detect(struct ptn36502 *ptn)
+{
+       struct device *dev = &ptn->client->dev;
+       unsigned int reg_val;
+       int ret;
+
+       ret = regmap_read(ptn->regmap, PTN36502_CHIP_ID_REG,
+                         &reg_val);
+       if (ret < 0)
+               return dev_err_probe(dev, ret, "Failed to read chip ID\n");
+
+       if (reg_val != PTN36502_CHIP_ID)
+               return dev_err_probe(dev, -ENODEV, "Unexpected chip ID: %x\n", reg_val);
+
+       ret = regmap_read(ptn->regmap, PTN36502_CHIP_REVISION_REG,
+                         &reg_val);
+       if (ret < 0)
+               return dev_err_probe(dev, ret, "Failed to read chip revision\n");
+
+       dev_dbg(dev, "Chip revision: base layer version %lx, metal layer version %lx\n",
+               FIELD_GET(PTN36502_CHIP_REVISION_BASE_MASK, reg_val),
+               FIELD_GET(PTN36502_CHIP_REVISION_METAL_MASK, reg_val));
+
+       return 0;
+}
+
+#if IS_ENABLED(CONFIG_OF) && IS_ENABLED(CONFIG_DRM_PANEL_BRIDGE)
+static int ptn36502_bridge_attach(struct drm_bridge *bridge,
+                                 enum drm_bridge_attach_flags flags)
+{
+       struct ptn36502 *ptn = container_of(bridge, struct ptn36502, bridge);
+       struct drm_bridge *next_bridge;
+
+       if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR))
+               return -EINVAL;
+
+       next_bridge = devm_drm_of_get_bridge(&ptn->client->dev, ptn->client->dev.of_node, 0, 0);
+       if (IS_ERR(next_bridge)) {
+               dev_err(&ptn->client->dev, "failed to acquire drm_bridge: %pe\n", next_bridge);
+               return PTR_ERR(next_bridge);
+       }
+
+       return drm_bridge_attach(bridge->encoder, next_bridge, bridge,
+                                DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+}
+
+static const struct drm_bridge_funcs ptn36502_bridge_funcs = {
+       .attach = ptn36502_bridge_attach,
+};
+
+static int ptn36502_register_bridge(struct ptn36502 *ptn)
+{
+       ptn->bridge.funcs = &ptn36502_bridge_funcs;
+       ptn->bridge.of_node = ptn->client->dev.of_node;
+
+       return devm_drm_bridge_add(&ptn->client->dev, &ptn->bridge);
+}
+#else
+static int ptn36502_register_bridge(struct ptn36502 *ptn)
+{
+       return 0;
+}
+#endif
+
+static const struct regmap_config ptn36502_regmap = {
+       .max_register = 0x0d,
+       .reg_bits = 8,
+       .val_bits = 8,
+};
+
+static int ptn36502_probe(struct i2c_client *client)
+{
+       struct device *dev = &client->dev;
+       struct typec_switch_desc sw_desc = { };
+       struct typec_retimer_desc retimer_desc = { };
+       struct ptn36502 *ptn;
+       int ret;
+
+       ptn = devm_kzalloc(dev, sizeof(*ptn), GFP_KERNEL);
+       if (!ptn)
+               return -ENOMEM;
+
+       ptn->client = client;
+
+       ptn->regmap = devm_regmap_init_i2c(client, &ptn36502_regmap);
+       if (IS_ERR(ptn->regmap)) {
+               dev_err(&client->dev, "Failed to allocate register map\n");
+               return PTR_ERR(ptn->regmap);
+       }
+
+       ptn->mode = TYPEC_STATE_SAFE;
+       ptn->orientation = TYPEC_ORIENTATION_NONE;
+
+       mutex_init(&ptn->lock);
+
+       ptn->vdd18_supply = devm_regulator_get_optional(dev, "vdd18");
+       if (IS_ERR(ptn->vdd18_supply))
+               return PTR_ERR(ptn->vdd18_supply);
+
+       ptn->typec_switch = fwnode_typec_switch_get(dev->fwnode);
+       if (IS_ERR(ptn->typec_switch))
+               return dev_err_probe(dev, PTR_ERR(ptn->typec_switch),
+                                    "Failed to acquire orientation-switch\n");
+
+       ret = regulator_enable(ptn->vdd18_supply);
+       if (ret)
+               return dev_err_probe(dev, ret, "Failed to enable vdd18\n");
+
+       ret = ptn36502_detect(ptn);
+       if (ret)
+               goto err_disable_regulator;
+
+       ret = ptn36502_register_bridge(ptn);
+       if (ret)
+               goto err_disable_regulator;
+
+       sw_desc.drvdata = ptn;
+       sw_desc.fwnode = dev->fwnode;
+       sw_desc.set = ptn36502_sw_set;
+
+       ptn->sw = typec_switch_register(dev, &sw_desc);
+       if (IS_ERR(ptn->sw)) {
+               ret = dev_err_probe(dev, PTR_ERR(ptn->sw),
+                                   "Failed to register typec switch\n");
+               goto err_disable_regulator;
+       }
+
+       retimer_desc.drvdata = ptn;
+       retimer_desc.fwnode = dev->fwnode;
+       retimer_desc.set = ptn36502_retimer_set;
+
+       ptn->retimer = typec_retimer_register(dev, &retimer_desc);
+       if (IS_ERR(ptn->retimer)) {
+               ret = dev_err_probe(dev, PTR_ERR(ptn->retimer),
+                                   "Failed to register typec retimer\n");
+               goto err_switch_unregister;
+       }
+
+       return 0;
+
+err_switch_unregister:
+       typec_switch_unregister(ptn->sw);
+
+err_disable_regulator:
+       regulator_disable(ptn->vdd18_supply);
+
+       return ret;
+}
+
+static void ptn36502_remove(struct i2c_client *client)
+{
+       struct ptn36502 *ptn = i2c_get_clientdata(client);
+
+       typec_retimer_unregister(ptn->retimer);
+       typec_switch_unregister(ptn->sw);
+
+       regulator_disable(ptn->vdd18_supply);
+}
+
+static const struct i2c_device_id ptn36502_table[] = {
+       { "ptn36502" },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, ptn36502_table);
+
+static const struct of_device_id ptn36502_of_table[] = {
+       { .compatible = "nxp,ptn36502" },
+       { }
+};
+MODULE_DEVICE_TABLE(of, ptn36502_of_table);
+
+static struct i2c_driver ptn36502_driver = {
+       .driver = {
+               .name = "ptn36502",
+               .of_match_table = ptn36502_of_table,
+       },
+       .probe          = ptn36502_probe,
+       .remove         = ptn36502_remove,
+       .id_table       = ptn36502_table,
+};
+module_i2c_driver(ptn36502_driver);
+
+MODULE_AUTHOR("Luca Weiss <luca.weiss@fairphone.com>");
+MODULE_DESCRIPTION("NXP PTN36502 Type-C driver");
+MODULE_LICENSE("GPL");
index 8cc66e4467c48f3f6175f612092c661b484a44e0..85d015cdbe1fe1b530720ae34bdddf08dae10929 100644 (file)
@@ -83,14 +83,12 @@ unchunked_extended_messages_supported_show(struct device *dev,
 }
 static DEVICE_ATTR_RO(unchunked_extended_messages_supported);
 
-/*
- * REVISIT: Peak Current requires access also to the RDO.
 static ssize_t
 peak_current_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
-       ...
+       return sysfs_emit(buf, "%u\n", (to_pdo(dev)->pdo >> PDO_FIXED_PEAK_CURR_SHIFT) & 3);
 }
-*/
+static DEVICE_ATTR_RO(peak_current);
 
 static ssize_t
 fast_role_swap_current_show(struct device *dev, struct device_attribute *attr, char *buf)
@@ -135,7 +133,7 @@ static struct attribute *source_fixed_supply_attrs[] = {
        &dev_attr_usb_communication_capable.attr,
        &dev_attr_dual_role_data.attr,
        &dev_attr_unchunked_extended_messages_supported.attr,
-       /*&dev_attr_peak_current.attr,*/
+       &dev_attr_peak_current.attr,
        &dev_attr_voltage.attr,
        &maximum_current_attr.attr,
        NULL
@@ -144,7 +142,7 @@ static struct attribute *source_fixed_supply_attrs[] = {
 static umode_t fixed_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n)
 {
        if (to_pdo(kobj_to_dev(kobj))->object_position &&
-           /*attr != &dev_attr_peak_current.attr &&*/
+           attr != &dev_attr_peak_current.attr &&
            attr != &dev_attr_voltage.attr &&
            attr != &maximum_current_attr.attr &&
            attr != &operational_current_attr.attr)
index a929e000d0e2cf05b7223f21c4321f9686c876d0..d42da5720a251da3dc0700baa2b2103a5c4a20cb 100644 (file)
@@ -8,17 +8,22 @@
 
 #include <linux/acpi.h>
 #include <linux/component.h>
+#include <linux/usb.h>
 
 #include "class.h"
 
 static int typec_aggregate_bind(struct device *dev)
 {
-       return component_bind_all(dev, NULL);
+       struct typec_port *port = to_typec_port(dev);
+
+       return component_bind_all(dev, &port->con);
 }
 
 static void typec_aggregate_unbind(struct device *dev)
 {
-       component_unbind_all(dev, NULL);
+       struct typec_port *port = to_typec_port(dev);
+
+       component_unbind_all(dev, &port->con);
 }
 
 static const struct component_master_ops typec_aggregate_ops = {
index 17ebc5fb684f9f56e7f91a695621a401dd31ee9a..67422d45eb5486dee00f191fb678c583f48474be 100644 (file)
@@ -7,6 +7,7 @@
 
 #include <linux/bits.h>
 #include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
 #include <linux/module.h>
 #include <linux/i2c.h>
 #include <linux/interrupt.h>
 /* 1b0 as fixed rx threshold of rd/rp 0.55V, 1b1 depends on RTCRTL4[0] */
 #define BMCIO_RXDZEN   BIT(0)
 
+struct rt1711h_chip_info {
+       u32 rxdz_sel;
+       u16 did;
+       bool enable_pd30_extended_message;
+};
+
 struct rt1711h_chip {
        struct tcpci_data data;
        struct tcpci *tcpci;
        struct device *dev;
        struct regulator *vbus;
+       const struct rt1711h_chip_info *info;
        bool src_en;
-       u16 did;
 };
 
 static int rt1711h_read16(struct rt1711h_chip *chip, unsigned int reg, u16 *val)
@@ -105,7 +112,7 @@ static int rt1711h_init(struct tcpci *tcpci, struct tcpci_data *tdata)
                return ret;
 
        /* Enable PD30 extended message for RT1715 */
-       if (chip->did == RT1715_DID) {
+       if (chip->info->enable_pd30_extended_message) {
                ret = regmap_update_bits(regmap, RT1711H_RTCTRL8,
                                         RT1711H_ENEXTMSG, RT1711H_ENEXTMSG);
                if (ret < 0)
@@ -200,10 +207,7 @@ static inline int rt1711h_init_cc_params(struct rt1711h_chip *chip, u8 status)
        if ((cc1 >= TYPEC_CC_RP_1_5 && cc2 < TYPEC_CC_RP_DEF) ||
            (cc2 >= TYPEC_CC_RP_1_5 && cc1 < TYPEC_CC_RP_DEF)) {
                rxdz_en = BMCIO_RXDZEN;
-               if (chip->did == RT1715_DID)
-                       rxdz_sel = RT1711H_BMCIO_RXDZSEL;
-               else
-                       rxdz_sel = 0;
+               rxdz_sel = chip->info->rxdz_sel;
        } else {
                rxdz_en = 0;
                rxdz_sel = RT1711H_BMCIO_RXDZSEL;
@@ -319,7 +323,7 @@ static int rt1711h_check_revision(struct i2c_client *i2c, struct rt1711h_chip *c
        ret = i2c_smbus_read_word_data(i2c, TCPC_BCD_DEV);
        if (ret < 0)
                return ret;
-       if (ret != chip->did) {
+       if (ret != chip->info->did) {
                dev_err(&i2c->dev, "did is not correct, 0x%04x\n", ret);
                return -ENODEV;
        }
@@ -336,7 +340,7 @@ static int rt1711h_probe(struct i2c_client *client)
        if (!chip)
                return -ENOMEM;
 
-       chip->did = (size_t)device_get_match_data(&client->dev);
+       chip->info = i2c_get_match_data(client);
 
        ret = rt1711h_check_revision(client, chip);
        if (ret < 0) {
@@ -391,26 +395,34 @@ static void rt1711h_remove(struct i2c_client *client)
        tcpci_unregister_port(chip->tcpci);
 }
 
+static const struct rt1711h_chip_info rt1711h = {
+       .did = RT1711H_DID,
+};
+
+static const struct rt1711h_chip_info rt1715 = {
+       .rxdz_sel = RT1711H_BMCIO_RXDZSEL,
+       .did = RT1715_DID,
+       .enable_pd30_extended_message = true,
+};
+
 static const struct i2c_device_id rt1711h_id[] = {
-       { "rt1711h", 0 },
-       { "rt1715", 0 },
-       { }
+       { "rt1711h", (kernel_ulong_t)&rt1711h },
+       { "rt1715", (kernel_ulong_t)&rt1715 },
+       {}
 };
 MODULE_DEVICE_TABLE(i2c, rt1711h_id);
 
-#ifdef CONFIG_OF
 static const struct of_device_id rt1711h_of_match[] = {
-       { .compatible = "richtek,rt1711h", .data = (void *)RT1711H_DID },
-       { .compatible = "richtek,rt1715", .data = (void *)RT1715_DID },
-       {},
+       { .compatible = "richtek,rt1711h", .data = &rt1711h },
+       { .compatible = "richtek,rt1715", .data = &rt1715 },
+       {}
 };
 MODULE_DEVICE_TABLE(of, rt1711h_of_match);
-#endif
 
 static struct i2c_driver rt1711h_i2c_driver = {
        .driver = {
                .name = "rt1711h",
-               .of_match_table = of_match_ptr(rt1711h_of_match),
+               .of_match_table = rt1711h_of_match,
        },
        .probe = rt1711h_probe,
        .remove = rt1711h_remove,
index d962f67c95ae66a25254f0b8fea1b4e725313112..058d5b853b5749d0b9743554380ccaf7880c75c0 100644 (file)
@@ -517,9 +517,9 @@ static const char * const pd_rev[] = {
        ((cc) == TYPEC_CC_RP_DEF || (cc) == TYPEC_CC_RP_1_5 || \
         (cc) == TYPEC_CC_RP_3_0)
 
+/* As long as cc is pulled up, we can consider it as sink. */
 #define tcpm_port_is_sink(port) \
-       ((tcpm_cc_is_sink((port)->cc1) && !tcpm_cc_is_sink((port)->cc2)) || \
-        (tcpm_cc_is_sink((port)->cc2) && !tcpm_cc_is_sink((port)->cc1)))
+       (tcpm_cc_is_sink((port)->cc1) || tcpm_cc_is_sink((port)->cc2))
 
 #define tcpm_cc_is_source(cc) ((cc) == TYPEC_CC_RD)
 #define tcpm_cc_is_audio(cc) ((cc) == TYPEC_CC_RA)
@@ -1625,6 +1625,9 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
                        if (PD_VDO_VID(p[0]) != USB_SID_PD)
                                break;
 
+                       if (IS_ERR_OR_NULL(port->partner))
+                               break;
+
                        if (PD_VDO_SVDM_VER(p[0]) < svdm_version) {
                                typec_partner_set_svdm_version(port->partner,
                                                               PD_VDO_SVDM_VER(p[0]));
@@ -3903,6 +3906,8 @@ static void run_state_machine(struct tcpm_port *port)
                port->potential_contaminant = ((port->enter_state == SRC_ATTACH_WAIT &&
                                                port->state == SRC_UNATTACHED) ||
                                               (port->enter_state == SNK_ATTACH_WAIT &&
+                                               port->state == SNK_UNATTACHED) ||
+                                              (port->enter_state == SNK_DEBOUNCED &&
                                                port->state == SNK_UNATTACHED));
 
        port->enter_state = port->state;
index 37b56ce75f39d5311ef6320d048c6b9669bb70ef..0e867f531d344bea87596b8522dc70d2375a7ef4 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/usb/typec_altmode.h>
 #include <linux/usb/role.h>
 #include <linux/workqueue.h>
+#include <linux/firmware.h>
 
 #include "tps6598x.h"
 #include "trace.h"
 #define TPS_REG_STATUS                 0x1a
 #define TPS_REG_SYSTEM_CONF            0x28
 #define TPS_REG_CTRL_CONF              0x29
+#define TPS_REG_BOOT_STATUS            0x2D
 #define TPS_REG_POWER_STATUS           0x3f
+#define TPS_REG_PD_STATUS              0x40
 #define TPS_REG_RX_IDENTITY_SOP                0x48
 #define TPS_REG_DATA_STATUS            0x5f
+#define TPS_REG_SLEEP_CONF             0x70
 
 /* TPS_REG_SYSTEM_CONF bits */
 #define TPS_SYSCONF_PORTINFO(c)                ((c) & 7)
 
+/*
+ * BPMs task timeout, recommended 5 seconds
+ * pg.48 TPS2575 Host Interface Technical Reference
+ * Manual (Rev. A)
+ * https://www.ti.com/lit/ug/slvuc05a/slvuc05a.pdf
+ */
+#define TPS_BUNDLE_TIMEOUT     0x32
+
+/* BPMs return code */
+#define TPS_TASK_BPMS_INVALID_BUNDLE_SIZE      0x4
+#define TPS_TASK_BPMS_INVALID_SLAVE_ADDR       0x5
+#define TPS_TASK_BPMS_INVALID_TIMEOUT          0x6
+
+/* PBMc data out */
+#define TPS_PBMC_RC    0 /* Return code */
+#define TPS_PBMC_DPCS  2 /* device patch complete status */
+
 enum {
        TPS_PORTINFO_SINK,
        TPS_PORTINFO_SINK_ACCESSORY,
@@ -68,6 +89,7 @@ enum {
        TPS_MODE_BOOT,
        TPS_MODE_BIST,
        TPS_MODE_DISC,
+       TPS_MODE_PTCH,
 };
 
 static const char *const modes[] = {
@@ -75,11 +97,22 @@ static const char *const modes[] = {
        [TPS_MODE_BOOT] = "BOOT",
        [TPS_MODE_BIST] = "BIST",
        [TPS_MODE_DISC] = "DISC",
+       [TPS_MODE_PTCH] = "PTCH",
 };
 
 /* Unrecognized commands will be replaced with "!CMD" */
 #define INVALID_CMD(_cmd_)             (_cmd_ == 0x444d4321)
 
+struct tps6598x;
+
+struct tipd_data {
+       irq_handler_t irq_handler;
+       int (*register_port)(struct tps6598x *tps, struct fwnode_handle *node);
+       void (*trace_power_status)(u16 status);
+       void (*trace_status)(u32 status);
+       int (*apply_patch)(struct tps6598x *tps);
+};
+
 struct tps6598x {
        struct device *dev;
        struct regmap *regmap;
@@ -97,9 +130,11 @@ struct tps6598x {
        enum power_supply_usb_type usb_type;
 
        int wakeup;
+       u32 status; /* status reg */
        u16 pwr_status;
        struct delayed_work     wq_poll;
-       irq_handler_t irq_handler;
+
+       const struct tipd_data *data;
 };
 
 static enum power_supply_property tps6598x_psy_props[] = {
@@ -180,6 +215,11 @@ static inline int tps6598x_read64(struct tps6598x *tps, u8 reg, u64 *val)
        return tps6598x_block_read(tps, reg, val, sizeof(u64));
 }
 
+static inline int tps6598x_write8(struct tps6598x *tps, u8 reg, u8 val)
+{
+       return tps6598x_block_write(tps, reg, &val, sizeof(u8));
+}
+
 static inline int tps6598x_write64(struct tps6598x *tps, u8 reg, u64 val)
 {
        return tps6598x_block_write(tps, reg, &val, sizeof(u64));
@@ -282,9 +322,10 @@ static void tps6598x_disconnect(struct tps6598x *tps, u32 status)
        power_supply_changed(tps->psy);
 }
 
-static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd,
+static int tps6598x_exec_cmd_tmo(struct tps6598x *tps, const char *cmd,
                             size_t in_len, u8 *in_data,
-                            size_t out_len, u8 *out_data)
+                            size_t out_len, u8 *out_data,
+                            u32 cmd_timeout_ms, u32 res_delay_ms)
 {
        unsigned long timeout;
        u32 val;
@@ -307,8 +348,7 @@ static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd,
        if (ret < 0)
                return ret;
 
-       /* XXX: Using 1s for now, but it may not be enough for every command. */
-       timeout = jiffies + msecs_to_jiffies(1000);
+       timeout = jiffies + msecs_to_jiffies(cmd_timeout_ms);
 
        do {
                ret = tps6598x_read32(tps, TPS_REG_CMD1, &val);
@@ -321,6 +361,9 @@ static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd,
                        return -ETIMEDOUT;
        } while (val);
 
+       /* some commands require delay for the result to be available */
+       mdelay(res_delay_ms);
+
        if (out_len) {
                ret = tps6598x_block_read(tps, TPS_REG_DATA1,
                                          out_data, out_len);
@@ -345,6 +388,14 @@ static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd,
        return 0;
 }
 
+static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd,
+                            size_t in_len, u8 *in_data,
+                            size_t out_len, u8 *out_data)
+{
+       return tps6598x_exec_cmd_tmo(tps, cmd, in_len, in_data,
+                                    out_len, out_data, 1000, 0);
+}
+
 static int tps6598x_dr_set(struct typec_port *port, enum typec_data_role role)
 {
        const char *cmd = (role == TYPEC_DEVICE) ? "SWUF" : "SWDF";
@@ -419,7 +470,9 @@ static bool tps6598x_read_status(struct tps6598x *tps, u32 *status)
                dev_err(tps->dev, "%s: failed to read status\n", __func__);
                return false;
        }
-       trace_tps6598x_status(*status);
+
+       if (tps->data->trace_status)
+               tps->data->trace_status(*status);
 
        return true;
 }
@@ -450,7 +503,9 @@ static bool tps6598x_read_power_status(struct tps6598x *tps)
                return false;
        }
        tps->pwr_status = pwr_status;
-       trace_tps6598x_power_status(pwr_status);
+
+       if (tps->data->trace_power_status)
+               tps->data->trace_power_status(pwr_status);
 
        return true;
 }
@@ -513,6 +568,65 @@ err_unlock:
        return IRQ_NONE;
 }
 
+static bool tps6598x_has_role_changed(struct tps6598x *tps, u32 status)
+{
+       status ^= tps->status;
+
+       return status & (TPS_STATUS_PORTROLE | TPS_STATUS_DATAROLE);
+}
+
+static irqreturn_t tps25750_interrupt(int irq, void *data)
+{
+       struct tps6598x *tps = data;
+       u64 event[2] = { };
+       u32 status;
+       int ret;
+
+       mutex_lock(&tps->lock);
+
+       ret = tps6598x_block_read(tps, TPS_REG_INT_EVENT1, event, 11);
+       if (ret) {
+               dev_err(tps->dev, "%s: failed to read events\n", __func__);
+               goto err_unlock;
+       }
+       trace_tps25750_irq(event[0]);
+
+       if (!(event[0] | event[1]))
+               goto err_unlock;
+
+       if (!tps6598x_read_status(tps, &status))
+               goto err_clear_ints;
+
+       if ((event[0] | event[1]) & TPS_REG_INT_POWER_STATUS_UPDATE)
+               if (!tps6598x_read_power_status(tps))
+                       goto err_clear_ints;
+
+       if ((event[0] | event[1]) & TPS_REG_INT_DATA_STATUS_UPDATE)
+               if (!tps6598x_read_data_status(tps))
+                       goto err_clear_ints;
+
+       /*
+        * data/port roles could be updated independently after
+        * a plug event. Therefore, we need to check
+        * for pr/dr status change to set TypeC dr/pr accordingly.
+        */
+       if ((event[0] | event[1]) & TPS_REG_INT_PLUG_EVENT ||
+           tps6598x_has_role_changed(tps, status))
+               tps6598x_handle_plug_event(tps, status);
+
+       tps->status = status;
+
+err_clear_ints:
+       tps6598x_block_write(tps, TPS_REG_INT_CLEAR1, event, 11);
+
+err_unlock:
+       mutex_unlock(&tps->lock);
+
+       if (event[0] | event[1])
+               return IRQ_HANDLED;
+       return IRQ_NONE;
+}
+
 static irqreturn_t tps6598x_interrupt(int irq, void *data)
 {
        struct tps6598x *tps = data;
@@ -568,7 +682,7 @@ static void tps6598x_poll_work(struct work_struct *work)
        struct tps6598x *tps = container_of(to_delayed_work(work),
                                            struct tps6598x, wq_poll);
 
-       tps->irq_handler(0, tps);
+       tps->data->irq_handler(0, tps);
        queue_delayed_work(system_power_efficient_wq,
                           &tps->wq_poll, msecs_to_jiffies(POLL_INTERVAL));
 }
@@ -582,12 +696,15 @@ static int tps6598x_check_mode(struct tps6598x *tps)
        if (ret)
                return ret;
 
-       switch (match_string(modes, ARRAY_SIZE(modes), mode)) {
+       ret = match_string(modes, ARRAY_SIZE(modes), mode);
+
+       switch (ret) {
        case TPS_MODE_APP:
-               return 0;
+       case TPS_MODE_PTCH:
+               return ret;
        case TPS_MODE_BOOT:
                dev_warn(tps->dev, "dead-battery condition\n");
-               return 0;
+               return ret;
        case TPS_MODE_BIST:
        case TPS_MODE_DISC:
        default:
@@ -697,18 +814,374 @@ static int devm_tps6598_psy_register(struct tps6598x *tps)
        return PTR_ERR_OR_ZERO(tps->psy);
 }
 
+static int
+tps6598x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode)
+{
+       int ret;
+       u32 conf;
+       struct typec_capability typec_cap = { };
+
+       ret = tps6598x_read32(tps, TPS_REG_SYSTEM_CONF, &conf);
+       if (ret)
+               return ret;
+
+       typec_cap.revision = USB_TYPEC_REV_1_2;
+       typec_cap.pd_revision = 0x200;
+       typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE;
+       typec_cap.driver_data = tps;
+       typec_cap.ops = &tps6598x_ops;
+       typec_cap.fwnode = fwnode;
+
+       switch (TPS_SYSCONF_PORTINFO(conf)) {
+       case TPS_PORTINFO_SINK_ACCESSORY:
+       case TPS_PORTINFO_SINK:
+               typec_cap.type = TYPEC_PORT_SNK;
+               typec_cap.data = TYPEC_PORT_UFP;
+               break;
+       case TPS_PORTINFO_DRP_UFP_DRD:
+       case TPS_PORTINFO_DRP_DFP_DRD:
+               typec_cap.type = TYPEC_PORT_DRP;
+               typec_cap.data = TYPEC_PORT_DRD;
+               break;
+       case TPS_PORTINFO_DRP_UFP:
+               typec_cap.type = TYPEC_PORT_DRP;
+               typec_cap.data = TYPEC_PORT_UFP;
+               break;
+       case TPS_PORTINFO_DRP_DFP:
+               typec_cap.type = TYPEC_PORT_DRP;
+               typec_cap.data = TYPEC_PORT_DFP;
+               break;
+       case TPS_PORTINFO_SOURCE:
+               typec_cap.type = TYPEC_PORT_SRC;
+               typec_cap.data = TYPEC_PORT_DFP;
+               break;
+       default:
+               return -ENODEV;
+       }
+
+       tps->port = typec_register_port(tps->dev, &typec_cap);
+       if (IS_ERR(tps->port))
+               return PTR_ERR(tps->port);
+
+       return 0;
+}
+
+static int
+tps25750_write_firmware(struct tps6598x *tps,
+                       u8 bpms_addr, const u8 *data, size_t len)
+{
+       struct i2c_client *client = to_i2c_client(tps->dev);
+       int ret;
+       u8 slave_addr;
+       int timeout;
+
+       slave_addr = client->addr;
+       timeout = client->adapter->timeout;
+
+       /*
+        * binary configuration size is around ~16Kbytes
+        * which might take some time to finish writing it
+        */
+       client->adapter->timeout = msecs_to_jiffies(5000);
+       client->addr = bpms_addr;
+
+       ret = regmap_raw_write(tps->regmap, data[0], &data[1], len - 1);
+
+       client->addr = slave_addr;
+       client->adapter->timeout = timeout;
+
+       return ret;
+}
+
+static int
+tps25750_exec_pbms(struct tps6598x *tps, u8 *in_data, size_t in_len)
+{
+       int ret;
+       u8 rc;
+
+       ret = tps6598x_exec_cmd_tmo(tps, "PBMs", in_len, in_data,
+                                   sizeof(rc), &rc, 4000, 0);
+       if (ret)
+               return ret;
+
+       switch (rc) {
+       case TPS_TASK_BPMS_INVALID_BUNDLE_SIZE:
+               dev_err(tps->dev, "%s: invalid fw size\n", __func__);
+               return -EINVAL;
+       case TPS_TASK_BPMS_INVALID_SLAVE_ADDR:
+               dev_err(tps->dev, "%s: invalid slave address\n", __func__);
+               return -EINVAL;
+       case TPS_TASK_BPMS_INVALID_TIMEOUT:
+               dev_err(tps->dev, "%s: timed out\n", __func__);
+               return -ETIMEDOUT;
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+static int tps25750_abort_patch_process(struct tps6598x *tps)
+{
+       int ret;
+
+       ret = tps6598x_exec_cmd(tps, "PBMe", 0, NULL, 0, NULL);
+       if (ret)
+               return ret;
+
+       ret = tps6598x_check_mode(tps);
+       if (ret != TPS_MODE_PTCH)
+               dev_err(tps->dev, "failed to switch to \"PTCH\" mode\n");
+
+       return ret;
+}
+
+static int tps25750_start_patch_burst_mode(struct tps6598x *tps)
+{
+       int ret;
+       const struct firmware *fw;
+       const char *firmware_name;
+       struct {
+               u32 fw_size;
+               u8 addr;
+               u8 timeout;
+       } __packed bpms_data;
+       u32 addr;
+       struct device_node *np = tps->dev->of_node;
+
+       ret = device_property_read_string(tps->dev, "firmware-name",
+                                         &firmware_name);
+       if (ret)
+               return ret;
+
+       ret = request_firmware(&fw, firmware_name, tps->dev);
+       if (ret) {
+               dev_err(tps->dev, "failed to retrieve \"%s\"\n", firmware_name);
+               return ret;
+       }
+
+       if (fw->size == 0) {
+               ret = -EINVAL;
+               goto release_fw;
+       }
+
+       ret = of_property_match_string(np, "reg-names", "patch-address");
+       if (ret < 0) {
+               dev_err(tps->dev, "failed to get patch-address %d\n", ret);
+               return ret;
+       }
+
+       ret = of_property_read_u32_index(np, "reg", ret, &addr);
+       if (ret)
+               return ret;
+
+       if (addr == 0 || (addr >= 0x20 && addr <= 0x23)) {
+               dev_err(tps->dev, "wrong patch address %u\n", addr);
+               return -EINVAL;
+       }
+
+       bpms_data.addr = (u8)addr;
+       bpms_data.fw_size = fw->size;
+       bpms_data.timeout = TPS_BUNDLE_TIMEOUT;
+
+       ret = tps25750_exec_pbms(tps, (u8 *)&bpms_data, sizeof(bpms_data));
+       if (ret)
+               goto release_fw;
+
+       ret = tps25750_write_firmware(tps, bpms_data.addr, fw->data, fw->size);
+       if (ret) {
+               dev_err(tps->dev, "Failed to write patch %s of %zu bytes\n",
+                       firmware_name, fw->size);
+               goto release_fw;
+       }
+
+       /*
+        * A delay of 500us is required after the firmware is written
+        * based on pg.62 in tps6598x Host Interface Technical
+        * Reference Manual
+        * https://www.ti.com/lit/ug/slvuc05a/slvuc05a.pdf
+        */
+       udelay(500);
+
+release_fw:
+       release_firmware(fw);
+
+       return ret;
+}
+
+static int tps25750_complete_patch_process(struct tps6598x *tps)
+{
+       int ret;
+       u8 out_data[40];
+       u8 dummy[2] = { };
+
+       /*
+        * Without writing something to DATA_IN, this command would
+        * return an error
+        */
+       ret = tps6598x_exec_cmd_tmo(tps, "PBMc", sizeof(dummy), dummy,
+                                   sizeof(out_data), out_data, 2000, 20);
+       if (ret)
+               return ret;
+
+       if (out_data[TPS_PBMC_RC]) {
+               dev_err(tps->dev,
+                       "%s: pbmc failed: %u\n", __func__,
+                       out_data[TPS_PBMC_RC]);
+               return -EIO;
+       }
+
+       if (out_data[TPS_PBMC_DPCS]) {
+               dev_err(tps->dev,
+                       "%s: failed device patch complete status: %u\n",
+                       __func__, out_data[TPS_PBMC_DPCS]);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static int tps25750_apply_patch(struct tps6598x *tps)
+{
+       int ret;
+       unsigned long timeout;
+       u64 status = 0;
+
+       ret = tps6598x_block_read(tps, TPS_REG_BOOT_STATUS, &status, 5);
+       if (ret)
+               return ret;
+       /*
+        * Nothing to be done if the configuration
+        * is being loaded from EERPOM
+        */
+       if (status & TPS_BOOT_STATUS_I2C_EEPROM_PRESENT)
+               goto wait_for_app;
+
+       ret = tps25750_start_patch_burst_mode(tps);
+       if (ret) {
+               tps25750_abort_patch_process(tps);
+               return ret;
+       }
+
+       ret = tps25750_complete_patch_process(tps);
+       if (ret)
+               return ret;
+
+wait_for_app:
+       timeout = jiffies + msecs_to_jiffies(1000);
+
+       do {
+               ret = tps6598x_check_mode(tps);
+               if (ret < 0)
+                       return ret;
+
+               if (time_is_before_jiffies(timeout))
+                       return -ETIMEDOUT;
+
+       } while (ret != TPS_MODE_APP);
+
+       /*
+        * The dead battery flag may be triggered when the controller
+        * port is connected to a device that can source power and
+        * attempts to power up both the controller and the board it is on.
+        * To restore controller functionality, it is necessary to clear
+        * this flag
+        */
+       if (status & TPS_BOOT_STATUS_DEAD_BATTERY_FLAG) {
+               ret = tps6598x_exec_cmd(tps, "DBfg", 0, NULL, 0, NULL);
+               if (ret) {
+                       dev_err(tps->dev, "failed to clear dead battery %d\n", ret);
+                       return ret;
+               }
+       }
+
+       dev_info(tps->dev, "controller switched to \"APP\" mode\n");
+
+       return 0;
+};
+
+static int tps25750_init(struct tps6598x *tps)
+{
+       int ret;
+
+       ret = tps->data->apply_patch(tps);
+       if (ret)
+               return ret;
+
+       ret = tps6598x_write8(tps, TPS_REG_SLEEP_CONF,
+                             TPS_SLEEP_CONF_SLEEP_MODE_ALLOWED);
+       if (ret)
+               dev_warn(tps->dev,
+                        "%s: failed to enable sleep mode: %d\n",
+                        __func__, ret);
+
+       return 0;
+}
+
+static int
+tps25750_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode)
+{
+       struct typec_capability typec_cap = { };
+       const char *data_role;
+       u8 pd_status;
+       int ret;
+
+       ret = tps6598x_read8(tps, TPS_REG_PD_STATUS, &pd_status);
+       if (ret)
+               return ret;
+
+       ret = fwnode_property_read_string(fwnode, "data-role", &data_role);
+       if (ret) {
+               dev_err(tps->dev, "data-role not found: %d\n", ret);
+               return ret;
+       }
+
+       ret = typec_find_port_data_role(data_role);
+       if (ret < 0) {
+               dev_err(tps->dev, "unknown data-role: %s\n", data_role);
+               return ret;
+       }
+
+       typec_cap.data = ret;
+       typec_cap.revision = USB_TYPEC_REV_1_3;
+       typec_cap.pd_revision = 0x300;
+       typec_cap.driver_data = tps;
+       typec_cap.ops = &tps6598x_ops;
+       typec_cap.fwnode = fwnode;
+       typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE;
+
+       switch (TPS_PD_STATUS_PORT_TYPE(pd_status)) {
+       case TPS_PD_STATUS_PORT_TYPE_SINK_SOURCE:
+       case TPS_PD_STATUS_PORT_TYPE_SOURCE_SINK:
+               typec_cap.type = TYPEC_PORT_DRP;
+               break;
+       case TPS_PD_STATUS_PORT_TYPE_SINK:
+               typec_cap.type = TYPEC_PORT_SNK;
+               break;
+       case TPS_PD_STATUS_PORT_TYPE_SOURCE:
+               typec_cap.type = TYPEC_PORT_SRC;
+               break;
+       default:
+               return -ENODEV;
+       }
+
+       tps->port = typec_register_port(tps->dev, &typec_cap);
+       if (IS_ERR(tps->port))
+               return PTR_ERR(tps->port);
+
+       return 0;
+}
+
 static int tps6598x_probe(struct i2c_client *client)
 {
-       irq_handler_t irq_handler = tps6598x_interrupt;
        struct device_node *np = client->dev.of_node;
-       struct typec_capability typec_cap = { };
        struct tps6598x *tps;
        struct fwnode_handle *fwnode;
        u32 status;
-       u32 conf;
        u32 vid;
        int ret;
        u64 mask1;
+       bool is_tps25750;
 
        tps = devm_kzalloc(&client->dev, sizeof(*tps), GFP_KERNEL);
        if (!tps)
@@ -721,9 +1194,12 @@ static int tps6598x_probe(struct i2c_client *client)
        if (IS_ERR(tps->regmap))
                return PTR_ERR(tps->regmap);
 
-       ret = tps6598x_read32(tps, TPS_REG_VID, &vid);
-       if (ret < 0 || !vid)
-               return -ENODEV;
+       is_tps25750 = device_is_compatible(tps->dev, "ti,tps25750");
+       if (!is_tps25750) {
+               ret = tps6598x_read32(tps, TPS_REG_VID, &vid);
+               if (ret < 0 || !vid)
+                       return -ENODEV;
+       }
 
        /*
         * Checking can the adapter handle SMBus protocol. If it can not, the
@@ -743,7 +1219,6 @@ static int tps6598x_probe(struct i2c_client *client)
                        APPLE_CD_REG_INT_DATA_STATUS_UPDATE |
                        APPLE_CD_REG_INT_PLUG_EVENT;
 
-               irq_handler = cd321x_interrupt;
        } else {
                /* Enable power status, data status and plug event interrupts */
                mask1 = TPS_REG_INT_POWER_STATUS_UPDATE |
@@ -751,24 +1226,29 @@ static int tps6598x_probe(struct i2c_client *client)
                        TPS_REG_INT_PLUG_EVENT;
        }
 
-       tps->irq_handler = irq_handler;
+       tps->data = device_get_match_data(tps->dev);
+       if (!tps->data)
+               return -EINVAL;
+
        /* Make sure the controller has application firmware running */
        ret = tps6598x_check_mode(tps);
-       if (ret)
+       if (ret < 0)
                return ret;
 
+       if (is_tps25750 && ret == TPS_MODE_PTCH) {
+               ret = tps25750_init(tps);
+               if (ret)
+                       return ret;
+       }
+
        ret = tps6598x_write64(tps, TPS_REG_INT_MASK1, mask1);
        if (ret)
-               return ret;
-
-       ret = tps6598x_read32(tps, TPS_REG_STATUS, &status);
-       if (ret < 0)
-               goto err_clear_mask;
-       trace_tps6598x_status(status);
+               goto err_reset_controller;
 
-       ret = tps6598x_read32(tps, TPS_REG_SYSTEM_CONF, &conf);
-       if (ret < 0)
+       if (!tps6598x_read_status(tps, &status)) {
+               ret = -ENODEV;
                goto err_clear_mask;
+       }
 
        /*
         * This fwnode has a "compatible" property, but is never populated as a
@@ -787,50 +1267,13 @@ static int tps6598x_probe(struct i2c_client *client)
                goto err_fwnode_put;
        }
 
-       typec_cap.revision = USB_TYPEC_REV_1_2;
-       typec_cap.pd_revision = 0x200;
-       typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE;
-       typec_cap.driver_data = tps;
-       typec_cap.ops = &tps6598x_ops;
-       typec_cap.fwnode = fwnode;
-
-       switch (TPS_SYSCONF_PORTINFO(conf)) {
-       case TPS_PORTINFO_SINK_ACCESSORY:
-       case TPS_PORTINFO_SINK:
-               typec_cap.type = TYPEC_PORT_SNK;
-               typec_cap.data = TYPEC_PORT_UFP;
-               break;
-       case TPS_PORTINFO_DRP_UFP_DRD:
-       case TPS_PORTINFO_DRP_DFP_DRD:
-               typec_cap.type = TYPEC_PORT_DRP;
-               typec_cap.data = TYPEC_PORT_DRD;
-               break;
-       case TPS_PORTINFO_DRP_UFP:
-               typec_cap.type = TYPEC_PORT_DRP;
-               typec_cap.data = TYPEC_PORT_UFP;
-               break;
-       case TPS_PORTINFO_DRP_DFP:
-               typec_cap.type = TYPEC_PORT_DRP;
-               typec_cap.data = TYPEC_PORT_DFP;
-               break;
-       case TPS_PORTINFO_SOURCE:
-               typec_cap.type = TYPEC_PORT_SRC;
-               typec_cap.data = TYPEC_PORT_DFP;
-               break;
-       default:
-               ret = -ENODEV;
-               goto err_role_put;
-       }
-
        ret = devm_tps6598_psy_register(tps);
        if (ret)
                goto err_role_put;
 
-       tps->port = typec_register_port(&client->dev, &typec_cap);
-       if (IS_ERR(tps->port)) {
-               ret = PTR_ERR(tps->port);
+       ret = tps->data->register_port(tps, fwnode);
+       if (ret)
                goto err_role_put;
-       }
 
        if (status & TPS_STATUS_PLUG_PRESENT) {
                ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &tps->pwr_status);
@@ -845,7 +1288,7 @@ static int tps6598x_probe(struct i2c_client *client)
 
        if (client->irq) {
                ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
-                                               irq_handler,
+                                               tps->data->irq_handler,
                                                IRQF_SHARED | IRQF_ONESHOT,
                                                dev_name(&client->dev), tps);
        } else {
@@ -879,6 +1322,10 @@ err_fwnode_put:
        fwnode_handle_put(fwnode);
 err_clear_mask:
        tps6598x_write64(tps, TPS_REG_INT_MASK1, 0);
+err_reset_controller:
+       /* Reset PD controller to remove any applied patch */
+       if (is_tps25750)
+               tps6598x_exec_cmd_tmo(tps, "GAID", 0, NULL, 0, NULL, 2000, 0);
        return ret;
 }
 
@@ -889,9 +1336,14 @@ static void tps6598x_remove(struct i2c_client *client)
        if (!client->irq)
                cancel_delayed_work_sync(&tps->wq_poll);
 
+       devm_free_irq(tps->dev, client->irq, tps);
        tps6598x_disconnect(tps, 0);
        typec_unregister_port(tps->port);
        usb_role_switch_put(tps->role_sw);
+
+       /* Reset PD controller to remove any applied patch */
+       if (device_is_compatible(tps->dev, "ti,tps25750"))
+               tps6598x_exec_cmd_tmo(tps, "GAID", 0, NULL, 0, NULL, 2000, 0);
 }
 
 static int __maybe_unused tps6598x_suspend(struct device *dev)
@@ -914,6 +1366,17 @@ static int __maybe_unused tps6598x_resume(struct device *dev)
 {
        struct i2c_client *client = to_i2c_client(dev);
        struct tps6598x *tps = i2c_get_clientdata(client);
+       int ret;
+
+       ret = tps6598x_check_mode(tps);
+       if (ret < 0)
+               return ret;
+
+       if (device_is_compatible(tps->dev, "ti,tps25750") && ret == TPS_MODE_PTCH) {
+               ret = tps25750_init(tps);
+               if (ret)
+                       return ret;
+       }
 
        if (tps->wakeup) {
                disable_irq_wake(client->irq);
@@ -931,9 +1394,32 @@ static const struct dev_pm_ops tps6598x_pm_ops = {
        SET_SYSTEM_SLEEP_PM_OPS(tps6598x_suspend, tps6598x_resume)
 };
 
+static const struct tipd_data cd321x_data = {
+       .irq_handler = cd321x_interrupt,
+       .register_port = tps6598x_register_port,
+       .trace_power_status = trace_tps6598x_power_status,
+       .trace_status = trace_tps6598x_status,
+};
+
+static const struct tipd_data tps6598x_data = {
+       .irq_handler = tps6598x_interrupt,
+       .register_port = tps6598x_register_port,
+       .trace_power_status = trace_tps6598x_power_status,
+       .trace_status = trace_tps6598x_status,
+};
+
+static const struct tipd_data tps25750_data = {
+       .irq_handler = tps25750_interrupt,
+       .register_port = tps25750_register_port,
+       .trace_power_status = trace_tps25750_power_status,
+       .trace_status = trace_tps25750_status,
+       .apply_patch = tps25750_apply_patch,
+};
+
 static const struct of_device_id tps6598x_of_match[] = {
-       { .compatible = "ti,tps6598x", },
-       { .compatible = "apple,cd321x", },
+       { .compatible = "ti,tps6598x", &tps6598x_data},
+       { .compatible = "apple,cd321x", &cd321x_data},
+       { .compatible = "ti,tps25750", &tps25750_data},
        {}
 };
 MODULE_DEVICE_TABLE(of, tps6598x_of_match);
index 527857549d699ea6cbd426beab91c71961729b96..01609bf509e4be7e393ac7eddf651c05ec0cc23d 100644 (file)
 #define TPS_POWER_STATUS_BC12_STATUS_CDP 2
 #define TPS_POWER_STATUS_BC12_STATUS_DCP 3
 
+/* TPS25750_REG_POWER_STATUS bits */
+#define TPS25750_POWER_STATUS_CHARGER_DETECT_STATUS_MASK       GENMASK(7, 4)
+#define TPS25750_POWER_STATUS_CHARGER_DETECT_STATUS(p) \
+       TPS_FIELD_GET(TPS25750_POWER_STATUS_CHARGER_DETECT_STATUS_MASK, (p))
+#define TPS25750_POWER_STATUS_CHARGER_ADVERTISE_STATUS_MASK    GENMASK(9, 8)
+#define TPS25750_POWER_STATUS_CHARGER_ADVERTISE_STATUS(p) \
+       TPS_FIELD_GET(TPS25750_POWER_STATUS_CHARGER_ADVERTISE_STATUS_MASK, (p))
+
+#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DISABLED      0
+#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_IN_PROGRESS   1
+#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_NONE          2
+#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_SPD           3
+#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_BC_1_2_CPD    4
+#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_BC_1_2_DPD    5
+#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_1_DCP     6
+#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_2_DCP     7
+#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_3_DCP     8
+#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_1_2V_DCP      9
+
 /* TPS_REG_DATA_STATUS bits */
 #define TPS_DATA_STATUS_DATA_CONNECTION             BIT(0)
 #define TPS_DATA_STATUS_UPSIDE_DOWN         BIT(1)
 #define TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_A    BIT(2)
 #define TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_B    (BIT(2) | BIT(1))
 
+/* BOOT STATUS REG*/
+#define TPS_BOOT_STATUS_DEAD_BATTERY_FLAG      BIT(2)
+#define TPS_BOOT_STATUS_I2C_EEPROM_PRESENT     BIT(3)
+
+/* PD STATUS REG */
+#define TPS_REG_PD_STATUS_PORT_TYPE_MASK       GENMASK(5, 4)
+#define TPS_PD_STATUS_PORT_TYPE(x) \
+       TPS_FIELD_GET(TPS_REG_PD_STATUS_PORT_TYPE_MASK, x)
+
+#define TPS_PD_STATUS_PORT_TYPE_SINK_SOURCE    0
+#define TPS_PD_STATUS_PORT_TYPE_SINK           1
+#define TPS_PD_STATUS_PORT_TYPE_SOURCE         2
+#define TPS_PD_STATUS_PORT_TYPE_SOURCE_SINK    3
+
+/* SLEEP CONF REG */
+#define TPS_SLEEP_CONF_SLEEP_MODE_ALLOWED      BIT(0)
+
 #endif /* __TPS6598X_H__ */
index 12cad1bde7cc5f3d4415858355f6dcfcc4de6d68..0669cca12ea1d4d753fb51f5d60d69fc1032be49 100644 (file)
                { APPLE_CD_REG_INT_DATA_STATUS_UPDATE,          "DATA_STATUS_UPDATE" }, \
                { APPLE_CD_REG_INT_STATUS_UPDATE,               "STATUS_UPDATE" })
 
+#define show_tps25750_irq_flags(flags) \
+       __print_flags_u64(flags, "|", \
+               { TPS_REG_INT_PLUG_EVENT,                       "PLUG_EVENT" }, \
+               { TPS_REG_INT_POWER_STATUS_UPDATE,              "POWER_STATUS_UPDATE" }, \
+               { TPS_REG_INT_STATUS_UPDATE,                    "STATUS_UPDATE" }, \
+               { TPS_REG_INT_PD_STATUS_UPDATE,                 "PD_STATUS_UPDATE" })
+
 #define TPS6598X_STATUS_FLAGS_MASK (GENMASK(31, 0) ^ (TPS_STATUS_CONN_STATE_MASK | \
                                                      TPS_STATUS_PP_5V0_SWITCH_MASK | \
                                                      TPS_STATUS_PP_HV_SWITCH_MASK | \
                                                      TPS_STATUS_USB_HOST_PRESENT_MASK | \
                                                      TPS_STATUS_LEGACY_MASK))
 
+#define TPS25750_STATUS_FLAGS_MASK (GENMASK(31, 0) ^ (TPS_STATUS_CONN_STATE_MASK | \
+                                                     GENMASK(19, 7) | \
+                                                     TPS_STATUS_VBUS_STATUS_MASK | \
+                                                     TPS_STATUS_USB_HOST_PRESENT_MASK | \
+                                                     TPS_STATUS_LEGACY_MASK | \
+                                                     BIT(26) | \
+                                                     GENMASK(31, 28)))
+
 #define show_status_conn_state(status) \
        __print_symbolic(TPS_STATUS_CONN_STATE((status)), \
                { TPS_STATUS_CONN_STATE_CONN_WITH_R_A,  "conn-Ra"  }, \
                      { TPS_STATUS_HIGH_VOLAGE_WARNING, "HIGH_VOLAGE_WARNING" }, \
                      { TPS_STATUS_HIGH_LOW_VOLTAGE_WARNING, "HIGH_LOW_VOLTAGE_WARNING" })
 
+#define show_tps25750_status_flags(flags) \
+       __print_flags((flags & TPS25750_STATUS_FLAGS_MASK), "|", \
+                     { TPS_STATUS_PLUG_PRESENT,        "PLUG_PRESENT" }, \
+                     { TPS_STATUS_PLUG_UPSIDE_DOWN,    "UPSIDE_DOWN" }, \
+                     { TPS_STATUS_PORTROLE,            "PORTROLE" }, \
+                     { TPS_STATUS_DATAROLE,            "DATAROLE" }, \
+                     { TPS_STATUS_BIST,                "BIST" })
+
 #define show_power_status_source_sink(power_status) \
        __print_symbolic(TPS_POWER_STATUS_SOURCESINK(power_status), \
                { 1, "sink" }, \
                { TPS_POWER_STATUS_BC12_STATUS_CDP, "cdp" }, \
                { TPS_POWER_STATUS_BC12_STATUS_SDP, "sdp" })
 
+#define show_tps25750_power_status_charger_detect_status(power_status) \
+       __print_symbolic(TPS25750_POWER_STATUS_CHARGER_DETECT_STATUS(power_status), \
+               { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DISABLED,    "disabled"}, \
+               { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_IN_PROGRESS, "in progress"}, \
+               { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_NONE,        "none"}, \
+               { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_SPD,         "spd"}, \
+               { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_BC_1_2_CPD,  "cpd"}, \
+               { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_BC_1_2_DPD,  "dpd"}, \
+               { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_1_DCP,   "divider 1 dcp"}, \
+               { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_2_DCP,   "divider 2 dcp"}, \
+               { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_3_DCP,   "divider 3 dpc"}, \
+               { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_1_2V_DCP,    "1.2V dpc"})
+
 #define TPS_DATA_STATUS_FLAGS_MASK (GENMASK(31, 0) ^ (TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK | \
                                                      TPS_DATA_STATUS_TBT_CABLE_SPEED_MASK | \
                                                      TPS_DATA_STATUS_TBT_CABLE_GEN_MASK))
@@ -230,6 +266,21 @@ TRACE_EVENT(cd321x_irq,
                      show_cd321x_irq_flags(__entry->event))
 );
 
+TRACE_EVENT(tps25750_irq,
+           TP_PROTO(u64 event),
+           TP_ARGS(event),
+
+           TP_STRUCT__entry(
+                            __field(u64, event)
+                            ),
+
+           TP_fast_assign(
+                          __entry->event = event;
+                          ),
+
+           TP_printk("event=%s", show_tps25750_irq_flags(__entry->event))
+);
+
 TRACE_EVENT(tps6598x_status,
            TP_PROTO(u32 status),
            TP_ARGS(status),
@@ -257,6 +308,27 @@ TRACE_EVENT(tps6598x_status,
                    )
 );
 
+TRACE_EVENT(tps25750_status,
+           TP_PROTO(u32 status),
+           TP_ARGS(status),
+
+           TP_STRUCT__entry(
+                            __field(u32, status)
+                            ),
+
+           TP_fast_assign(
+                          __entry->status = status;
+                          ),
+
+           TP_printk("conn: %s, vbus: %s, usb-host: %s, legacy: %s, flags: %s",
+                     show_status_conn_state(__entry->status),
+                     show_status_vbus_status(__entry->status),
+                     show_status_usb_host_present(__entry->status),
+                     show_status_legacy(__entry->status),
+                     show_tps25750_status_flags(__entry->status)
+                   )
+);
+
 TRACE_EVENT(tps6598x_power_status,
            TP_PROTO(u16 power_status),
            TP_ARGS(power_status),
@@ -277,6 +349,26 @@ TRACE_EVENT(tps6598x_power_status,
                    )
 );
 
+TRACE_EVENT(tps25750_power_status,
+           TP_PROTO(u16 power_status),
+           TP_ARGS(power_status),
+
+           TP_STRUCT__entry(
+                            __field(u16, power_status)
+                            ),
+
+           TP_fast_assign(
+                          __entry->power_status = power_status;
+                          ),
+
+           TP_printk("conn: %d, pwr-role: %s, typec: %s, charger detect: %s",
+                     !!TPS_POWER_STATUS_CONNECTION(__entry->power_status),
+                     show_power_status_source_sink(__entry->power_status),
+                     show_power_status_typec_status(__entry->power_status),
+                     show_tps25750_power_status_charger_detect_status(__entry->power_status)
+                   )
+);
+
 TRACE_EVENT(tps6598x_data_status,
            TP_PROTO(u32 data_status),
            TP_ARGS(data_status),
index 73cd5bf350472fcb88756a4faa94c0757edab1fe..d9d3c91125ca888fd1d1f14e61f0858700675762 100644 (file)
@@ -315,7 +315,7 @@ struct typec_altmode *ucsi_register_displayport(struct ucsi_connector *con,
        struct ucsi_dp *dp;
 
        /* We can't rely on the firmware with the capabilities. */
-       desc->vdo |= DP_CAP_DP_SIGNALING | DP_CAP_RECEPTACLE;
+       desc->vdo |= DP_CAP_DP_SIGNALLING(0) | DP_CAP_RECEPTACLE;
 
        /* Claiming that we support all pin assignments */
        desc->vdo |= all_assignments << 8;
index 607061a37eca3de4ce5499e8dae2602fc5c718b2..449c125f6f87008ec636c95491180aef1e81cfcf 100644 (file)
@@ -501,8 +501,8 @@ static void ucsi_ccg_nvidia_altmode(struct ucsi_ccg *uc,
        case NVIDIA_FTB_DP_OFFSET:
                if (alt[0].mid == USB_TYPEC_NVIDIA_VLINK_DBG_VDO)
                        alt[0].mid = USB_TYPEC_NVIDIA_VLINK_DP_VDO |
-                               DP_CAP_DP_SIGNALING | DP_CAP_USB |
-                               DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_E));
+                                    DP_CAP_DP_SIGNALLING(0) | DP_CAP_USB |
+                                    DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_E));
                break;
        case NVIDIA_FTB_DBG_OFFSET:
                if (alt[0].mid == USB_TYPEC_NVIDIA_VLINK_DP_VDO)
index bb1854b3311dc70c9450b42a64d07b83957e8a22..db6e248f82083130a1dc578cc363a79c10789234 100644 (file)
@@ -8,9 +8,13 @@
 #include <linux/mutex.h>
 #include <linux/property.h>
 #include <linux/soc/qcom/pdr.h>
+#include <linux/usb/typec_mux.h>
+#include <linux/gpio/consumer.h>
 #include <linux/soc/qcom/pmic_glink.h>
 #include "ucsi.h"
 
+#define PMIC_GLINK_MAX_PORTS   2
+
 #define UCSI_BUF_SIZE                   48
 
 #define MSG_TYPE_REQ_RESP               1
@@ -52,6 +56,9 @@ struct ucsi_notify_ind_msg {
 struct pmic_glink_ucsi {
        struct device *dev;
 
+       struct gpio_desc *port_orientation[PMIC_GLINK_MAX_PORTS];
+       struct typec_switch *port_switch[PMIC_GLINK_MAX_PORTS];
+
        struct pmic_glink_client *client;
 
        struct ucsi *ucsi;
@@ -220,8 +227,20 @@ static void pmic_glink_ucsi_notify(struct work_struct *work)
        }
 
        con_num = UCSI_CCI_CONNECTOR(cci);
-       if (con_num)
+       if (con_num) {
+               if (con_num < PMIC_GLINK_MAX_PORTS &&
+                   ucsi->port_orientation[con_num - 1]) {
+                       int orientation = gpiod_get_value(ucsi->port_orientation[con_num - 1]);
+
+                       if (orientation >= 0) {
+                               typec_switch_set(ucsi->port_switch[con_num - 1],
+                                                orientation ? TYPEC_ORIENTATION_REVERSE
+                                                            : TYPEC_ORIENTATION_NORMAL);
+                       }
+               }
+
                ucsi_connector_change(ucsi->ucsi, con_num);
+       }
 
        if (ucsi->sync_pending && cci & UCSI_CCI_BUSY) {
                ucsi->sync_val = -EBUSY;
@@ -282,6 +301,7 @@ static int pmic_glink_ucsi_probe(struct auxiliary_device *adev,
 {
        struct pmic_glink_ucsi *ucsi;
        struct device *dev = &adev->dev;
+       struct fwnode_handle *fwnode;
        int ret;
 
        ucsi = devm_kzalloc(dev, sizeof(*ucsi), GFP_KERNEL);
@@ -309,6 +329,38 @@ static int pmic_glink_ucsi_probe(struct auxiliary_device *adev,
 
        ucsi_set_drvdata(ucsi->ucsi, ucsi);
 
+       device_for_each_child_node(dev, fwnode) {
+               struct gpio_desc *desc;
+               u32 port;
+
+               ret = fwnode_property_read_u32(fwnode, "reg", &port);
+               if (ret < 0) {
+                       dev_err(dev, "missing reg property of %pOFn\n", fwnode);
+                       return ret;
+               }
+
+               if (port >= PMIC_GLINK_MAX_PORTS) {
+                       dev_warn(dev, "invalid connector number, ignoring\n");
+                       continue;
+               }
+
+               desc = devm_gpiod_get_index_optional(&adev->dev, "orientation", port, GPIOD_IN);
+
+               /* If GPIO isn't found, continue */
+               if (!desc)
+                       continue;
+
+               if (IS_ERR(desc))
+                       return dev_err_probe(dev, PTR_ERR(desc),
+                                            "unable to acquire orientation gpio\n");
+               ucsi->port_orientation[port] = desc;
+
+               ucsi->port_switch[port] = fwnode_typec_switch_get(fwnode);
+               if (IS_ERR(ucsi->port_switch[port]))
+                       return dev_err_probe(dev, PTR_ERR(ucsi->port_switch[port]),
+                                       "failed to acquire orientation-switch\n");
+       }
+
        ucsi->client = devm_pmic_glink_register_client(dev,
                                                       PMIC_GLINK_OWNER_USBC,
                                                       pmic_glink_ucsi_callback,
index 9c6954aad6c882c804f0750fd9f939623362521a..ce625b1ce9a5125c43655acc46c160d227d37686 100644 (file)
@@ -464,8 +464,13 @@ static void stub_disconnect(struct usb_device *udev)
        /* release port */
        rc = usb_hub_release_port(udev->parent, udev->portnum,
                                  (struct usb_dev_state *) udev);
-       if (rc) {
-               dev_dbg(&udev->dev, "unable to release port\n");
+       /*
+        * NOTE: If a HUB disconnect triggered disconnect of the down stream
+        * device usb_hub_release_port will return -ENODEV so we can safely ignore
+        * that error here.
+        */
+       if (rc && (rc != -ENODEV)) {
+               dev_dbg(&udev->dev, "unable to release port (%i)\n", rc);
                return;
        }
 
index 37d1fc34e8a564485dc1243667bf0265e6c5f16d..82650c11e4516125777dc55e4719e398fbbb644c 100644 (file)
@@ -1140,6 +1140,7 @@ static int hcd_name_to_id(const char *name)
 static int vhci_setup(struct usb_hcd *hcd)
 {
        struct vhci *vhci = *((void **)dev_get_platdata(hcd->self.controller));
+
        if (usb_hcd_is_primary_hcd(hcd)) {
                vhci->vhci_hcd_hs = hcd_to_vhci_hcd(hcd);
                vhci->vhci_hcd_hs->vhci = vhci;
@@ -1493,13 +1494,10 @@ static struct platform_driver vhci_driver = {
 
 static void del_platform_devices(void)
 {
-       struct platform_device *pdev;
        int i;
 
        for (i = 0; i < vhci_num_controllers; i++) {
-               pdev = vhcis[i].pdev;
-               if (pdev != NULL)
-                       platform_device_unregister(pdev);
+               platform_device_unregister(vhcis[i].pdev);
                vhcis[i].pdev = NULL;
        }
        sysfs_remove_link(&platform_bus.kobj, driver_name);
@@ -1519,45 +1517,33 @@ static int __init vhci_hcd_init(void)
        if (vhcis == NULL)
                return -ENOMEM;
 
-       for (i = 0; i < vhci_num_controllers; i++) {
-               vhcis[i].pdev = platform_device_alloc(driver_name, i);
-               if (!vhcis[i].pdev) {
-                       i--;
-                       while (i >= 0)
-                               platform_device_put(vhcis[i--].pdev);
-                       ret = -ENOMEM;
-                       goto err_device_alloc;
-               }
-       }
-       for (i = 0; i < vhci_num_controllers; i++) {
-               void *vhci = &vhcis[i];
-               ret = platform_device_add_data(vhcis[i].pdev, &vhci, sizeof(void *));
-               if (ret)
-                       goto err_driver_register;
-       }
-
        ret = platform_driver_register(&vhci_driver);
        if (ret)
                goto err_driver_register;
 
        for (i = 0; i < vhci_num_controllers; i++) {
-               ret = platform_device_add(vhcis[i].pdev);
+               void *vhci = &vhcis[i];
+               struct platform_device_info pdevinfo = {
+                       .name = driver_name,
+                       .id = i,
+                       .data = &vhci,
+                       .size_data = sizeof(void *),
+               };
+
+               vhcis[i].pdev = platform_device_register_full(&pdevinfo);
+               ret = PTR_ERR_OR_ZERO(vhcis[i].pdev);
                if (ret < 0) {
-                       i--;
-                       while (i >= 0)
-                               platform_device_del(vhcis[i--].pdev);
+                       while (i--)
+                               platform_device_unregister(vhcis[i].pdev);
                        goto err_add_hcd;
                }
        }
 
-       return ret;
+       return 0;
 
 err_add_hcd:
        platform_driver_unregister(&vhci_driver);
 err_driver_register:
-       for (i = 0; i < vhci_num_controllers; i++)
-               platform_device_put(vhcis[i].pdev);
-err_device_alloc:
        kfree(vhcis);
        return ret;
 }
index 02333f47c9941b84477e395aff17a1095cba9b5c..6151c210d987d4468aa84c6bcb3584256548a1ca 100644 (file)
@@ -175,7 +175,7 @@ void tb_unregister_property_dir(const char *key, struct tb_property_dir *dir);
  * enum tb_link_width - Thunderbolt/USB4 link width
  * @TB_LINK_WIDTH_SINGLE: Single lane link
  * @TB_LINK_WIDTH_DUAL: Dual lane symmetric link
- * @TB_LINK_WIDTH_ASYM_TX: Dual lane asymmetric Gen 4 link with 3 trasmitters
+ * @TB_LINK_WIDTH_ASYM_TX: Dual lane asymmetric Gen 4 link with 3 transmitters
  * @TB_LINK_WIDTH_ASYM_RX: Dual lane asymmetric Gen 4 link with 3 receivers
  */
 enum tb_link_width {
index a21074861f91fe351027a9ce325868d7cd5ccae1..8c61643acd4993ceca4fe4bb0382d4261ccd9c84 100644 (file)
@@ -1823,22 +1823,6 @@ void *usb_alloc_coherent(struct usb_device *dev, size_t size,
 void usb_free_coherent(struct usb_device *dev, size_t size,
        void *addr, dma_addr_t dma);
 
-#if 0
-struct urb *usb_buffer_map(struct urb *urb);
-void usb_buffer_dmasync(struct urb *urb);
-void usb_buffer_unmap(struct urb *urb);
-#endif
-
-struct scatterlist;
-int usb_buffer_map_sg(const struct usb_device *dev, int is_in,
-                     struct scatterlist *sg, int nents);
-#if 0
-void usb_buffer_dmasync_sg(const struct usb_device *dev, int is_in,
-                          struct scatterlist *sg, int n_hw_ents);
-#endif
-void usb_buffer_unmap_sg(const struct usb_device *dev, int is_in,
-                        struct scatterlist *sg, int n_hw_ents);
-
 /*-------------------------------------------------------------------*
  *                         SYNCHRONOUS CALL SUPPORT                  *
  *-------------------------------------------------------------------*/
index 0b4f2d5faa080dfd122a051d8fde6b706fe6c61f..5a7f96684ea226e2de0512e7f3246f1712dc6cd7 100644 (file)
@@ -64,6 +64,7 @@ struct ci_hdrc_platform_data {
 #define CI_HDRC_PMQOS                  BIT(15)
 #define CI_HDRC_PHY_VBUS_CONTROL       BIT(16)
 #define CI_HDRC_HAS_PORTSC_PEC_MISSED  BIT(17)
+#define CI_HDRC_FORCE_VBUS_ACTIVE_ALWAYS       BIT(18)
        enum usb_dr_mode        dr_mode;
 #define CI_HDRC_CONTROLLER_RESET_EVENT         0
 #define CI_HDRC_CONTROLLER_STOPPED_EVENT       1
index 6014340ba980de112ad4dc3f6cff6f6d1593b6ef..af3cd2aae4bcb3a14ca60a6c9e8ec31af5dcb8f3 100644 (file)
  * are ready. The control transfer will then be kept from completing till
  * all the function drivers that requested for USB_GADGET_DELAYED_STAUS
  * invoke usb_composite_setup_continue().
+ *
+ * NOTE: USB_GADGET_DELAYED_STATUS must not be used in UDC drivers: they
+ * must delay completing the status stage for 0-length control transfers
+ * regardless of the whether USB_GADGET_DELAYED_STATUS is returned from
+ * the gadget driver's setup() callback.
+ * Currently, a number of UDC drivers rely on USB_GADGET_DELAYED_STATUS,
+ * which is a bug. These drivers must be fixed and USB_GADGET_DELAYED_STATUS
+ * must be contained within the composite framework.
  */
 #define USB_GADGET_DELAYED_STATUS       0x7fff /* Impossibly large value */
 
index 75bda0783395a0f48c21611196264feb9cab6222..6532beb587b1978e09bc5b17dc088daf91f9f88c 100644 (file)
@@ -711,6 +711,15 @@ static inline int usb_gadget_check_config(struct usb_gadget *gadget)
  * get_interface.  Setting a configuration (or interface) is where
  * endpoints should be activated or (config 0) shut down.
  *
+ * The gadget driver's setup() callback does not have to queue a response to
+ * ep0 within the setup() call, the driver can do it after setup() returns.
+ * The UDC driver must wait until such a response is queued before proceeding
+ * with the data/status stages of the control transfer.
+ *
+ * NOTE: Currently, a number of UDC drivers rely on USB_GADGET_DELAYED_STATUS
+ * being returned from the setup() callback, which is a bug. See the comment
+ * next to USB_GADGET_DELAYED_STATUS for details.
+ *
  * (Note that only the default control endpoint is supported.  Neither
  * hosts nor devices generally support control traffic except to ep0.)
  *
index 61d4f0b793dcdc7300b2c991e53b862381edcb35..00724b4f6e122bf700da6c0c81c250c7c6bc19f7 100644 (file)
@@ -484,8 +484,25 @@ extern int usb_hcd_pci_probe(struct pci_dev *dev,
 extern void usb_hcd_pci_remove(struct pci_dev *dev);
 extern void usb_hcd_pci_shutdown(struct pci_dev *dev);
 
+#ifdef CONFIG_USB_PCI_AMD
 extern int usb_hcd_amd_remote_wakeup_quirk(struct pci_dev *dev);
 
+static inline bool usb_hcd_amd_resume_bug(struct pci_dev *dev,
+                                         const struct hc_driver *driver)
+{
+       if (!usb_hcd_amd_remote_wakeup_quirk(dev))
+               return false;
+       if (driver->flags & (HCD_USB11 | HCD_USB3))
+               return true;
+       return false;
+}
+#else /* CONFIG_USB_PCI_AMD */
+static inline bool usb_hcd_amd_resume_bug(struct pci_dev *dev,
+                                         const struct hc_driver *driver)
+{
+       return false;
+}
+#endif
 extern const struct dev_pm_ops usb_hcd_pci_pm_ops;
 #endif /* CONFIG_USB_PCI */
 
diff --git a/include/linux/usb/ljca.h b/include/linux/usb/ljca.h
new file mode 100644 (file)
index 0000000..47661fe
--- /dev/null
@@ -0,0 +1,145 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2023, Intel Corporation. All rights reserved.
+ */
+#ifndef _LINUX_USB_LJCA_H_
+#define _LINUX_USB_LJCA_H_
+
+#include <linux/auxiliary_bus.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+#define LJCA_MAX_GPIO_NUM 64
+
+#define auxiliary_dev_to_ljca_client(auxiliary_dev)                    \
+               container_of(auxiliary_dev, struct ljca_client, auxdev)
+
+struct ljca_adapter;
+
+/**
+ * typedef ljca_event_cb_t - event callback function signature
+ *
+ * @context: the execution context of who registered this callback
+ * @cmd: the command from device for this event
+ * @evt_data: the event data payload
+ * @len: the event data payload length
+ *
+ * The callback function is called in interrupt context and the data payload is
+ * only valid during the call. If the user needs later access of the data, it
+ * must copy it.
+ */
+typedef void (*ljca_event_cb_t)(void *context, u8 cmd, const void *evt_data, int len);
+
+/**
+ * struct ljca_client - represent a ljca client device
+ *
+ * @type: ljca client type
+ * @id: ljca client id within same client type
+ * @link: ljca client on the same ljca adapter
+ * @auxdev: auxiliary device object
+ * @adapter: ljca adapter the ljca client sit on
+ * @context: the execution context of the event callback
+ * @event_cb: ljca client driver register this callback to get
+ *     firmware asynchronous rx buffer pending notifications
+ * @event_cb_lock: spinlock to protect event callback
+ */
+struct ljca_client {
+       u8 type;
+       u8 id;
+       struct list_head link;
+       struct auxiliary_device auxdev;
+       struct ljca_adapter *adapter;
+
+       void *context;
+       ljca_event_cb_t event_cb;
+       /* lock to protect event_cb */
+       spinlock_t event_cb_lock;
+};
+
+/**
+ * struct ljca_gpio_info - ljca gpio client device info
+ *
+ * @num: ljca gpio client device pin number
+ * @valid_pin_map: ljca gpio client device valid pin mapping
+ */
+struct ljca_gpio_info {
+       unsigned int num;
+       DECLARE_BITMAP(valid_pin_map, LJCA_MAX_GPIO_NUM);
+};
+
+/**
+ * struct ljca_i2c_info - ljca i2c client device info
+ *
+ * @id: ljca i2c client device identification number
+ * @capacity: ljca i2c client device capacity
+ * @intr_pin: ljca i2c client device interrupt pin number if exists
+ */
+struct ljca_i2c_info {
+       u8 id;
+       u8 capacity;
+       u8 intr_pin;
+};
+
+/**
+ * struct ljca_spi_info - ljca spi client device info
+ *
+ * @id: ljca spi client device identification number
+ * @capacity: ljca spi client device capacity
+ */
+struct ljca_spi_info {
+       u8 id;
+       u8 capacity;
+};
+
+/**
+ * ljca_register_event_cb - register a callback function to receive events
+ *
+ * @client: ljca client device
+ * @event_cb: callback function
+ * @context: execution context of event callback
+ *
+ * Return: 0 in case of success, negative value in case of error
+ */
+int ljca_register_event_cb(struct ljca_client *client, ljca_event_cb_t event_cb, void *context);
+
+/**
+ * ljca_unregister_event_cb - unregister the callback function for an event
+ *
+ * @client: ljca client device
+ */
+void ljca_unregister_event_cb(struct ljca_client *client);
+
+/**
+ * ljca_transfer - issue a LJCA command and wait for a response
+ *
+ * @client: ljca client device
+ * @cmd: the command to be sent to the device
+ * @obuf: the buffer to be sent to the device; it can be NULL if the user
+ *     doesn't need to transmit data with this command
+ * @obuf_len: the size of the buffer to be sent to the device; it should
+ *     be 0 when obuf is NULL
+ * @ibuf: any data associated with the response will be copied here; it can be
+ *     NULL if the user doesn't need the response data
+ * @ibuf_len: must be initialized to the input buffer size
+ *
+ * Return: the actual length of response data for success, negative value for errors
+ */
+int ljca_transfer(struct ljca_client *client, u8 cmd, const u8 *obuf,
+                 u8 obuf_len, u8 *ibuf, u8 ibuf_len);
+
+/**
+ * ljca_transfer_noack - issue a LJCA command without a response
+ *
+ * @client: ljca client device
+ * @cmd: the command to be sent to the device
+ * @obuf: the buffer to be sent to the device; it can be NULL if the user
+ *     doesn't need to transmit data with this command
+ * @obuf_len: the size of the buffer to be sent to the device
+ *
+ * Return: 0 for success, negative value for errors
+ */
+int ljca_transfer_noack(struct ljca_client *client, u8 cmd, const u8 *obuf,
+                       u8 obuf_len);
+
+#endif
index c59fb79a42e8672111c0f146c8d535c04c395777..eb626af0e4e7cfc739842a34736317688ac88187 100644 (file)
@@ -228,6 +228,7 @@ enum pd_pdo_type {
 #define PDO_FIXED_UNCHUNK_EXT          BIT(24) /* Unchunked Extended Message supported (Source) */
 #define PDO_FIXED_FRS_CURR_MASK                (BIT(24) | BIT(23)) /* FR_Swap Current (Sink) */
 #define PDO_FIXED_FRS_CURR_SHIFT       23
+#define PDO_FIXED_PEAK_CURR_SHIFT      20
 #define PDO_FIXED_VOLT_SHIFT           10      /* 50mV units */
 #define PDO_FIXED_CURR_SHIFT           0       /* 10mA units */
 
index b057250704e898d7a97a82d8b78e774e247b9e0f..3a747938cdab499ad77607aebfbddb38a0c09026 100644 (file)
         | ((vbm) & 0x3) << 9 | (sbu) << 8 | (sbut) << 7 | ((cur) & 0x3) << 5   \
         | (vbt) << 4 | (sopp) << 3 | ((spd) & 0x7))
 
+#define VDO_TYPEC_CABLE_SPEED(vdo)     ((vdo) & 0x7)
 #define VDO_TYPEC_CABLE_TYPE(vdo)      (((vdo) >> 18) & 0x3)
 
 /*
index d418c55523a7e2115cb8ce44af9d259bd449fcde..372898d9eeb00bbc3ae034717c0d9335da015e49 100644 (file)
@@ -5,16 +5,6 @@
  * Copyright (C) 2011 Renesas Solutions Corp.
  * Copyright (C) 2019 Renesas Electronics Corporation
  * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
- *
  */
 #ifndef RENESAS_USB_H
 #define RENESAS_USB_H
index 8fa781207970ae6f83a2f1ed2f512d1fd2f32de7..a05d6f6f2536921306c6a9c8177956c943f488cc 100644 (file)
@@ -202,6 +202,8 @@ struct typec_cable_desc {
  * @accessory: Audio, Debug or none.
  * @identity: Discover Identity command data
  * @pd_revision: USB Power Delivery Specification Revision if supported
+ * @attach: Notification about attached USB device
+ * @deattach: Notification about removed USB device
  *
  * Details about a partner that is attached to USB Type-C port. If @identity
  * member exists when partner is registered, a directory named "identity" is
@@ -217,6 +219,9 @@ struct typec_partner_desc {
        enum typec_accessory    accessory;
        struct usb_pd_identity  *identity;
        u16                     pd_revision; /* 0300H = "3.0" */
+
+       void (*attach)(struct typec_partner *partner, struct device *dev);
+       void (*deattach)(struct typec_partner *partner, struct device *dev);
 };
 
 /**
@@ -335,4 +340,36 @@ int typec_port_set_usb_power_delivery(struct typec_port *port, struct usb_power_
 int typec_partner_set_usb_power_delivery(struct typec_partner *partner,
                                         struct usb_power_delivery *pd);
 
+/**
+ * struct typec_connector - Representation of Type-C port for external drivers
+ * @attach: notification about device removal
+ * @deattach: notification about device removal
+ *
+ * Drivers that control the USB and other ports (DisplayPorts, etc.), that are
+ * connected to the Type-C connectors, can use these callbacks to inform the
+ * Type-C connector class about connections and disconnections. That information
+ * can then be used by the typec-port drivers to power on or off parts that are
+ * needed or not needed - as an example, in USB mode if USB2 device is
+ * enumerated, USB3 components (retimers, phys, and what have you) do not need
+ * to be powered on.
+ *
+ * The attached (enumerated) devices will be liked with the typec-partner device.
+ */
+struct typec_connector {
+       void (*attach)(struct typec_connector *con, struct device *dev);
+       void (*deattach)(struct typec_connector *con, struct device *dev);
+};
+
+static inline void typec_attach(struct typec_connector *con, struct device *dev)
+{
+       if (con && con->attach)
+               con->attach(con, dev);
+}
+
+static inline void typec_deattach(struct typec_connector *con, struct device *dev)
+{
+       if (con && con->deattach)
+               con->deattach(con, dev);
+}
+
 #endif /* __LINUX_USB_TYPEC_H */
index 8d09c2f0a9b80717cdda7eba91cf63959d3be158..1f358098522df71b2a8435b2e69c5f5b8530ee6e 100644 (file)
@@ -67,8 +67,10 @@ enum {
 #define   DP_CAP_UFP_D                 1
 #define   DP_CAP_DFP_D                 2
 #define   DP_CAP_DFP_D_AND_UFP_D       3
-#define DP_CAP_DP_SIGNALING            BIT(2) /* Always set */
-#define DP_CAP_GEN2                    BIT(3) /* Reserved after v1.0b */
+#define DP_CAP_DP_SIGNALLING(_cap_)    (((_cap_) & GENMASK(5, 2)) >> 2)
+#define   DP_CAP_SIGNALLING_HBR3       1
+#define   DP_CAP_SIGNALLING_UHBR10     2
+#define   DP_CAP_SIGNALLING_UHBR20     3
 #define DP_CAP_RECEPTACLE              BIT(6)
 #define DP_CAP_USB                     BIT(7)
 #define DP_CAP_DFP_D_PIN_ASSIGN(_cap_) (((_cap_) & GENMASK(15, 8)) >> 8)
@@ -78,6 +80,13 @@ enum {
                        DP_CAP_UFP_D_PIN_ASSIGN(_cap_) : DP_CAP_DFP_D_PIN_ASSIGN(_cap_))
 #define DP_CAP_PIN_ASSIGN_DFP_D(_cap_) ((_cap_ & DP_CAP_RECEPTACLE) ? \
                        DP_CAP_DFP_D_PIN_ASSIGN(_cap_) : DP_CAP_UFP_D_PIN_ASSIGN(_cap_))
+#define DP_CAP_UHBR_13_5_SUPPORT       BIT(26)
+#define DP_CAP_CABLE_TYPE(_cap_)       (((_cap_) & GENMASK(29, 28)) >> 28)
+#define   DP_CAP_CABLE_TYPE_PASSIVE    0
+#define   DP_CAP_CABLE_TYPE_RE_TIMER   1
+#define   DP_CAP_CABLE_TYPE_RE_DRIVER  2
+#define   DP_CAP_CABLE_TYPE_OPTICAL    3
+#define DP_CAP_DPAM_VERSION            BIT(30)
 
 /* DisplayPort Status Update VDO bits */
 #define DP_STATUS_CONNECTION(_status_) ((_status_) & 3)
@@ -97,13 +106,24 @@ enum {
 #define DP_CONF_CURRENTLY(_conf_)      ((_conf_) & 3)
 #define DP_CONF_UFP_U_AS_DFP_D         BIT(0)
 #define DP_CONF_UFP_U_AS_UFP_D         BIT(1)
-#define DP_CONF_SIGNALING_DP           BIT(2)
-#define DP_CONF_SIGNALING_GEN_2                BIT(3) /* Reserved after v1.0b */
+#define DP_CONF_SIGNALLING_MASK                GENMASK(5, 2)
+#define DP_CONF_SIGNALLING_SHIFT       2
+#define   DP_CONF_SIGNALLING_HBR3      1
+#define   DP_CONF_SIGNALLING_UHBR10    2
+#define   DP_CONF_SIGNALLING_UHBR20    3
 #define DP_CONF_PIN_ASSIGNEMENT_SHIFT  8
 #define DP_CONF_PIN_ASSIGNEMENT_MASK   GENMASK(15, 8)
 
 /* Helper for setting/getting the pin assignment value to the configuration */
 #define DP_CONF_SET_PIN_ASSIGN(_a_)    ((_a_) << 8)
 #define DP_CONF_GET_PIN_ASSIGN(_conf_) (((_conf_) & GENMASK(15, 8)) >> 8)
+#define DP_CONF_UHBR13_5_SUPPORT       BIT(26)
+#define DP_CONF_CABLE_TYPE_MASK                GENMASK(29, 28)
+#define DP_CONF_CABLE_TYPE_SHIFT       28
+#define   DP_CONF_CABLE_TYPE_PASSIVE   0
+#define   DP_CONF_CABLE_TYPE_RE_TIMER  1
+#define   DP_CONF_CABLE_TYPE_RE_DRIVER 2
+#define   DP_CONF_CABLE_TYPE_OPTICAL   3
+#define DP_CONF_DPAM_VERSION           BIT(30)
 
 #endif /* __USB_TYPEC_DP_H */
index 63dd44b72e0c975f5fc3326dc1fdffaf34b4c167..c7a2153bd6f50fa2954f0a292176c3ad31a96811 100644 (file)
@@ -46,6 +46,7 @@ struct typec_thunderbolt_data {
 #define TBT_CABLE_OPTICAL              BIT(21)
 #define TBT_CABLE_RETIMER              BIT(22)
 #define TBT_CABLE_LINK_TRAINING                BIT(23)
+#define TBT_CABLE_ACTIVE_PASSIVE       BIT(25)
 
 #define TBT_SET_CABLE_SPEED(_s_)       (((_s_) & GENMASK(2, 0)) << 16)
 #define TBT_SET_CABLE_ROUNDED(_g_)     (((_g_) & GENMASK(1, 0)) << 19)
index c7d2199134d72f884dc6d11e2150241d30fffe5e..f0224a8dc85896fbca49d139d5f4226f7024ba47 100644 (file)
@@ -44,6 +44,16 @@ enum usb_raw_event_type {
        /* This event is queued when a new control request arrived to ep0. */
        USB_RAW_EVENT_CONTROL = 2,
 
+       /*
+        * These events are queued when the gadget driver is suspended,
+        * resumed, reset, or disconnected. Note that some UDCs (e.g. dwc2)
+        * report a disconnect event instead of a reset.
+        */
+       USB_RAW_EVENT_SUSPEND = 3,
+       USB_RAW_EVENT_RESUME = 4,
+       USB_RAW_EVENT_RESET = 5,
+       USB_RAW_EVENT_DISCONNECT = 6,
+
        /* The list might grow in the future. */
 };
 
@@ -54,8 +64,8 @@ enum usb_raw_event_type {
  *     actual length of the fetched event data.
  * @data: A buffer to store the fetched event data.
  *
- * Currently the fetched data buffer is empty for USB_RAW_EVENT_CONNECT,
- * and contains struct usb_ctrlrequest for USB_RAW_EVENT_CONTROL.
+ * The fetched event data buffer contains struct usb_ctrlrequest for
+ * USB_RAW_EVENT_CONTROL and is empty for other events.
  */
 struct usb_raw_event {
        __u32           type;