diff options
264 files changed, 11502 insertions, 2328 deletions
diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index cb172db41b34..be663258b9b7 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -292,6 +292,16 @@ Description: which is marked with early_stop has failed to initialize, it will ignore all future connections until this attribute is clear. +What: /sys/bus/usb/devices/.../<hub_interface>/port<X>/state +Date: June 2023 +Contact: Roy Luo <royluo@google.com> +Description: + Indicates current state of the USB device attached to the port. + Valid states are: 'not-attached', 'attached', 'powered', + 'reconnecting', 'unauthenticated', 'default', 'addressed', + 'configured', and 'suspended'. This file supports poll() to + monitor the state change from user space. + What: /sys/bus/usb/devices/.../power/usb2_lpm_l1_timeout Date: May 2013 Contact: Mathias Nyman <mathias.nyman@linux.intel.com> diff --git a/Documentation/ABI/testing/sysfs-driver-eud b/Documentation/ABI/testing/sysfs-driver-eud index 83f3872182a4..2bab0db2d2f0 100644 --- a/Documentation/ABI/testing/sysfs-driver-eud +++ b/Documentation/ABI/testing/sysfs-driver-eud @@ -1,4 +1,4 @@ -What: /sys/bus/platform/drivers/eud/.../enable +What: /sys/bus/platform/drivers/qcom_eud/.../enable Date: February 2022 Contact: Souradeep Chowdhury <quic_schowdhu@quicinc.com> Description: diff --git a/Documentation/devicetree/bindings/phy/qcom,sc7180-qmp-usb3-dp-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,sc7180-qmp-usb3-dp-phy.yaml index 0ef2c9b9d466..d30734338888 100644 --- a/Documentation/devicetree/bindings/phy/qcom,sc7180-qmp-usb3-dp-phy.yaml +++ b/Documentation/devicetree/bindings/phy/qcom,sc7180-qmp-usb3-dp-phy.yaml @@ -61,6 +61,10 @@ properties: power-domains: maxItems: 1 + orientation-switch: + description: Flag the port as possible handler of orientation switching + type: boolean + resets: items: - description: reset of phy block. @@ -251,6 +255,8 @@ examples: vdda-phy-supply = <&vdda_usb2_ss_1p2>; vdda-pll-supply = <&vdda_usb2_ss_core>; + orientation-switch; + usb3-phy@200 { reg = <0x200 0x128>, <0x400 0x200>, diff --git a/Documentation/devicetree/bindings/regulator/qcom,usb-vbus-regulator.yaml b/Documentation/devicetree/bindings/regulator/qcom,usb-vbus-regulator.yaml index b1cff3adb21b..89c564dfa5db 100644 --- a/Documentation/devicetree/bindings/regulator/qcom,usb-vbus-regulator.yaml +++ b/Documentation/devicetree/bindings/regulator/qcom,usb-vbus-regulator.yaml @@ -14,6 +14,9 @@ description: | regulator will be enabled in situations where the device is required to provide power to the connected peripheral. +allOf: + - $ref: regulator.yaml# + properties: compatible: enum: @@ -25,8 +28,11 @@ properties: required: - compatible + - reg + - regulator-min-microamp + - regulator-max-microamp -additionalProperties: false +unevaluatedProperties: false examples: - | @@ -36,6 +42,8 @@ examples: pm8150b_vbus: usb-vbus-regulator@1100 { compatible = "qcom,pm8150b-vbus-reg"; reg = <0x1100>; + regulator-min-microamp = <500000>; + regulator-max-microamp = <3000000>; }; }; ... diff --git a/Documentation/devicetree/bindings/reset/xlnx,zynqmp-reset.txt b/Documentation/devicetree/bindings/reset/xlnx,zynqmp-reset.txt deleted file mode 100644 index ed836868dbf1..000000000000 --- a/Documentation/devicetree/bindings/reset/xlnx,zynqmp-reset.txt +++ /dev/null @@ -1,55 +0,0 @@ --------------------------------------------------------------------------- - = Zynq UltraScale+ MPSoC and Versal reset driver binding = --------------------------------------------------------------------------- -The Zynq UltraScale+ MPSoC and Versal has several different resets. - -See Chapter 36 of the Zynq UltraScale+ MPSoC TRM (UG) for more information -about zynqmp resets. - -Please also refer to reset.txt in this directory for common reset -controller binding usage. - -Required Properties: -- compatible: "xlnx,zynqmp-reset" for Zynq UltraScale+ MPSoC platform - "xlnx,versal-reset" for Versal platform -- #reset-cells: Specifies the number of cells needed to encode reset - line, should be 1 - -------- -Example -------- - -firmware { - zynqmp_firmware: zynqmp-firmware { - compatible = "xlnx,zynqmp-firmware"; - method = "smc"; - - zynqmp_reset: reset-controller { - compatible = "xlnx,zynqmp-reset"; - #reset-cells = <1>; - }; - }; -}; - -Specifying reset lines connected to IP modules -============================================== - -Device nodes that need access to reset lines should -specify them as a reset phandle in their corresponding node as -specified in reset.txt. - -For list of all valid reset indices for Zynq UltraScale+ MPSoC see -<dt-bindings/reset/xlnx-zynqmp-resets.h> -For list of all valid reset indices for Versal see -<dt-bindings/reset/xlnx-versal-resets.h> - -Example: - -serdes: zynqmp_phy@fd400000 { - ... - - resets = <&zynqmp_reset ZYNQMP_RESET_SATA>; - reset-names = "sata_rst"; - - ... -}; diff --git a/Documentation/devicetree/bindings/reset/xlnx,zynqmp-reset.yaml b/Documentation/devicetree/bindings/reset/xlnx,zynqmp-reset.yaml new file mode 100644 index 000000000000..0d50f6a54af3 --- /dev/null +++ b/Documentation/devicetree/bindings/reset/xlnx,zynqmp-reset.yaml @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/reset/xlnx,zynqmp-reset.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Zynq UltraScale+ MPSoC and Versal reset + +maintainers: + - Piyush Mehta <piyush.mehta@amd.com> + +description: | + The Zynq UltraScale+ MPSoC and Versal has several different resets. + + The PS reset subsystem is responsible for handling the external reset + input to the device and that all internal reset requirements are met + for the system (as a whole) and for the functional units. + + Please also refer to reset.txt in this directory for common reset + controller binding usage. Device nodes that need access to reset + lines should specify them as a reset phandle in their corresponding + node as specified in reset.txt. + + For list of all valid reset indices for Zynq UltraScale+ MPSoC + <dt-bindings/reset/xlnx-zynqmp-resets.h> + + For list of all valid reset indices for Versal + <dt-bindings/reset/xlnx-versal-resets.h> + +properties: + compatible: + enum: + - xlnx,zynqmp-reset + - xlnx,versal-reset + + "#reset-cells": + const: 1 + +required: + - compatible + - "#reset-cells" + +additionalProperties: false + +examples: + - | + zynqmp_reset: reset-controller { + compatible = "xlnx,zynqmp-reset"; + #reset-cells = <1>; + }; + +... diff --git a/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.yaml b/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.yaml index b26d26c2b023..782402800d4a 100644 --- a/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.yaml +++ b/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.yaml @@ -45,7 +45,9 @@ properties: - fsl,vf610-usb - const: fsl,imx27-usb - items: - - const: fsl,imx8dxl-usb + - enum: + - fsl,imx8dxl-usb + - fsl,imx8ulp-usb - const: fsl,imx7ulp-usb - const: fsl,imx6ul-usb - items: diff --git a/Documentation/devicetree/bindings/usb/dwc2.yaml b/Documentation/devicetree/bindings/usb/dwc2.yaml index d3506090f8b1..0a5c98ea711d 100644 --- a/Documentation/devicetree/bindings/usb/dwc2.yaml +++ b/Documentation/devicetree/bindings/usb/dwc2.yaml @@ -53,6 +53,7 @@ properties: - amlogic,meson8b-usb - amlogic,meson-gxbb-usb - amlogic,meson-g12a-usb + - amlogic,meson-a1-usb - intel,socfpga-agilex-hsotg - const: snps,dwc2 - const: amcc,dwc-otg diff --git a/Documentation/devicetree/bindings/usb/fsl,imx8qm-cdns3.yaml b/Documentation/devicetree/bindings/usb/fsl,imx8qm-cdns3.yaml new file mode 100644 index 000000000000..ceb76394af60 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/fsl,imx8qm-cdns3.yaml @@ -0,0 +1,103 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (c) 2020 NXP +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/fsl,imx8qm-cdns3.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NXP iMX8QM Soc USB Controller + +maintainers: + - Frank Li <Frank.Li@nxp.com> + +properties: + compatible: + const: fsl,imx8qm-usb3 + + reg: + items: + - description: Register set for iMX USB3 Platform Control + + "#address-cells": + enum: [ 1, 2 ] + + "#size-cells": + enum: [ 1, 2 ] + + ranges: true + + clocks: + items: + - description: Standby clock. Used during ultra low power states. + - description: USB bus clock for usb3 controller. + - description: AXI clock for AXI interface. + - description: ipg clock for register access. + - description: Core clock for usb3 controller. + + clock-names: + items: + - const: lpm + - const: bus + - const: aclk + - const: ipg + - const: core + + power-domains: + maxItems: 1 + +# Required child node: + +patternProperties: + "^usb@[0-9a-f]+$": + $ref: cdns,usb3.yaml# + +required: + - compatible + - reg + - "#address-cells" + - "#size-cells" + - ranges + - clocks + - clock-names + - power-domains + +additionalProperties: false + +examples: + - | + #include <dt-bindings/clock/imx8-lpcg.h> + #include <dt-bindings/firmware/imx/rsrc.h> + #include <dt-bindings/interrupt-controller/arm-gic.h> + + usb@5b110000 { + compatible = "fsl,imx8qm-usb3"; + reg = <0x5b110000 0x10000>; + ranges; + clocks = <&usb3_lpcg IMX_LPCG_CLK_1>, + <&usb3_lpcg IMX_LPCG_CLK_0>, + <&usb3_lpcg IMX_LPCG_CLK_7>, + <&usb3_lpcg IMX_LPCG_CLK_4>, + <&usb3_lpcg IMX_LPCG_CLK_5>; + clock-names = "lpm", "bus", "aclk", "ipg", "core"; + assigned-clocks = <&clk IMX_SC_R_USB_2 IMX_SC_PM_CLK_MST_BUS>; + assigned-clock-rates = <250000000>; + power-domains = <&pd IMX_SC_R_USB_2>; + #address-cells = <1>; + #size-cells = <1>; + + usb@5b120000 { + compatible = "cdns,usb3"; + reg = <0x5b120000 0x10000>, /* memory area for OTG/DRD registers */ + <0x5b130000 0x10000>, /* memory area for HOST registers */ + <0x5b140000 0x10000>; /* memory area for DEVICE registers */ + reg-names = "otg", "xhci", "dev"; + interrupt-parent = <&gic>; + interrupts = <GIC_SPI 271 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 271 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 271 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 271 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "host", "peripheral", "otg", "wakeup"; + phys = <&usb3_phy>; + phy-names = "cdns3,usb3-phy"; + }; + }; diff --git a/Documentation/devicetree/bindings/usb/generic-ehci.yaml b/Documentation/devicetree/bindings/usb/generic-ehci.yaml index 9445764bd8de..b956bb5fada7 100644 --- a/Documentation/devicetree/bindings/usb/generic-ehci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-ehci.yaml @@ -61,6 +61,7 @@ properties: - ibm,476gtr-ehci - nxp,lpc1850-ehci - qca,ar7100-ehci + - rockchip,rk3588-ehci - snps,hsdk-v1.0-ehci - socionext,uniphier-ehci - const: generic-ehci diff --git a/Documentation/devicetree/bindings/usb/generic-ohci.yaml b/Documentation/devicetree/bindings/usb/generic-ohci.yaml index d06d1e7d8876..be268e23ca79 100644 --- a/Documentation/devicetree/bindings/usb/generic-ohci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-ohci.yaml @@ -44,6 +44,7 @@ properties: - hpe,gxp-ohci - ibm,476gtr-ohci - ingenic,jz4740-ohci + - rockchip,rk3588-ohci - snps,hsdk-v1.0-ohci - const: generic-ohci - enum: @@ -69,7 +70,7 @@ properties: clocks: minItems: 1 - maxItems: 3 + maxItems: 4 description: | In case the Renesas R-Car Gen3 SoCs: - if a host only channel: first clock should be host. @@ -147,6 +148,20 @@ allOf: then: properties: transceiver: false + - if: + properties: + compatible: + contains: + const: rockchip,rk3588-ohci + then: + properties: + clocks: + minItems: 4 + else: + properties: + clocks: + minItems: 1 + maxItems: 3 unevaluatedProperties: false diff --git a/Documentation/devicetree/bindings/usb/microchip,usb5744.yaml b/Documentation/devicetree/bindings/usb/microchip,usb5744.yaml new file mode 100644 index 000000000000..ff3a1707ef57 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/microchip,usb5744.yaml @@ -0,0 +1,107 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/microchip,usb5744.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Microchip USB5744 4-port Hub Controller + +description: + Microchip's USB5744 SmartHubTM IC is a 4 port, SuperSpeed (SS)/Hi-Speed (HS), + low power, low pin count configurable and fully compliant with the USB 3.1 + Gen 1 specification. The USB5744 also supports Full Speed (FS) and Low Speed + (LS) USB signaling, offering complete coverage of all defined USB operating + speeds. The new SuperSpeed hubs operate in parallel with the USB 2.0 + controller, so 5 Gbps SuperSpeed data transfers are not affected by slower + USB 2.0 traffic. + +maintainers: + - Piyush Mehta <piyush.mehta@amd.com> + - Michal Simek <michal.simek@amd.com> + +properties: + compatible: + enum: + - usb424,2744 + - usb424,5744 + - microchip,usb5744 + + reg: + maxItems: 1 + + reset-gpios: + maxItems: 1 + description: + GPIO controlling the GRST# pin. + + vdd-supply: + description: + VDD power supply to the hub + + peer-hub: + $ref: /schemas/types.yaml#/definitions/phandle + description: + phandle to the peer hub on the controller. + + i2c-bus: + $ref: /schemas/types.yaml#/definitions/phandle + description: + phandle of an usb hub connected via i2c bus. + +required: + - compatible + - reg + +allOf: + - if: + properties: + compatible: + contains: + const: microchip,usb5744 + then: + properties: + reset-gpios: false + vdd-supply: false + peer-hub: false + i2c-bus: false + else: + $ref: /schemas/usb/usb-device.yaml + required: + - peer-hub + +additionalProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + i2c: i2c { + #address-cells = <1>; + #size-cells = <0>; + hub: usb-hub@2d { + compatible = "microchip,usb5744"; + reg = <0x2d>; + }; + }; + + usb { + #address-cells = <1>; + #size-cells = <0>; + + /* 2.0 hub on port 1 */ + hub_2_0: hub@1 { + compatible = "usb424,2744"; + reg = <1>; + peer-hub = <&hub_3_0>; + i2c-bus = <&hub>; + reset-gpios = <&gpio 3 GPIO_ACTIVE_LOW>; + }; + + /* 3.0 hub on port 2 */ + hub_3_0: hub@2 { + compatible = "usb424,5744"; + reg = <2>; + peer-hub = <&hub_2_0>; + i2c-bus = <&hub>; + reset-gpios = <&gpio 3 GPIO_ACTIVE_LOW>; + }; + }; diff --git a/Documentation/devicetree/bindings/usb/onnn,nb7vpq904m.yaml b/Documentation/devicetree/bindings/usb/onnn,nb7vpq904m.yaml new file mode 100644 index 000000000000..c0201da002f6 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/onnn,nb7vpq904m.yaml @@ -0,0 +1,141 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/onnn,nb7vpq904m.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ON Semiconductor Type-C DisplayPort ALT Mode Linear Redriver + +maintainers: + - Neil Armstrong <neil.armstrong@linaro.org> + +properties: + compatible: + enum: + - onnn,nb7vpq904m + + reg: + maxItems: 1 + + vcc-supply: + description: power supply (1.8V) + + enable-gpios: true + + 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#/$defs/port-base + description: Super Speed (SS) Input endpoint from the Super-Speed PHY + unevaluatedProperties: false + + properties: + endpoint: + $ref: /schemas/graph.yaml#/$defs/endpoint-base + unevaluatedProperties: false + + properties: + data-lanes: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: | + An array of physical data lane indexes. Position determines how + lanes are connected to the redriver, It is assumed the same order + is kept on the other side of the redriver. + Lane number represents the following + - 0 is RX2 lane + - 1 is TX2 lane + - 2 is TX1 lane + - 3 is RX1 lane + The position determines the physical port of the redriver, in the + order A, B, C & D. + oneOf: + - items: + - const: 0 + - const: 1 + - const: 2 + - const: 3 + description: | + This is the lanes default layout + - Port A to RX2 lane + - Port B to TX2 lane + - Port C to TX1 lane + - Port D to RX1 lane + - items: + - const: 3 + - const: 2 + - const: 1 + - const: 0 + description: | + This is the USBRX2/USBTX2 and USBRX1/USBTX1 swapped lanes layout + - Port A to RX1 lane + - Port B to TX1 lane + - Port C to TX2 lane + - Port D to RX2 lane + + 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@32 { + compatible = "onnn,nb7vpq904m"; + reg = <0x32>; + + vcc-supply = <&vreg_l15b_1p8>; + + 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>; + data-lanes = <3 2 1 0>; + }; + }; + port@2 { + reg = <2>; + usb_con_sbu: endpoint { + remote-endpoint = <&typec_dp_aux>; + }; + }; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml index d84281926f10..5c1322972f4e 100644 --- a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml @@ -17,6 +17,7 @@ properties: - qcom,ipq6018-dwc3 - qcom,ipq8064-dwc3 - qcom,ipq8074-dwc3 + - qcom,ipq9574-dwc3 - qcom,msm8953-dwc3 - qcom,msm8994-dwc3 - qcom,msm8996-dwc3 @@ -133,7 +134,6 @@ required: - "#address-cells" - "#size-cells" - ranges - - power-domains - clocks - clock-names - interrupts @@ -177,6 +177,7 @@ allOf: compatible: contains: enum: + - qcom,ipq9574-dwc3 - qcom,msm8953-dwc3 - qcom,msm8996-dwc3 - qcom,msm8998-dwc3 diff --git a/Documentation/devicetree/bindings/usb/qcom,pmic-typec.yaml b/Documentation/devicetree/bindings/usb/qcom,pmic-typec.yaml new file mode 100644 index 000000000000..55df3129a0bc --- /dev/null +++ b/Documentation/devicetree/bindings/usb/qcom,pmic-typec.yaml @@ -0,0 +1,190 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/qcom,pmic-typec.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm PMIC based USB Type-C block + +maintainers: + - Bryan O'Donoghue <bryan.odonoghue@linaro.org> + +description: + Qualcomm PMIC Type-C block + +properties: + compatible: + enum: + - qcom,pm8150b-typec + + connector: + type: object + $ref: /schemas/connector/usb-connector.yaml# + unevaluatedProperties: false + + reg: + description: Type-C port and pdphy SPMI register base offsets + maxItems: 2 + + interrupts: + items: + - description: Type-C CC attach notification, VBUS error, tCCDebounce done + - description: Type-C VCONN powered + - description: Type-C CC state change + - description: Type-C VCONN over-current + - description: Type-C VBUS state change + - description: Type-C Attach/detach notification + - description: Type-C Legacy cable detect + - description: Type-C Try.Src Try.Snk state change + - description: Power Domain Signal TX - HardReset or CableReset signal TX + - description: Power Domain Signal RX - HardReset or CableReset signal RX + - description: Power Domain TX complete + - description: Power Domain RX complete + - description: Power Domain TX fail + - description: Power Domain TX message discard + - description: Power Domain RX message discard + - description: Power Domain Fast Role Swap event + + interrupt-names: + items: + - const: or-rid-detect-change + - const: vpd-detect + - const: cc-state-change + - const: vconn-oc + - const: vbus-change + - const: attach-detach + - const: legacy-cable-detect + - const: try-snk-src-detect + - const: sig-tx + - const: sig-rx + - const: msg-tx + - const: msg-rx + - const: msg-tx-failed + - const: msg-tx-discarded + - const: msg-rx-discarded + - const: fr-swap + + vdd-vbus-supply: + description: VBUS power supply. + + vdd-pdphy-supply: + description: VDD regulator supply to the PDPHY. + + port: + $ref: /schemas/graph.yaml#/properties/port + description: + Contains a port which produces data-role switching messages. + +required: + - compatible + - reg + - interrupts + - interrupt-names + - vdd-vbus-supply + - vdd-pdphy-supply + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/arm-gic.h> + #include <dt-bindings/usb/pd.h> + + pmic { + #address-cells = <1>; + #size-cells = <0>; + + pm8150b_typec: typec@1500 { + compatible = "qcom,pm8150b-typec"; + reg = <0x1500>, + <0x1700>; + + interrupts = <0x2 0x15 0x00 IRQ_TYPE_EDGE_RISING>, + <0x2 0x15 0x01 IRQ_TYPE_EDGE_BOTH>, + <0x2 0x15 0x02 IRQ_TYPE_EDGE_RISING>, + <0x2 0x15 0x03 IRQ_TYPE_EDGE_BOTH>, + <0x2 0x15 0x04 IRQ_TYPE_EDGE_RISING>, + <0x2 0x15 0x05 IRQ_TYPE_EDGE_RISING>, + <0x2 0x15 0x06 IRQ_TYPE_EDGE_BOTH>, + <0x2 0x15 0x07 IRQ_TYPE_EDGE_RISING>, + <0x2 0x17 0x00 IRQ_TYPE_EDGE_RISING>, + <0x2 0x17 0x01 IRQ_TYPE_EDGE_RISING>, + <0x2 0x17 0x02 IRQ_TYPE_EDGE_RISING>, + <0x2 0x17 0x03 IRQ_TYPE_EDGE_RISING>, + <0x2 0x17 0x04 IRQ_TYPE_EDGE_RISING>, + <0x2 0x17 0x05 IRQ_TYPE_EDGE_RISING>, + <0x2 0x17 0x06 IRQ_TYPE_EDGE_RISING>, + <0x2 0x17 0x07 IRQ_TYPE_EDGE_RISING>; + + interrupt-names = "or-rid-detect-change", + "vpd-detect", + "cc-state-change", + "vconn-oc", + "vbus-change", + "attach-detach", + "legacy-cable-detect", + "try-snk-src-detect", + "sig-tx", + "sig-rx", + "msg-tx", + "msg-rx", + "msg-tx-failed", + "msg-tx-discarded", + "msg-rx-discarded", + "fr-swap"; + + vdd-vbus-supply = <&pm8150b_vbus>; + vdd-pdphy-supply = <&vreg_l2a_3p1>; + + connector { + compatible = "usb-c-connector"; + + power-role = "source"; + data-role = "dual"; + self-powered; + + source-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_DUAL_ROLE | + PDO_FIXED_USB_COMM | PDO_FIXED_DATA_SWAP)>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + pmic_typec_mux_out: endpoint { + remote-endpoint = <&usb_phy_typec_mux_in>; + }; + }; + + port@1 { + reg = <1>; + pmic_typec_role_switch_out: endpoint { + remote-endpoint = <&usb_role_switch_in>; + }; + }; + }; + }; + }; + }; + + usb { + dr_mode = "otg"; + usb-role-switch; + port { + usb_role_switch_in: endpoint { + remote-endpoint = <&pmic_typec_role_switch_out>; + }; + }; + }; + + usb-phy { + orientation-switch; + port { + usb_phy_typec_mux_in: endpoint { + remote-endpoint = <&pmic_typec_mux_out>; + }; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml index 4f7625955ccc..a696f23730d3 100644 --- a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml @@ -44,15 +44,15 @@ properties: It's either a single common DWC3 interrupt (dwc_usb3) or individual interrupts for the host, gadget and DRD modes. minItems: 1 - maxItems: 3 + maxItems: 4 interrupt-names: minItems: 1 - maxItems: 3 + maxItems: 4 oneOf: - const: dwc_usb3 - items: - enum: [host, peripheral, otg] + enum: [host, peripheral, otg, wakeup] clocks: description: diff --git a/Documentation/devicetree/bindings/usb/starfive,jh7110-usb.yaml b/Documentation/devicetree/bindings/usb/starfive,jh7110-usb.yaml new file mode 100644 index 000000000000..24aa9c10d6ab --- /dev/null +++ b/Documentation/devicetree/bindings/usb/starfive,jh7110-usb.yaml @@ -0,0 +1,115 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/starfive,jh7110-usb.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: StarFive JH7110 wrapper module for the Cadence USBSS-DRD controller + +maintainers: + - Minda Chen <minda.chen@starfivetech.com> + +properties: + compatible: + const: starfive,jh7110-usb + + ranges: true + + starfive,stg-syscon: + $ref: /schemas/types.yaml#/definitions/phandle-array + items: + - items: + - description: phandle to System Register Controller stg_syscon node. + - description: dr mode register offset of STG_SYSCONSAIF__SYSCFG register for USB. + description: + The phandle to System Register Controller syscon node and the offset + of STG_SYSCONSAIF__SYSCFG register for USB. + + dr_mode: + enum: [host, otg, peripheral] + + "#address-cells": + enum: [1, 2] + + "#size-cells": + enum: [1, 2] + + clocks: + items: + - description: link power management clock + - description: standby clock + - description: APB clock + - description: AXI clock + - description: UTMI APB clock + + clock-names: + items: + - const: lpm + - const: stb + - const: apb + - const: axi + - const: utmi_apb + + resets: + items: + - description: Power up reset + - description: APB clock reset + - description: AXI clock reset + - description: UTMI APB clock reset + + reset-names: + items: + - const: pwrup + - const: apb + - const: axi + - const: utmi_apb + +patternProperties: + "^usb@[0-9a-f]+$": + $ref: cdns,usb3.yaml# + description: Required child node + +required: + - compatible + - ranges + - starfive,stg-syscon + - '#address-cells' + - '#size-cells' + - dr_mode + - clocks + - resets + +additionalProperties: false + +examples: + - | + usb@10100000 { + compatible = "starfive,jh7110-usb"; + ranges = <0x0 0x10100000 0x100000>; + #address-cells = <1>; + #size-cells = <1>; + starfive,stg-syscon = <&stg_syscon 0x4>; + clocks = <&syscrg 4>, + <&stgcrg 5>, + <&stgcrg 1>, + <&stgcrg 3>, + <&stgcrg 2>; + clock-names = "lpm", "stb", "apb", "axi", "utmi_apb"; + resets = <&stgcrg 10>, + <&stgcrg 8>, + <&stgcrg 7>, + <&stgcrg 9>; + reset-names = "pwrup", "apb", "axi", "utmi_apb"; + dr_mode = "host"; + + usb@0 { + compatible = "cdns,usb3"; + reg = <0x0 0x10000>, + <0x10000 0x10000>, + <0x20000 0x10000>; + reg-names = "otg", "xhci", "dev"; + interrupts = <100>, <108>, <110>; + interrupt-names = "host", "peripheral", "otg"; + maximum-speed = "super-speed"; + }; + }; diff --git a/Documentation/devicetree/bindings/usb/usb251xb.yaml b/Documentation/devicetree/bindings/usb/usb251xb.yaml index 4d1530816817..ac5b99710332 100644 --- a/Documentation/devicetree/bindings/usb/usb251xb.yaml +++ b/Documentation/devicetree/bindings/usb/usb251xb.yaml @@ -231,7 +231,7 @@ properties: power-on sequence to a port until the port has adequate power. swap-dx-lanes: - $ref: /schemas/types.yaml#/definitions/uint8-array + $ref: /schemas/types.yaml#/definitions/uint32-array description: | Specifies the ports which will swap the differential-pair (D+/D-), default is not-swapped. diff --git a/MAINTAINERS b/MAINTAINERS index 018c5edd230f..33dd25d4149f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4540,6 +4540,12 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/usb.git F: drivers/usb/cdns3/ X: drivers/usb/cdns3/cdns3* +CADENCE USBHS DRIVER +M: Pawel Laszczak <pawell@cadence.com> +L: linux-usb@vger.kernel.org +S: Maintained +F: drivers/usb/gadget/udc/cdns2 + CADET FM/AM RADIO RECEIVER DRIVER M: Hans Verkuil <hverkuil@xs4all.nl> L: linux-media@vger.kernel.org @@ -17694,6 +17700,14 @@ S: Maintained F: Documentation/devicetree/bindings/thermal/qcom-tsens.yaml F: drivers/thermal/qcom/ +QUALCOMM TYPEC PORT MANAGER DRIVER +M: Bryan O'Donoghue <bryan.odonoghue@linaro.org> +L: linux-arm-msm@vger.kernel.org +L: linux-usb@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/usb/qcom,pmic-*.yaml +F: drivers/usb/typec/tcpm/qcom/ + QUALCOMM VENUS VIDEO ACCELERATOR DRIVER M: Stanimir Varbanov <stanimir.k.varbanov@gmail.com> M: Vikash Garodia <quic_vgarodia@quicinc.com> @@ -20329,6 +20343,12 @@ F: Documentation/devicetree/bindings/reset/starfive,jh7100-reset.yaml F: drivers/reset/starfive/reset-starfive-jh71* F: include/dt-bindings/reset/starfive?jh71*.h +STARFIVE JH71X0 USB DRIVERS +M: Minda Chen <minda.chen@starfivetech.com> +S: Maintained +F: Documentation/devicetree/bindings/usb/starfive,jh7110-usb.yaml +F: drivers/usb/cdns3/cdns3-starfive.c + STARFIVE JH71XX PMU CONTROLLER DRIVER M: Walker Chen <walker.chen@starfivetech.com> S: Supported @@ -22126,6 +22146,7 @@ F: drivers/usb/ F: include/dt-bindings/usb/ F: include/linux/usb.h F: include/linux/usb/ +F: include/uapi/linux/usb/ USB TYPEC BUS FOR ALTERNATE MODES M: Heikki Krogerus <heikki.krogerus@linux.intel.com> diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index a673c3342470..25f9767c28e8 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -77,7 +77,7 @@ static int cros_typec_get_switch_handles(struct cros_typec_port *port, { int ret = 0; - port->mux = fwnode_typec_mux_get(fwnode, NULL); + port->mux = fwnode_typec_mux_get(fwnode); if (IS_ERR(port->mux)) { ret = PTR_ERR(port->mux); dev_dbg(dev, "Mux handle not found: %d.\n", ret); diff --git a/drivers/soc/qcom/pmic_glink_altmode.c b/drivers/soc/qcom/pmic_glink_altmode.c index 4d7895bdeaf2..df48fbea4b68 100644 --- a/drivers/soc/qcom/pmic_glink_altmode.c +++ b/drivers/soc/qcom/pmic_glink_altmode.c @@ -369,7 +369,6 @@ static int pmic_glink_altmode_probe(struct auxiliary_device *adev, { struct pmic_glink_altmode_port *alt_port; struct pmic_glink_altmode *altmode; - struct typec_altmode_desc mux_desc = {}; const struct of_device_id *match; struct fwnode_handle *fwnode; struct device *dev = &adev->dev; @@ -427,9 +426,7 @@ static int pmic_glink_altmode_probe(struct auxiliary_device *adev, alt_port->dp_alt.mode = USB_TYPEC_DP_MODE; alt_port->dp_alt.active = 1; - mux_desc.svid = USB_TYPEC_DP_SID; - mux_desc.mode = USB_TYPEC_DP_MODE; - alt_port->typec_mux = fwnode_typec_mux_get(fwnode, &mux_desc); + alt_port->typec_mux = fwnode_typec_mux_get(fwnode); if (IS_ERR(alt_port->typec_mux)) return dev_err_probe(dev, PTR_ERR(alt_port->typec_mux), "failed to acquire mode-switch for port: %d\n", diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index 78fd365893c1..c8b3d7b78098 100644 --- a/drivers/thunderbolt/Makefile +++ b/drivers/thunderbolt/Makefile @@ -2,7 +2,7 @@ obj-${CONFIG_USB4} := thunderbolt.o thunderbolt-objs := nhi.o nhi_ops.o ctl.o tb.o switch.o cap.o path.o tunnel.o eeprom.o thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o lc.o tmu.o usb4.o -thunderbolt-objs += usb4_port.o nvm.o retimer.o quirks.o +thunderbolt-objs += usb4_port.o nvm.o retimer.o quirks.o clx.o thunderbolt-${CONFIG_ACPI} += acpi.o thunderbolt-$(CONFIG_DEBUG_FS) += debugfs.o diff --git a/drivers/thunderbolt/acpi.c b/drivers/thunderbolt/acpi.c index 3514bf65b7a4..38fefd0e5268 100644 --- a/drivers/thunderbolt/acpi.c +++ b/drivers/thunderbolt/acpi.c @@ -296,16 +296,15 @@ static bool tb_acpi_bus_match(struct device *dev) static struct acpi_device *tb_acpi_switch_find_companion(struct tb_switch *sw) { + struct tb_switch *parent_sw = tb_switch_parent(sw); struct acpi_device *adev = NULL; - struct tb_switch *parent_sw; /* * Device routers exists under the downstream facing USB4 port * of the parent router. Their _ADR is always 0. */ - parent_sw = tb_switch_parent(sw); if (parent_sw) { - struct tb_port *port = tb_port_at(tb_route(sw), parent_sw); + struct tb_port *port = tb_switch_downstream_port(sw); struct acpi_device *port_adev; port_adev = acpi_find_child_by_adr(ACPI_COMPANION(&parent_sw->dev), diff --git a/drivers/thunderbolt/clx.c b/drivers/thunderbolt/clx.c new file mode 100644 index 000000000000..13d217ae98e6 --- /dev/null +++ b/drivers/thunderbolt/clx.c @@ -0,0 +1,423 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * CLx support + * + * Copyright (C) 2020 - 2023, Intel Corporation + * Authors: Gil Fine <gil.fine@intel.com> + * Mika Westerberg <mika.westerberg@linux.intel.com> + */ + +#include <linux/module.h> + +#include "tb.h" + +static bool clx_enabled = true; +module_param_named(clx, clx_enabled, bool, 0444); +MODULE_PARM_DESC(clx, "allow low power states on the high-speed lanes (default: true)"); + +static const char *clx_name(unsigned int clx) +{ + switch (clx) { + case TB_CL0S | TB_CL1 | TB_CL2: + return "CL0s/CL1/CL2"; + case TB_CL1 | TB_CL2: + return "CL1/CL2"; + case TB_CL0S | TB_CL2: + return "CL0s/CL2"; + case TB_CL0S | TB_CL1: + return "CL0s/CL1"; + case TB_CL0S: + return "CL0s"; + case 0: + return "disabled"; + default: + return "unknown"; + } +} + +static int tb_port_pm_secondary_set(struct tb_port *port, bool secondary) +{ + u32 phy; + int ret; + + ret = tb_port_read(port, &phy, TB_CFG_PORT, + port->cap_phy + LANE_ADP_CS_1, 1); + if (ret) + return ret; + + if (secondary) + phy |= LANE_ADP_CS_1_PMS; + else + phy &= ~LANE_ADP_CS_1_PMS; + + return tb_port_write(port, &phy, TB_CFG_PORT, + port->cap_phy + LANE_ADP_CS_1, 1); +} + +static int tb_port_pm_secondary_enable(struct tb_port *port) +{ + return tb_port_pm_secondary_set(port, true); +} + +static int tb_port_pm_secondary_disable(struct tb_port *port) +{ + return tb_port_pm_secondary_set(port, false); +} + +/* Called for USB4 or Titan Ridge routers only */ +static bool tb_port_clx_supported(struct tb_port *port, unsigned int clx) +{ + u32 val, mask = 0; + bool ret; + + /* Don't enable CLx in case of two single-lane links */ + if (!port->bonded && port->dual_link_port) + return false; + + /* Don't enable CLx in case of inter-domain link */ + if (port->xdomain) + return false; + + if (tb_switch_is_usb4(port->sw)) { + if (!usb4_port_clx_supported(port)) + return false; + } else if (!tb_lc_is_clx_supported(port)) { + return false; + } + + if (clx & TB_CL0S) + mask |= LANE_ADP_CS_0_CL0S_SUPPORT; + if (clx & TB_CL1) + mask |= LANE_ADP_CS_0_CL1_SUPPORT; + if (clx & TB_CL2) + mask |= LANE_ADP_CS_0_CL2_SUPPORT; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_phy + LANE_ADP_CS_0, 1); + if (ret) + return false; + + return !!(val & mask); +} + +static int tb_port_clx_set(struct tb_port *port, unsigned int clx, bool enable) +{ + u32 phy, mask = 0; + int ret; + + if (clx & TB_CL0S) + mask |= LANE_ADP_CS_1_CL0S_ENABLE; + if (clx & TB_CL1) + mask |= LANE_ADP_CS_1_CL1_ENABLE; + if (clx & TB_CL2) + mask |= LANE_ADP_CS_1_CL2_ENABLE; + + if (!mask) + return -EOPNOTSUPP; + + ret = tb_port_read(port, &phy, TB_CFG_PORT, + port->cap_phy + LANE_ADP_CS_1, 1); + if (ret) + return ret; + + if (enable) + phy |= mask; + else + phy &= ~mask; + + return tb_port_write(port, &phy, TB_CFG_PORT, + port->cap_phy + LANE_ADP_CS_1, 1); +} + +static int tb_port_clx_disable(struct tb_port *port, unsigned int clx) +{ + return tb_port_clx_set(port, clx, false); +} + +static int tb_port_clx_enable(struct tb_port *port, unsigned int clx) +{ + return tb_port_clx_set(port, clx, true); +} + +static int tb_port_clx(struct tb_port *port) +{ + u32 val; + int ret; + + if (!tb_port_clx_supported(port, TB_CL0S | TB_CL1 | TB_CL2)) + return 0; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_phy + LANE_ADP_CS_1, 1); + if (ret) + return ret; + + if (val & LANE_ADP_CS_1_CL0S_ENABLE) + ret |= TB_CL0S; + if (val & LANE_ADP_CS_1_CL1_ENABLE) + ret |= TB_CL1; + if (val & LANE_ADP_CS_1_CL2_ENABLE) + ret |= TB_CL2; + + return ret; +} + +/** + * tb_port_clx_is_enabled() - Is given CL state enabled + * @port: USB4 port to check + * @clx: Mask of CL states to check + * + * Returns true if any of the given CL states is enabled for @port. + */ +bool tb_port_clx_is_enabled(struct tb_port *port, unsigned int clx) +{ + return !!(tb_port_clx(port) & clx); +} + +/** + * tb_switch_clx_init() - Initialize router CL states + * @sw: Router + * + * Can be called for any router. Initializes the current CL state by + * reading it from the hardware. + * + * Returns %0 in case of success and negative errno in case of failure. + */ +int tb_switch_clx_init(struct tb_switch *sw) +{ + struct tb_port *up, *down; + unsigned int clx, tmp; + + if (tb_switch_is_icm(sw)) + return 0; + + if (!tb_route(sw)) + return 0; + + if (!tb_switch_clx_is_supported(sw)) + return 0; + + up = tb_upstream_port(sw); + down = tb_switch_downstream_port(sw); + + clx = tb_port_clx(up); + tmp = tb_port_clx(down); + if (clx != tmp) + tb_sw_warn(sw, "CLx: inconsistent configuration %#x != %#x\n", + clx, tmp); + + tb_sw_dbg(sw, "CLx: current mode: %s\n", clx_name(clx)); + + sw->clx = clx; + return 0; +} + +static int tb_switch_pm_secondary_resolve(struct tb_switch *sw) +{ + struct tb_port *up, *down; + int ret; + + if (!tb_route(sw)) + return 0; + + up = tb_upstream_port(sw); + down = tb_switch_downstream_port(sw); + ret = tb_port_pm_secondary_enable(up); + if (ret) + return ret; + + return tb_port_pm_secondary_disable(down); +} + +static int tb_switch_mask_clx_objections(struct tb_switch *sw) +{ + int up_port = sw->config.upstream_port_number; + u32 offset, val[2], mask_obj, unmask_obj; + int ret, i; + + /* Only Titan Ridge of pre-USB4 devices support CLx states */ + if (!tb_switch_is_titan_ridge(sw)) + return 0; + + if (!tb_route(sw)) + return 0; + + /* + * In Titan Ridge there are only 2 dual-lane Thunderbolt ports: + * Port A consists of lane adapters 1,2 and + * Port B consists of lane adapters 3,4 + * If upstream port is A, (lanes are 1,2), we mask objections from + * port B (lanes 3,4) and unmask objections from Port A and vice-versa. + */ + if (up_port == 1) { + mask_obj = TB_LOW_PWR_C0_PORT_B_MASK; + unmask_obj = TB_LOW_PWR_C1_PORT_A_MASK; + offset = TB_LOW_PWR_C1_CL1; + } else { + mask_obj = TB_LOW_PWR_C1_PORT_A_MASK; + unmask_obj = TB_LOW_PWR_C0_PORT_B_MASK; + offset = TB_LOW_PWR_C3_CL1; + } + + ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, + sw->cap_lp + offset, ARRAY_SIZE(val)); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(val); i++) { + val[i] |= mask_obj; + val[i] &= ~unmask_obj; + } + + return tb_sw_write(sw, &val, TB_CFG_SWITCH, + 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 */ + if (clx & TB_CL1) + return (clx & TB_CL0S) == TB_CL0S; + return true; +} + +/** + * tb_switch_clx_enable() - Enable CLx on upstream port of specified router + * @sw: Router to enable CLx for + * @clx: The CLx state to enable + * + * CLx is enabled only if both sides of the link support CLx, and if both sides + * of the link are not configured as two single lane links and only if the link + * is not inter-domain link. The complete set of conditions is described in CM + * Guide 1.0 section 8.1. + * + * Returns %0 on success or an error code on failure. + */ +int tb_switch_clx_enable(struct tb_switch *sw, unsigned int clx) +{ + bool up_clx_support, down_clx_support; + struct tb_switch *parent_sw; + struct tb_port *up, *down; + int ret; + + if (!clx || sw->clx == clx) + return 0; + + if (!validate_mask(clx)) + return -EINVAL; + + parent_sw = tb_switch_parent(sw); + if (!parent_sw) + return 0; + + if (!tb_switch_clx_is_supported(parent_sw) || + !tb_switch_clx_is_supported(sw)) + return 0; + + /* Only support CL2 for v2 routers */ + if ((clx & TB_CL2) && + (usb4_switch_version(parent_sw) < 2 || + usb4_switch_version(sw) < 2)) + return -EOPNOTSUPP; + + ret = tb_switch_pm_secondary_resolve(sw); + if (ret) + return ret; + + up = tb_upstream_port(sw); + down = tb_switch_downstream_port(sw); + + up_clx_support = tb_port_clx_supported(up, clx); + down_clx_support = tb_port_clx_supported(down, clx); + + tb_port_dbg(up, "CLx: %s %ssupported\n", clx_name(clx), + up_clx_support ? "" : "not "); + tb_port_dbg(down, "CLx: %s %ssupported\n", clx_name(clx), + down_clx_support ? "" : "not "); + + if (!up_clx_support || !down_clx_support) + return -EOPNOTSUPP; + + ret = tb_port_clx_enable(up, clx); + if (ret) + return ret; + + ret = tb_port_clx_enable(down, clx); + if (ret) { + tb_port_clx_disable(up, clx); + return ret; + } + + ret = tb_switch_mask_clx_objections(sw); + if (ret) { + tb_port_clx_disable(up, clx); + tb_port_clx_disable(down, clx); + return ret; + } + + sw->clx |= clx; + + tb_sw_dbg(sw, "CLx: %s enabled\n", clx_name(clx)); + return 0; +} + +/** + * tb_switch_clx_disable() - Disable CLx on upstream port of specified router + * @sw: Router to disable CLx for + * + * Disables all CL states of the given router. Can be called on any + * router and if the states were not enabled already does nothing. + * + * Returns the CL states that were disabled or negative errno in case of + * failure. + */ +int tb_switch_clx_disable(struct tb_switch *sw) +{ + unsigned int clx = sw->clx; + struct tb_port *up, *down; + int ret; + + if (!tb_switch_clx_is_supported(sw)) + return 0; + + if (!clx) + return 0; + + up = tb_upstream_port(sw); + down = tb_switch_downstream_port(sw); + + ret = tb_port_clx_disable(up, clx); + if (ret) + return ret; + + ret = tb_port_clx_disable(down, clx); + if (ret) + return ret; + + sw->clx = 0; + + tb_sw_dbg(sw, "CLx: %s disabled\n", clx_name(clx)); + return clx; +} diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index 3a213322ec7a..d997a4c545f7 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -409,6 +409,13 @@ static int tb_async_error(const struct ctl_pkg *pkg) case TB_CFG_ERROR_HEC_ERROR_DETECTED: case TB_CFG_ERROR_FLOW_CONTROL_ERROR: case TB_CFG_ERROR_DP_BW: + case TB_CFG_ERROR_ROP_CMPLT: + case TB_CFG_ERROR_POP_CMPLT: + case TB_CFG_ERROR_PCIE_WAKE: + case TB_CFG_ERROR_DP_CON_CHANGE: + case TB_CFG_ERROR_DPTX_DISCOVERY: + case TB_CFG_ERROR_LINK_RECOVERY: + case TB_CFG_ERROR_ASYM_LINK: return true; default: @@ -758,6 +765,27 @@ int tb_cfg_ack_notification(struct tb_ctl *ctl, u64 route, case TB_CFG_ERROR_DP_BW: name = "DP_BW"; break; + case TB_CFG_ERROR_ROP_CMPLT: + name = "router operation completion"; + break; + case TB_CFG_ERROR_POP_CMPLT: + name = "port operation completion"; + break; + case TB_CFG_ERROR_PCIE_WAKE: + name = "PCIe wake"; + break; + case TB_CFG_ERROR_DP_CON_CHANGE: + name = "DP connector change"; + break; + case TB_CFG_ERROR_DPTX_DISCOVERY: + name = "DPTX discovery"; + break; + case TB_CFG_ERROR_LINK_RECOVERY: + name = "link recovery"; + break; + case TB_CFG_ERROR_ASYM_LINK: + name = "asymmetric link"; + break; default: name = "unknown"; break; diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c index f92ad71ef983..c9ddd49138d8 100644 --- a/drivers/thunderbolt/debugfs.c +++ b/drivers/thunderbolt/debugfs.c @@ -14,12 +14,15 @@ #include "tb.h" #include "sb_regs.h" -#define PORT_CAP_PCIE_LEN 1 +#define PORT_CAP_V1_PCIE_LEN 1 +#define PORT_CAP_V2_PCIE_LEN 2 #define PORT_CAP_POWER_LEN 2 #define PORT_CAP_LANE_LEN 3 #define PORT_CAP_USB3_LEN 5 -#define PORT_CAP_DP_LEN 8 -#define PORT_CAP_TMU_LEN 8 +#define PORT_CAP_DP_V1_LEN 9 +#define PORT_CAP_DP_V2_LEN 14 +#define PORT_CAP_TMU_V1_LEN 8 +#define PORT_CAP_TMU_V2_LEN 10 #define PORT_CAP_BASIC_LEN 9 #define PORT_CAP_USB4_LEN 20 @@ -553,8 +556,9 @@ static int margining_run_write(void *data, u64 val) struct usb4_port *usb4 = port->usb4; struct tb_switch *sw = port->sw; struct tb_margining *margining; + struct tb_switch *down_sw; struct tb *tb = sw->tb; - int ret; + int ret, clx; if (val != 1) return -EINVAL; @@ -566,15 +570,24 @@ static int margining_run_write(void *data, u64 val) goto out_rpm_put; } - /* - * CL states may interfere with lane margining so inform the user know - * and bail out. - */ - if (tb_port_is_clx_enabled(port, TB_CL1 | TB_CL2)) { - tb_port_warn(port, - "CL states are enabled, Disable them with clx=0 and re-connect\n"); - ret = -EINVAL; - goto out_unlock; + if (tb_is_upstream_port(port)) + down_sw = sw; + else if (port->remote) + down_sw = port->remote->sw; + else + down_sw = NULL; + + if (down_sw) { + /* + * CL states may interfere with lane margining so + * disable them temporarily now. + */ + ret = tb_switch_clx_disable(down_sw); + if (ret < 0) { + tb_sw_warn(down_sw, "failed to disable CL states\n"); + goto out_unlock; + } + clx = ret; } margining = usb4->margining; @@ -586,7 +599,7 @@ static int margining_run_write(void *data, u64 val) margining->right_high, USB4_MARGIN_SW_COUNTER_CLEAR); if (ret) - goto out_unlock; + goto out_clx; ret = usb4_port_sw_margin_errors(port, &margining->results[0]); } else { @@ -600,6 +613,9 @@ static int margining_run_write(void *data, u64 val) margining->right_high, margining->results); } +out_clx: + if (down_sw) + tb_switch_clx_enable(down_sw, clx); out_unlock: mutex_unlock(&tb->lock); out_rpm_put: @@ -1148,7 +1164,10 @@ static void port_cap_show(struct tb_port *port, struct seq_file *s, break; case TB_PORT_CAP_TIME1: - length = PORT_CAP_TMU_LEN; + if (usb4_switch_version(port->sw) < 2) + length = PORT_CAP_TMU_V1_LEN; + else + length = PORT_CAP_TMU_V2_LEN; break; case TB_PORT_CAP_POWER: @@ -1157,12 +1176,17 @@ static void port_cap_show(struct tb_port *port, struct seq_file *s, case TB_PORT_CAP_ADAP: if (tb_port_is_pcie_down(port) || tb_port_is_pcie_up(port)) { - length = PORT_CAP_PCIE_LEN; - } else if (tb_port_is_dpin(port) || tb_port_is_dpout(port)) { - if (usb4_dp_port_bw_mode_supported(port)) - length = PORT_CAP_DP_LEN + 1; + if (usb4_switch_version(port->sw) < 2) + length = PORT_CAP_V1_PCIE_LEN; + else + length = PORT_CAP_V2_PCIE_LEN; + } else if (tb_port_is_dpin(port)) { + if (usb4_switch_version(port->sw) < 2) + length = PORT_CAP_DP_V1_LEN; else - length = PORT_CAP_DP_LEN; + length = PORT_CAP_DP_V2_LEN; + } else if (tb_port_is_dpout(port)) { + length = PORT_CAP_DP_V1_LEN; } else if (tb_port_is_usb3_down(port) || tb_port_is_usb3_up(port)) { length = PORT_CAP_USB3_LEN; diff --git a/drivers/thunderbolt/dma_test.c b/drivers/thunderbolt/dma_test.c index 14bb6dec6c4b..39476fc48801 100644 --- a/drivers/thunderbolt/dma_test.c +++ b/drivers/thunderbolt/dma_test.c @@ -412,6 +412,7 @@ static void speed_get(const struct dma_test *dt, u64 *val) static int speed_validate(u64 val) { switch (val) { + case 40: case 20: case 10: case 0: @@ -489,9 +490,12 @@ 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 && - dt->xd->link_width != dt->link_width) { - dt->error_code = DMA_TEST_WIDTH_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->packets_to_send != dt->packets_sent || dt->packets_to_receive != dt->packets_received || dt->crc_errors || dt->buffer_overflow_errors) { @@ -756,5 +760,5 @@ module_exit(dma_test_exit); MODULE_AUTHOR("Isaac Hazan <isaac.hazan@intel.com>"); MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>"); -MODULE_DESCRIPTION("DMA traffic test driver"); +MODULE_DESCRIPTION("Thunderbolt/USB4 DMA traffic test driver"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index 0f6099c18a94..eb241b270f79 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -605,9 +605,8 @@ static int usb4_drom_parse(struct tb_switch *sw) crc = tb_crc32(sw->drom + TB_DROM_DATA_START, header->data_len); if (crc != header->data_crc32) { tb_sw_warn(sw, - "DROM data CRC32 mismatch (expected: %#x, got: %#x), aborting\n", + "DROM data CRC32 mismatch (expected: %#x, got: %#x), continuing\n", header->data_crc32, crc); - return -EINVAL; } return tb_drom_parse_entries(sw, USB4_DROM_HEADER_SIZE); diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index 86521ebb2579..dbdcad8d73bf 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -644,13 +644,14 @@ static int add_switch(struct tb_switch *parent_sw, struct tb_switch *sw) return ret; } -static void update_switch(struct tb_switch *parent_sw, struct tb_switch *sw, - u64 route, u8 connection_id, u8 connection_key, - u8 link, u8 depth, bool boot) +static void update_switch(struct tb_switch *sw, u64 route, u8 connection_id, + u8 connection_key, u8 link, u8 depth, bool boot) { + struct tb_switch *parent_sw = tb_switch_parent(sw); + /* Disconnect from parent */ - tb_port_at(tb_route(sw), parent_sw)->remote = NULL; - /* Re-connect via updated port*/ + tb_switch_downstream_port(sw)->remote = NULL; + /* Re-connect via updated port */ tb_port_at(route, parent_sw)->remote = tb_upstream_port(sw); /* Update with the new addressing information */ @@ -671,10 +672,7 @@ static void update_switch(struct tb_switch *parent_sw, struct tb_switch *sw, static void remove_switch(struct tb_switch *sw) { - struct tb_switch *parent_sw; - - parent_sw = tb_to_switch(sw->dev.parent); - tb_port_at(tb_route(sw), parent_sw)->remote = NULL; + tb_switch_downstream_port(sw)->remote = NULL; tb_switch_remove(sw); } @@ -755,7 +753,6 @@ icm_fr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr) if (sw) { u8 phy_port, sw_phy_port; - parent_sw = tb_to_switch(sw->dev.parent); sw_phy_port = tb_phy_port_from_link(sw->link); phy_port = tb_phy_port_from_link(link); @@ -785,7 +782,7 @@ icm_fr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr) route = tb_route(sw); } - update_switch(parent_sw, sw, route, pkg->connection_id, + update_switch(sw, route, pkg->connection_id, pkg->connection_key, link, depth, boot); tb_switch_put(sw); return; @@ -853,7 +850,8 @@ icm_fr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr) sw->security_level = security_level; sw->boot = boot; sw->link_speed = speed_gen3 ? 20 : 10; - sw->link_width = dual_lane ? 2 : 1; + sw->link_width = dual_lane ? TB_LINK_WIDTH_DUAL : + TB_LINK_WIDTH_SINGLE; sw->rpm = intel_vss_is_rtd3(pkg->ep_name, sizeof(pkg->ep_name)); if (add_switch(parent_sw, sw)) @@ -1236,9 +1234,8 @@ __icm_tr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr, if (sw) { /* Update the switch if it is still in the same place */ if (tb_route(sw) == route && !!sw->authorized == authorized) { - parent_sw = tb_to_switch(sw->dev.parent); - update_switch(parent_sw, sw, route, pkg->connection_id, - 0, 0, 0, boot); + update_switch(sw, route, pkg->connection_id, 0, 0, 0, + boot); tb_switch_put(sw); return; } @@ -1276,7 +1273,8 @@ __icm_tr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr, sw->security_level = security_level; sw->boot = boot; sw->link_speed = speed_gen3 ? 20 : 10; - sw->link_width = dual_lane ? 2 : 1; + sw->link_width = dual_lane ? TB_LINK_WIDTH_DUAL : + TB_LINK_WIDTH_SINGLE; sw->rpm = force_rtd3; if (!sw->rpm) sw->rpm = intel_vss_is_rtd3(pkg->ep_name, diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index e58beac44295..4b7bec74e89f 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -46,6 +46,10 @@ #define QUIRK_AUTO_CLEAR_INT BIT(0) #define QUIRK_E2E BIT(1) +static bool host_reset = true; +module_param(host_reset, bool, 0444); +MODULE_PARM_DESC(host_reset, "reset USBv2 host router (default: true)"); + static int ring_interrupt_index(const struct tb_ring *ring) { int bit = ring->hop; @@ -1217,6 +1221,37 @@ static void nhi_check_iommu(struct tb_nhi *nhi) str_enabled_disabled(port_ok)); } +static void nhi_reset(struct tb_nhi *nhi) +{ + ktime_t timeout; + u32 val; + + val = ioread32(nhi->iobase + REG_CAPS); + /* Reset only v2 and later routers */ + if (FIELD_GET(REG_CAPS_VERSION_MASK, val) < REG_CAPS_VERSION_2) + return; + + if (!host_reset) { + dev_dbg(&nhi->pdev->dev, "skipping host router reset\n"); + return; + } + + iowrite32(REG_RESET_HRR, nhi->iobase + REG_RESET); + msleep(100); + + timeout = ktime_add_ms(ktime_get(), 500); + do { + val = ioread32(nhi->iobase + REG_RESET); + if (!(val & REG_RESET_HRR)) { + dev_warn(&nhi->pdev->dev, "host router reset successful\n"); + return; + } + usleep_range(10, 20); + } while (ktime_before(ktime_get(), timeout)); + + dev_warn(&nhi->pdev->dev, "timeout resetting host router\n"); +} + static int nhi_init_msi(struct tb_nhi *nhi) { struct pci_dev *pdev = nhi->pdev; @@ -1317,7 +1352,7 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) nhi->ops = (const struct tb_nhi_ops *)id->driver_data; /* cannot fail - table is allocated in pcim_iomap_regions */ nhi->iobase = pcim_iomap_table(pdev)[0]; - nhi->hop_count = ioread32(nhi->iobase + REG_HOP_COUNT) & 0x3ff; + nhi->hop_count = ioread32(nhi->iobase + REG_CAPS) & 0x3ff; dev_dbg(dev, "total paths: %d\n", nhi->hop_count); nhi->tx_rings = devm_kcalloc(&pdev->dev, nhi->hop_count, @@ -1330,6 +1365,8 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) nhi_check_quirks(nhi); nhi_check_iommu(nhi); + nhi_reset(nhi); + res = nhi_init_msi(nhi); if (res) return dev_err_probe(dev, res, "cannot enable MSI, aborting\n"); @@ -1480,6 +1517,8 @@ static struct pci_device_id nhi_ids[] = { .driver_data = (kernel_ulong_t)&icl_nhi_ops }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_P_NHI1), .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_80G_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_40G_NHI) }, /* Any USB4 compliant host */ { PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_USB4, ~0) }, @@ -1488,6 +1527,7 @@ static struct pci_device_id nhi_ids[] = { }; MODULE_DEVICE_TABLE(pci, nhi_ids); +MODULE_DESCRIPTION("Thunderbolt/USB4 core driver"); MODULE_LICENSE("GPL"); static struct pci_driver nhi_driver = { diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index b0718020c6f5..0f029ce75882 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -75,6 +75,10 @@ extern const struct tb_nhi_ops icl_nhi_ops; #define PCI_DEVICE_ID_INTEL_TITAN_RIDGE_DD_BRIDGE 0x15ef #define PCI_DEVICE_ID_INTEL_ADL_NHI0 0x463e #define PCI_DEVICE_ID_INTEL_ADL_NHI1 0x466d +#define PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_80G_NHI 0x5781 +#define PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_40G_NHI 0x5784 +#define PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_80G_BRIDGE 0x5786 +#define PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_40G_BRIDGE 0x57a4 #define PCI_DEVICE_ID_INTEL_MTL_M_NHI0 0x7eb2 #define PCI_DEVICE_ID_INTEL_MTL_P_NHI0 0x7ec2 #define PCI_DEVICE_ID_INTEL_MTL_P_NHI1 0x7ec3 diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h index 6ba295815477..297a3e440648 100644 --- a/drivers/thunderbolt/nhi_regs.h +++ b/drivers/thunderbolt/nhi_regs.h @@ -37,7 +37,7 @@ struct ring_desc { /* NHI registers in bar 0 */ /* - * 16 bytes per entry, one entry for every hop (REG_HOP_COUNT) + * 16 bytes per entry, one entry for every hop (REG_CAPS) * 00: physical pointer to an array of struct ring_desc * 08: ring tail (set by NHI) * 10: ring head (index of first non posted descriptor) @@ -46,7 +46,7 @@ struct ring_desc { #define REG_TX_RING_BASE 0x00000 /* - * 16 bytes per entry, one entry for every hop (REG_HOP_COUNT) + * 16 bytes per entry, one entry for every hop (REG_CAPS) * 00: physical pointer to an array of struct ring_desc * 08: ring head (index of first not posted descriptor) * 10: ring tail (set by NHI) @@ -56,7 +56,7 @@ struct ring_desc { #define REG_RX_RING_BASE 0x08000 /* - * 32 bytes per entry, one entry for every hop (REG_HOP_COUNT) + * 32 bytes per entry, one entry for every hop (REG_CAPS) * 00: enum_ring_flags * 04: isoch time stamp ?? (write 0) * ..: unknown @@ -64,7 +64,7 @@ struct ring_desc { #define REG_TX_OPTIONS_BASE 0x19800 /* - * 32 bytes per entry, one entry for every hop (REG_HOP_COUNT) + * 32 bytes per entry, one entry for every hop (REG_CAPS) * 00: enum ring_flags * If RING_FLAG_E2E_FLOW_CONTROL is set then bits 13-23 must be set to * the corresponding TX hop id. @@ -77,7 +77,7 @@ struct ring_desc { /* * three bitfields: tx, rx, rx overflow - * Every bitfield contains one bit for every hop (REG_HOP_COUNT). + * Every bitfield contains one bit for every hop (REG_CAPS). * New interrupts are fired only after ALL registers have been * read (even those containing only disabled rings). */ @@ -87,7 +87,7 @@ struct ring_desc { /* * two bitfields: rx, tx - * Both bitfields contains one bit for every hop (REG_HOP_COUNT). To + * Both bitfields contains one bit for every hop (REG_CAPS). To * enable/disable interrupts set/clear the corresponding bits. */ #define REG_RING_INTERRUPT_BASE 0x38200 @@ -104,12 +104,17 @@ struct ring_desc { #define REG_INT_VEC_ALLOC_REGS (32 / REG_INT_VEC_ALLOC_BITS) /* The last 11 bits contain the number of hops supported by the NHI port. */ -#define REG_HOP_COUNT 0x39640 +#define REG_CAPS 0x39640 +#define REG_CAPS_VERSION_MASK GENMASK(23, 16) +#define REG_CAPS_VERSION_2 0x40 #define REG_DMA_MISC 0x39864 #define REG_DMA_MISC_INT_AUTO_CLEAR BIT(2) #define REG_DMA_MISC_DISABLE_AUTO_CLEAR BIT(17) +#define REG_RESET 0x39898 +#define REG_RESET_HRR BIT(0) + #define REG_INMAIL_DATA 0x39900 #define REG_INMAIL_CMD 0x39904 diff --git a/drivers/thunderbolt/nvm.c b/drivers/thunderbolt/nvm.c index 3dd5f81bd629..69fb3b0fa34f 100644 --- a/drivers/thunderbolt/nvm.c +++ b/drivers/thunderbolt/nvm.c @@ -12,6 +12,10 @@ #include "tb.h" +#define NVM_MIN_SIZE SZ_32K +#define NVM_MAX_SIZE SZ_1M +#define NVM_DATA_DWORDS 16 + /* Intel specific NVM offsets */ #define INTEL_NVM_DEVID 0x05 #define INTEL_NVM_VERSION 0x08 diff --git a/drivers/thunderbolt/quirks.c b/drivers/thunderbolt/quirks.c index 1157b8869bcc..488138a28ae1 100644 --- a/drivers/thunderbolt/quirks.c +++ b/drivers/thunderbolt/quirks.c @@ -10,6 +10,7 @@ static void quirk_force_power_link(struct tb_switch *sw) { sw->quirks |= QUIRK_FORCE_POWER_LINK_CONTROLLER; + tb_sw_dbg(sw, "forcing power to link controller\n"); } static void quirk_dp_credit_allocation(struct tb_switch *sw) @@ -74,6 +75,14 @@ static const struct tb_quirk tb_quirks[] = { quirk_usb3_maximum_bandwidth }, { 0x8087, PCI_DEVICE_ID_INTEL_MTL_P_NHI1, 0x0000, 0x0000, quirk_usb3_maximum_bandwidth }, + { 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_80G_NHI, 0x0000, 0x0000, + quirk_usb3_maximum_bandwidth }, + { 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_40G_NHI, 0x0000, 0x0000, + quirk_usb3_maximum_bandwidth }, + { 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_80G_BRIDGE, 0x0000, 0x0000, + quirk_usb3_maximum_bandwidth }, + { 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_40G_BRIDGE, 0x0000, 0x0000, + quirk_usb3_maximum_bandwidth }, /* * CLx is not supported on AMD USB4 Yellow Carp and Pink Sardine platforms. */ @@ -105,6 +114,7 @@ void tb_check_quirks(struct tb_switch *sw) if (q->device && q->device != sw->device) continue; + tb_sw_dbg(sw, "running %ps\n", q->hook); q->hook(sw); } } diff --git a/drivers/thunderbolt/retimer.c b/drivers/thunderbolt/retimer.c index 9cc28197dbc4..47becb363ada 100644 --- a/drivers/thunderbolt/retimer.c +++ b/drivers/thunderbolt/retimer.c @@ -187,10 +187,34 @@ static ssize_t nvm_authenticate_show(struct device *dev, return ret; } +static void tb_retimer_nvm_authenticate_status(struct tb_port *port, u32 *status) +{ + int i; + + tb_port_dbg(port, "reading NVM authentication status of retimers\n"); + + /* + * Before doing anything else, read the authentication status. + * If the retimer has it set, store it for the new retimer + * device instance. + */ + for (i = 1; i <= TB_MAX_RETIMER_INDEX; i++) + usb4_port_retimer_nvm_authenticate_status(port, i, &status[i]); +} + static void tb_retimer_set_inbound_sbtx(struct tb_port *port) { int i; + /* + * When USB4 port is online sideband communications are + * already up. + */ + if (!usb4_port_device_is_offline(port->usb4)) + return; + + tb_port_dbg(port, "enabling sideband transactions\n"); + for (i = 1; i <= TB_MAX_RETIMER_INDEX; i++) usb4_port_retimer_set_inbound_sbtx(port, i); } @@ -199,6 +223,16 @@ static void tb_retimer_unset_inbound_sbtx(struct tb_port *port) { int i; + /* + * When USB4 port is offline we need to keep the sideband + * communications up to make it possible to communicate with + * the connected retimers. + */ + if (usb4_port_device_is_offline(port->usb4)) + return; + + tb_port_dbg(port, "disabling sideband transactions\n"); + for (i = TB_MAX_RETIMER_INDEX; i >= 1; i--) usb4_port_retimer_unset_inbound_sbtx(port, i); } @@ -229,6 +263,13 @@ static ssize_t nvm_authenticate_store(struct device *dev, rt->auth_status = 0; if (val) { + /* + * When NVM authentication starts the retimer is not + * accessible so calling tb_retimer_unset_inbound_sbtx() + * will fail and therefore we do not call it. Exception + * is when the validation fails or we only write the new + * NVM image without authentication. + */ tb_retimer_set_inbound_sbtx(rt->port); if (val == AUTHENTICATE_ONLY) { ret = tb_retimer_nvm_authenticate(rt, true); @@ -249,7 +290,8 @@ static ssize_t nvm_authenticate_store(struct device *dev, } exit_unlock: - tb_retimer_unset_inbound_sbtx(rt->port); + if (ret || val == WRITE_ONLY) + tb_retimer_unset_inbound_sbtx(rt->port); mutex_unlock(&rt->tb->lock); exit_rpm: pm_runtime_mark_last_busy(&rt->dev); @@ -341,12 +383,6 @@ static int tb_retimer_add(struct tb_port *port, u8 index, u32 auth_status) return ret; } - if (vendor != PCI_VENDOR_ID_INTEL && vendor != 0x8087) { - tb_port_info(port, "retimer NVM format of vendor %#x is not supported\n", - vendor); - return -EOPNOTSUPP; - } - /* * Check that it supports NVM operations. If not then don't add * the device at all. @@ -455,18 +491,16 @@ int tb_retimer_scan(struct tb_port *port, bool add) return ret; /* - * Enable sideband channel for each retimer. We can do this - * regardless whether there is device connected or not. + * Immediately after sending enumerate retimers read the + * authentication status of each retimer. */ - tb_retimer_set_inbound_sbtx(port); + tb_retimer_nvm_authenticate_status(port, status); /* - * Before doing anything else, read the authentication status. - * If the retimer has it set, store it for the new retimer - * device instance. + * Enable sideband channel for each retimer. We can do this + * regardless whether there is device connected or not. */ - for (i = 1; i <= TB_MAX_RETIMER_INDEX; i++) - usb4_port_retimer_nvm_authenticate_status(port, i, &status[i]); + tb_retimer_set_inbound_sbtx(port); for (i = 1; i <= TB_MAX_RETIMER_INDEX; i++) { /* diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 51e86b5171c7..7ea63bb31714 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -26,10 +26,6 @@ struct nvm_auth_status { u32 status; }; -static bool clx_enabled = true; -module_param_named(clx, clx_enabled, bool, 0444); -MODULE_PARM_DESC(clx, "allow low power states on the high-speed lanes (default: true)"); - /* * Hold NVM authentication failure status per switch This information * needs to stay around even when the switch gets power cycled so we @@ -727,7 +723,7 @@ static int tb_init_port(struct tb_port *port) * can be read from the path config space. Legacy * devices we use hard-coded value. */ - if (tb_switch_is_usb4(port->sw)) { + if (port->cap_usb4) { struct tb_regs_hop hop; if (!tb_port_read(port, &hop, TB_CFG_HOPS, 0, 2)) @@ -907,15 +903,23 @@ int tb_port_get_link_speed(struct tb_port *port) speed = (val & LANE_ADP_CS_1_CURRENT_SPEED_MASK) >> LANE_ADP_CS_1_CURRENT_SPEED_SHIFT; - return speed == LANE_ADP_CS_1_CURRENT_SPEED_GEN3 ? 20 : 10; + + switch (speed) { + case LANE_ADP_CS_1_CURRENT_SPEED_GEN4: + return 40; + case LANE_ADP_CS_1_CURRENT_SPEED_GEN3: + return 20; + default: + return 10; + } } /** * tb_port_get_link_width() - Get current link width * @port: Port to check (USB4 or CIO) * - * Returns link width. Return values can be 1 (Single-Lane), 2 (Dual-Lane) - * or negative errno in case of failure. + * Returns link width. Return the link width as encoded in &enum + * tb_link_width or negative errno in case of failure. */ int tb_port_get_link_width(struct tb_port *port) { @@ -930,11 +934,13 @@ int tb_port_get_link_width(struct tb_port *port) if (ret) return ret; + /* Matches the values in enum tb_link_width */ return (val & LANE_ADP_CS_1_CURRENT_WIDTH_MASK) >> LANE_ADP_CS_1_CURRENT_WIDTH_SHIFT; } -static bool tb_port_is_width_supported(struct tb_port *port, int width) +static bool tb_port_is_width_supported(struct tb_port *port, + unsigned int width_mask) { u32 phy, widths; int ret; @@ -950,20 +956,25 @@ static bool tb_port_is_width_supported(struct tb_port *port, int width) widths = (phy & LANE_ADP_CS_0_SUPPORTED_WIDTH_MASK) >> LANE_ADP_CS_0_SUPPORTED_WIDTH_SHIFT; - return !!(widths & width); + return widths & width_mask; +} + +static bool is_gen4_link(struct tb_port *port) +{ + return tb_port_get_link_speed(port) > 20; } /** * tb_port_set_link_width() - Set target link width of the lane adapter * @port: Lane adapter - * @width: Target link width (%1 or %2) + * @width: Target link width * * Sets the target link width of the lane adapter to @width. Does not * enable/disable lane bonding. For that call tb_port_set_lane_bonding(). * * Return: %0 in case of success and negative errno in case of error */ -int tb_port_set_link_width(struct tb_port *port, unsigned int width) +int tb_port_set_link_width(struct tb_port *port, enum tb_link_width width) { u32 val; int ret; @@ -978,11 +989,14 @@ int tb_port_set_link_width(struct tb_port *port, unsigned int width) val &= ~LANE_ADP_CS_1_TARGET_WIDTH_MASK; switch (width) { - case 1: + case TB_LINK_WIDTH_SINGLE: + /* Gen 4 link cannot be single */ + if (is_gen4_link(port)) + return -EOPNOTSUPP; val |= LANE_ADP_CS_1_TARGET_WIDTH_SINGLE << LANE_ADP_CS_1_TARGET_WIDTH_SHIFT; break; - case 2: + case TB_LINK_WIDTH_DUAL: val |= LANE_ADP_CS_1_TARGET_WIDTH_DUAL << LANE_ADP_CS_1_TARGET_WIDTH_SHIFT; break; @@ -1004,12 +1018,9 @@ int tb_port_set_link_width(struct tb_port *port, unsigned int width) * cases one should use tb_port_lane_bonding_enable() instead to enable * lane bonding. * - * As a side effect sets @port->bonding accordingly (and does the same - * for lane 1 too). - * * Return: %0 in case of success and negative errno in case of error */ -int tb_port_set_lane_bonding(struct tb_port *port, bool bonding) +static int tb_port_set_lane_bonding(struct tb_port *port, bool bonding) { u32 val; int ret; @@ -1027,19 +1038,8 @@ int tb_port_set_lane_bonding(struct tb_port *port, bool bonding) else val &= ~LANE_ADP_CS_1_LB; - ret = tb_port_write(port, &val, TB_CFG_PORT, - port->cap_phy + LANE_ADP_CS_1, 1); - if (ret) - return ret; - - /* - * When lane 0 bonding is set it will affect lane 1 too so - * update both. - */ - port->bonded = bonding; - port->dual_link_port->bonded = bonding; - - return 0; + return tb_port_write(port, &val, TB_CFG_PORT, + port->cap_phy + LANE_ADP_CS_1, 1); } /** @@ -1056,36 +1056,52 @@ int tb_port_set_lane_bonding(struct tb_port *port, bool bonding) */ int tb_port_lane_bonding_enable(struct tb_port *port) { + enum tb_link_width width; int ret; /* * Enable lane bonding for both links if not already enabled by * for example the boot firmware. */ - ret = tb_port_get_link_width(port); - if (ret == 1) { - ret = tb_port_set_link_width(port, 2); + width = tb_port_get_link_width(port); + if (width == TB_LINK_WIDTH_SINGLE) { + ret = tb_port_set_link_width(port, TB_LINK_WIDTH_DUAL); if (ret) goto err_lane0; } - ret = tb_port_get_link_width(port->dual_link_port); - if (ret == 1) { - ret = tb_port_set_link_width(port->dual_link_port, 2); + width = tb_port_get_link_width(port->dual_link_port); + if (width == TB_LINK_WIDTH_SINGLE) { + ret = tb_port_set_link_width(port->dual_link_port, + TB_LINK_WIDTH_DUAL); if (ret) goto err_lane0; } - ret = tb_port_set_lane_bonding(port, true); - if (ret) - goto err_lane1; + /* + * Only set bonding if the link was not already bonded. This + * avoids the lane adapter to re-enter bonding state. + */ + if (width == TB_LINK_WIDTH_SINGLE) { + ret = tb_port_set_lane_bonding(port, true); + if (ret) + goto err_lane1; + } + + /* + * When lane 0 bonding is set it will affect lane 1 too so + * update both. + */ + port->bonded = true; + port->dual_link_port->bonded = true; return 0; err_lane1: - tb_port_set_link_width(port->dual_link_port, 1); + tb_port_set_link_width(port->dual_link_port, TB_LINK_WIDTH_SINGLE); err_lane0: - tb_port_set_link_width(port, 1); + tb_port_set_link_width(port, TB_LINK_WIDTH_SINGLE); + return ret; } @@ -1099,27 +1115,34 @@ err_lane0: void tb_port_lane_bonding_disable(struct tb_port *port) { tb_port_set_lane_bonding(port, false); - tb_port_set_link_width(port->dual_link_port, 1); - tb_port_set_link_width(port, 1); + tb_port_set_link_width(port->dual_link_port, TB_LINK_WIDTH_SINGLE); + tb_port_set_link_width(port, TB_LINK_WIDTH_SINGLE); + port->dual_link_port->bonded = false; + port->bonded = false; } /** * tb_port_wait_for_link_width() - Wait until link reaches specific width * @port: Port to wait for - * @width: Expected link width (%1 or %2) + * @width_mask: Expected link width mask * @timeout_msec: Timeout in ms how long to wait * * Should be used after both ends of the link have been bonded (or * bonding has been disabled) to wait until the link actually reaches - * the expected state. Returns %-ETIMEDOUT if the @width was not reached - * within the given timeout, %0 if it did. + * the expected state. Returns %-ETIMEDOUT if the width was not reached + * 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, int width, +int tb_port_wait_for_link_width(struct tb_port *port, unsigned int width_mask, 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)) + return -EOPNOTSUPP; + do { ret = tb_port_get_link_width(port); if (ret < 0) { @@ -1130,7 +1153,7 @@ int tb_port_wait_for_link_width(struct tb_port *port, int width, */ if (ret != -EACCES) return ret; - } else if (ret == width) { + } else if (ret & width_mask) { return 0; } @@ -1183,135 +1206,6 @@ int tb_port_update_credits(struct tb_port *port) return tb_port_do_update_credits(port->dual_link_port); } -static int __tb_port_pm_secondary_set(struct tb_port *port, bool secondary) -{ - u32 phy; - int ret; - - ret = tb_port_read(port, &phy, TB_CFG_PORT, - port->cap_phy + LANE_ADP_CS_1, 1); - if (ret) - return ret; - - if (secondary) - phy |= LANE_ADP_CS_1_PMS; - else - phy &= ~LANE_ADP_CS_1_PMS; - - return tb_port_write(port, &phy, TB_CFG_PORT, - port->cap_phy + LANE_ADP_CS_1, 1); -} - -static int tb_port_pm_secondary_enable(struct tb_port *port) -{ - return __tb_port_pm_secondary_set(port, true); -} - -static int tb_port_pm_secondary_disable(struct tb_port *port) -{ - return __tb_port_pm_secondary_set(port, false); -} - -/* Called for USB4 or Titan Ridge routers only */ -static bool tb_port_clx_supported(struct tb_port *port, unsigned int clx_mask) -{ - u32 val, mask = 0; - bool ret; - - /* Don't enable CLx in case of two single-lane links */ - if (!port->bonded && port->dual_link_port) - return false; - - /* Don't enable CLx in case of inter-domain link */ - if (port->xdomain) - return false; - - if (tb_switch_is_usb4(port->sw)) { - if (!usb4_port_clx_supported(port)) - return false; - } else if (!tb_lc_is_clx_supported(port)) { - return false; - } - - if (clx_mask & TB_CL1) { - /* CL0s and CL1 are enabled and supported together */ - mask |= LANE_ADP_CS_0_CL0S_SUPPORT | LANE_ADP_CS_0_CL1_SUPPORT; - } - if (clx_mask & TB_CL2) - mask |= LANE_ADP_CS_0_CL2_SUPPORT; - - ret = tb_port_read(port, &val, TB_CFG_PORT, - port->cap_phy + LANE_ADP_CS_0, 1); - if (ret) - return false; - - return !!(val & mask); -} - -static int __tb_port_clx_set(struct tb_port *port, enum tb_clx clx, bool enable) -{ - u32 phy, mask; - int ret; - - /* CL0s and CL1 are enabled and supported together */ - if (clx == TB_CL1) - mask = LANE_ADP_CS_1_CL0S_ENABLE | LANE_ADP_CS_1_CL1_ENABLE; - else - /* For now we support only CL0s and CL1. Not CL2 */ - return -EOPNOTSUPP; - - ret = tb_port_read(port, &phy, TB_CFG_PORT, - port->cap_phy + LANE_ADP_CS_1, 1); - if (ret) - return ret; - - if (enable) - phy |= mask; - else - phy &= ~mask; - - return tb_port_write(port, &phy, TB_CFG_PORT, - port->cap_phy + LANE_ADP_CS_1, 1); -} - -static int tb_port_clx_disable(struct tb_port *port, enum tb_clx clx) -{ - return __tb_port_clx_set(port, clx, false); -} - -static int tb_port_clx_enable(struct tb_port *port, enum tb_clx clx) -{ - return __tb_port_clx_set(port, clx, true); -} - -/** - * tb_port_is_clx_enabled() - Is given CL state enabled - * @port: USB4 port to check - * @clx_mask: Mask of CL states to check - * - * Returns true if any of the given CL states is enabled for @port. - */ -bool tb_port_is_clx_enabled(struct tb_port *port, unsigned int clx_mask) -{ - u32 val, mask = 0; - int ret; - - if (!tb_port_clx_supported(port, clx_mask)) - return false; - - if (clx_mask & TB_CL1) - mask |= LANE_ADP_CS_1_CL0S_ENABLE | LANE_ADP_CS_1_CL1_ENABLE; - if (clx_mask & TB_CL2) - mask |= LANE_ADP_CS_1_CL2_ENABLE; - - ret = tb_port_read(port, &val, TB_CFG_PORT, - port->cap_phy + LANE_ADP_CS_1, 1); - if (ret) - return false; - - return !!(val & mask); -} - static int tb_port_start_lane_initialization(struct tb_port *port) { int ret; @@ -1911,20 +1805,57 @@ static ssize_t speed_show(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR(rx_speed, 0444, speed_show, NULL); static DEVICE_ATTR(tx_speed, 0444, speed_show, NULL); -static ssize_t lanes_show(struct device *dev, struct device_attribute *attr, - char *buf) +static ssize_t rx_lanes_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct tb_switch *sw = tb_to_switch(dev); + unsigned int width; + + switch (sw->link_width) { + case TB_LINK_WIDTH_SINGLE: + case TB_LINK_WIDTH_ASYM_TX: + width = 1; + break; + case TB_LINK_WIDTH_DUAL: + width = 2; + break; + case TB_LINK_WIDTH_ASYM_RX: + width = 3; + break; + default: + WARN_ON_ONCE(1); + return -EINVAL; + } - return sysfs_emit(buf, "%u\n", sw->link_width); + return sysfs_emit(buf, "%u\n", width); } +static DEVICE_ATTR(rx_lanes, 0444, rx_lanes_show, NULL); -/* - * Currently link has same amount of lanes both directions (1 or 2) but - * expose them separately to allow possible asymmetric links in the future. - */ -static DEVICE_ATTR(rx_lanes, 0444, lanes_show, NULL); -static DEVICE_ATTR(tx_lanes, 0444, lanes_show, NULL); +static ssize_t tx_lanes_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tb_switch *sw = tb_to_switch(dev); + unsigned int width; + + switch (sw->link_width) { + case TB_LINK_WIDTH_SINGLE: + case TB_LINK_WIDTH_ASYM_RX: + width = 1; + break; + case TB_LINK_WIDTH_DUAL: + width = 2; + break; + case TB_LINK_WIDTH_ASYM_TX: + width = 3; + break; + default: + WARN_ON_ONCE(1); + return -EINVAL; + } + + return sysfs_emit(buf, "%u\n", width); +} +static DEVICE_ATTR(tx_lanes, 0444, tx_lanes_show, NULL); static ssize_t nvm_authenticate_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -2189,8 +2120,9 @@ static int tb_switch_uevent(const struct device *dev, struct kobj_uevent_env *en const struct tb_switch *sw = tb_to_switch(dev); const char *type; - if (sw->config.thunderbolt_version == USB4_VERSION_1_0) { - if (add_uevent_var(env, "USB4_VERSION=1.0")) + if (tb_switch_is_usb4(sw)) { + if (add_uevent_var(env, "USB4_VERSION=%u.0", + usb4_switch_version(sw))) return -ENOMEM; } @@ -2498,9 +2430,13 @@ int tb_switch_configure(struct tb_switch *sw) /* * For USB4 devices, we need to program the CM version * accordingly so that it knows to expose all the - * additional capabilities. + * additional capabilities. Program it according to USB4 + * version to avoid changing existing (v1) routers behaviour. */ - sw->config.cmuv = USB4_VERSION_1_0; + if (usb4_switch_version(sw) < 2) + sw->config.cmuv = ROUTER_CS_4_CMUV_V1; + else + sw->config.cmuv = ROUTER_CS_4_CMUV_V2; sw->config.plug_events_delay = 0xa; /* Enumerate the switch */ @@ -2530,6 +2466,22 @@ int tb_switch_configure(struct tb_switch *sw) return tb_plug_events_active(sw, true); } +/** + * tb_switch_configuration_valid() - Set the tunneling configuration to be valid + * @sw: Router to configure + * + * Needs to be called before any tunnels can be setup through the + * router. Can be called to any router. + * + * Returns %0 in success and negative errno otherwise. + */ +int tb_switch_configuration_valid(struct tb_switch *sw) +{ + if (tb_switch_is_usb4(sw)) + return usb4_switch_configuration_valid(sw); + return 0; +} + static int tb_switch_set_uuid(struct tb_switch *sw) { bool uid = false; @@ -2754,9 +2706,9 @@ static int tb_switch_update_link_attributes(struct tb_switch *sw) */ int tb_switch_lane_bonding_enable(struct tb_switch *sw) { - struct tb_switch *parent = tb_to_switch(sw->dev.parent); struct tb_port *up, *down; u64 route = tb_route(sw); + unsigned int width_mask; int ret; if (!route) @@ -2766,10 +2718,10 @@ int tb_switch_lane_bonding_enable(struct tb_switch *sw) return 0; up = tb_upstream_port(sw); - down = tb_port_at(route, parent); + down = tb_switch_downstream_port(sw); - if (!tb_port_is_width_supported(up, 2) || - !tb_port_is_width_supported(down, 2)) + if (!tb_port_is_width_supported(up, TB_LINK_WIDTH_DUAL) || + !tb_port_is_width_supported(down, TB_LINK_WIDTH_DUAL)) return 0; ret = tb_port_lane_bonding_enable(up); @@ -2785,7 +2737,11 @@ int tb_switch_lane_bonding_enable(struct tb_switch *sw) return ret; } - ret = tb_port_wait_for_link_width(down, 2, 100); + /* 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; @@ -2808,8 +2764,8 @@ int tb_switch_lane_bonding_enable(struct tb_switch *sw) */ void tb_switch_lane_bonding_disable(struct tb_switch *sw) { - struct tb_switch *parent = tb_to_switch(sw->dev.parent); struct tb_port *up, *down; + int ret; if (!tb_route(sw)) return; @@ -2818,7 +2774,7 @@ void tb_switch_lane_bonding_disable(struct tb_switch *sw) if (!up->bonded) return; - down = tb_port_at(tb_route(sw), parent); + down = tb_switch_downstream_port(sw); tb_port_lane_bonding_disable(up); tb_port_lane_bonding_disable(down); @@ -2827,7 +2783,8 @@ 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. */ - if (tb_port_wait_for_link_width(down, 1, 100) == -ETIMEDOUT) + 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"); tb_port_update_credits(down); @@ -2994,6 +2951,10 @@ int tb_switch_add(struct tb_switch *sw) if (ret) return ret; + ret = tb_switch_clx_init(sw); + if (ret) + return ret; + ret = tb_switch_tmu_init(sw); if (ret) return ret; @@ -3246,13 +3207,8 @@ void tb_switch_suspend(struct tb_switch *sw, bool runtime) /* * Actually only needed for Titan Ridge but for simplicity can be * done for USB4 device too as CLx is re-enabled at resume. - * CL0s and CL1 are enabled and supported together. */ - if (tb_switch_is_clx_enabled(sw, TB_CL1)) { - if (tb_switch_disable_clx(sw, TB_CL1)) - tb_sw_warn(sw, "failed to disable %s on upstream port\n", - tb_switch_clx_name(TB_CL1)); - } + tb_switch_clx_disable(sw); err = tb_plug_events_active(sw, false); if (err) @@ -3474,234 +3430,6 @@ struct tb_port *tb_switch_find_port(struct tb_switch *sw, return NULL; } -static int tb_switch_pm_secondary_resolve(struct tb_switch *sw) -{ - struct tb_switch *parent = tb_switch_parent(sw); - struct tb_port *up, *down; - int ret; - - if (!tb_route(sw)) - return 0; - - up = tb_upstream_port(sw); - down = tb_port_at(tb_route(sw), parent); - ret = tb_port_pm_secondary_enable(up); - if (ret) - return ret; - - return tb_port_pm_secondary_disable(down); -} - -static int __tb_switch_enable_clx(struct tb_switch *sw, enum tb_clx clx) -{ - struct tb_switch *parent = tb_switch_parent(sw); - bool up_clx_support, down_clx_support; - struct tb_port *up, *down; - int ret; - - if (!tb_switch_is_clx_supported(sw)) - return 0; - - /* - * Enable CLx for host router's downstream port as part of the - * downstream router enabling procedure. - */ - if (!tb_route(sw)) - return 0; - - /* Enable CLx only for first hop router (depth = 1) */ - if (tb_route(parent)) - return 0; - - ret = tb_switch_pm_secondary_resolve(sw); - if (ret) - return ret; - - up = tb_upstream_port(sw); - down = tb_port_at(tb_route(sw), parent); - - up_clx_support = tb_port_clx_supported(up, clx); - down_clx_support = tb_port_clx_supported(down, clx); - - tb_port_dbg(up, "%s %ssupported\n", tb_switch_clx_name(clx), - up_clx_support ? "" : "not "); - tb_port_dbg(down, "%s %ssupported\n", tb_switch_clx_name(clx), - down_clx_support ? "" : "not "); - - if (!up_clx_support || !down_clx_support) - return -EOPNOTSUPP; - - ret = tb_port_clx_enable(up, clx); - if (ret) - return ret; - - ret = tb_port_clx_enable(down, clx); - if (ret) { - tb_port_clx_disable(up, clx); - return ret; - } - - ret = tb_switch_mask_clx_objections(sw); - if (ret) { - tb_port_clx_disable(up, clx); - tb_port_clx_disable(down, clx); - return ret; - } - - sw->clx = clx; - - tb_port_dbg(up, "%s enabled\n", tb_switch_clx_name(clx)); - return 0; -} - -/** - * tb_switch_enable_clx() - Enable CLx on upstream port of specified router - * @sw: Router to enable CLx for - * @clx: The CLx state to enable - * - * Enable CLx state only for first hop router. That is the most common - * use-case, that is intended for better thermal management, and so helps - * to improve performance. CLx is enabled only if both sides of the link - * support CLx, and if both sides of the link are not configured as two - * single lane links and only if the link is not inter-domain link. The - * complete set of conditions is described in CM Guide 1.0 section 8.1. - * - * Return: Returns 0 on success or an error code on failure. - */ -int tb_switch_enable_clx(struct tb_switch *sw, enum tb_clx clx) -{ - struct tb_switch *root_sw = sw->tb->root_switch; - - if (!clx_enabled) - return 0; - - /* - * CLx is not enabled and validated on Intel USB4 platforms before - * Alder Lake. - */ - if (root_sw->generation < 4 || tb_switch_is_tiger_lake(root_sw)) - return 0; - - switch (clx) { - case TB_CL1: - /* CL0s and CL1 are enabled and supported together */ - return __tb_switch_enable_clx(sw, clx); - - default: - return -EOPNOTSUPP; - } -} - -static int __tb_switch_disable_clx(struct tb_switch *sw, enum tb_clx clx) -{ - struct tb_switch *parent = tb_switch_parent(sw); - struct tb_port *up, *down; - int ret; - - if (!tb_switch_is_clx_supported(sw)) - return 0; - - /* - * Disable CLx for host router's downstream port as part of the - * downstream router enabling procedure. - */ - if (!tb_route(sw)) - return 0; - - /* Disable CLx only for first hop router (depth = 1) */ - if (tb_route(parent)) - return 0; - - up = tb_upstream_port(sw); - down = tb_port_at(tb_route(sw), parent); - ret = tb_port_clx_disable(up, clx); - if (ret) - return ret; - - ret = tb_port_clx_disable(down, clx); - if (ret) - return ret; - - sw->clx = TB_CLX_DISABLE; - - tb_port_dbg(up, "%s disabled\n", tb_switch_clx_name(clx)); - return 0; -} - -/** - * tb_switch_disable_clx() - Disable CLx on upstream port of specified router - * @sw: Router to disable CLx for - * @clx: The CLx state to disable - * - * Return: Returns 0 on success or an error code on failure. - */ -int tb_switch_disable_clx(struct tb_switch *sw, enum tb_clx clx) -{ - if (!clx_enabled) - return 0; - - switch (clx) { - case TB_CL1: - /* CL0s and CL1 are enabled and supported together */ - return __tb_switch_disable_clx(sw, clx); - - default: - return -EOPNOTSUPP; - } -} - -/** - * tb_switch_mask_clx_objections() - Mask CLx objections for a router - * @sw: Router to mask objections for - * - * Mask the objections coming from the second depth routers in order to - * stop these objections from interfering with the CLx states of the first - * depth link. - */ -int tb_switch_mask_clx_objections(struct tb_switch *sw) -{ - int up_port = sw->config.upstream_port_number; - u32 offset, val[2], mask_obj, unmask_obj; - int ret, i; - - /* Only Titan Ridge of pre-USB4 devices support CLx states */ - if (!tb_switch_is_titan_ridge(sw)) - return 0; - - if (!tb_route(sw)) - return 0; - - /* - * In Titan Ridge there are only 2 dual-lane Thunderbolt ports: - * Port A consists of lane adapters 1,2 and - * Port B consists of lane adapters 3,4 - * If upstream port is A, (lanes are 1,2), we mask objections from - * port B (lanes 3,4) and unmask objections from Port A and vice-versa. - */ - if (up_port == 1) { - mask_obj = TB_LOW_PWR_C0_PORT_B_MASK; - unmask_obj = TB_LOW_PWR_C1_PORT_A_MASK; - offset = TB_LOW_PWR_C1_CL1; - } else { - mask_obj = TB_LOW_PWR_C1_PORT_A_MASK; - unmask_obj = TB_LOW_PWR_C0_PORT_B_MASK; - offset = TB_LOW_PWR_C3_CL1; - } - - ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, - sw->cap_lp + offset, ARRAY_SIZE(val)); - if (ret) - return ret; - - for (i = 0; i < ARRAY_SIZE(val); i++) { - val[i] |= mask_obj; - val[i] &= ~unmask_obj; - } - - return tb_sw_write(sw, &val, TB_CFG_SWITCH, - sw->cap_lp + offset, ARRAY_SIZE(val)); -} - /* * Can be used for read/write a specified PCIe bridge for any Thunderbolt 3 * device. For now used only for Titan Ridge. diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index c1af712ca728..62b26b7998fd 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -131,7 +131,7 @@ tb_attach_bandwidth_group(struct tb_cm *tcm, struct tb_port *in, static void tb_discover_bandwidth_group(struct tb_cm *tcm, struct tb_port *in, struct tb_port *out) { - if (usb4_dp_port_bw_mode_enabled(in)) { + if (usb4_dp_port_bandwidth_mode_enabled(in)) { int index, i; index = usb4_dp_port_group_id(in); @@ -240,6 +240,147 @@ static void tb_discover_dp_resources(struct tb *tb) } } +/* Enables CL states up to host router */ +static int tb_enable_clx(struct tb_switch *sw) +{ + struct tb_cm *tcm = tb_priv(sw->tb); + unsigned int clx = TB_CL0S | TB_CL1; + const struct tb_tunnel *tunnel; + int ret; + + /* + * Currently only enable CLx for the first link. This is enough + * to allow the CPU to save energy at least on Intel hardware + * and makes it slightly simpler to implement. We may change + * this in the future to cover the whole topology if it turns + * out to be beneficial. + */ + while (sw && sw->config.depth > 1) + sw = tb_switch_parent(sw); + + if (!sw) + return 0; + + if (sw->config.depth != 1) + return 0; + + /* + * If we are re-enabling then check if there is an active DMA + * tunnel and in that case bail out. + */ + list_for_each_entry(tunnel, &tcm->tunnel_list, list) { + if (tb_tunnel_is_dma(tunnel)) { + if (tb_tunnel_port_on_path(tunnel, tb_upstream_port(sw))) + return 0; + } + } + + /* + * Initially try with CL2. If that's not supported by the + * topology try with CL0s and CL1 and then give up. + */ + ret = tb_switch_clx_enable(sw, clx | TB_CL2); + if (ret == -EOPNOTSUPP) + ret = tb_switch_clx_enable(sw, clx); + return ret == -EOPNOTSUPP ? 0 : ret; +} + +/* Disables CL states up to the host router */ +static void tb_disable_clx(struct tb_switch *sw) +{ + do { + if (tb_switch_clx_disable(sw) < 0) + tb_sw_warn(sw, "failed to disable CL states\n"); + sw = tb_switch_parent(sw); + } while (sw); +} + +static int tb_increase_switch_tmu_accuracy(struct device *dev, void *data) +{ + struct tb_switch *sw; + + sw = tb_to_switch(dev); + if (!sw) + return 0; + + if (tb_switch_tmu_is_configured(sw, TB_SWITCH_TMU_MODE_LOWRES)) { + enum tb_switch_tmu_mode mode; + int ret; + + if (tb_switch_clx_is_enabled(sw, TB_CL1)) + mode = TB_SWITCH_TMU_MODE_HIFI_UNI; + else + mode = TB_SWITCH_TMU_MODE_HIFI_BI; + + ret = tb_switch_tmu_configure(sw, mode); + if (ret) + return ret; + + return tb_switch_tmu_enable(sw); + } + + return 0; +} + +static void tb_increase_tmu_accuracy(struct tb_tunnel *tunnel) +{ + struct tb_switch *sw; + + if (!tunnel) + return; + + /* + * Once first DP tunnel is established we change the TMU + * accuracy of first depth child routers (and the host router) + * to the highest. This is needed for the DP tunneling to work + * but also allows CL0s. + * + * If both routers are v2 then we don't need to do anything as + * they are using enhanced TMU mode that allows all CLx. + */ + sw = tunnel->tb->root_switch; + device_for_each_child(&sw->dev, NULL, tb_increase_switch_tmu_accuracy); +} + +static int tb_enable_tmu(struct tb_switch *sw) +{ + int ret; + + /* + * If both routers at the end of the link are v2 we simply + * enable the enhanched uni-directional mode. That covers all + * the CL states. For v1 and before we need to use the normal + * rate to allow CL1 (when supported). Otherwise we keep the TMU + * running at the highest accuracy. + */ + ret = tb_switch_tmu_configure(sw, + TB_SWITCH_TMU_MODE_MEDRES_ENHANCED_UNI); + if (ret == -EOPNOTSUPP) { + if (tb_switch_clx_is_enabled(sw, TB_CL1)) + ret = tb_switch_tmu_configure(sw, + TB_SWITCH_TMU_MODE_LOWRES); + else + ret = tb_switch_tmu_configure(sw, + TB_SWITCH_TMU_MODE_HIFI_BI); + } + if (ret) + return ret; + + /* If it is already enabled in correct mode, don't touch it */ + if (tb_switch_tmu_is_enabled(sw)) + return 0; + + ret = tb_switch_tmu_disable(sw); + if (ret) + return ret; + + ret = tb_switch_tmu_post_time(sw); + if (ret) + return ret; + + return tb_switch_tmu_enable(sw); +} + static void tb_switch_discover_tunnels(struct tb_switch *sw, struct list_head *list, bool alloc_hopids) @@ -253,13 +394,7 @@ static void tb_switch_discover_tunnels(struct tb_switch *sw, switch (port->config.type) { case TB_TYPE_DP_HDMI_IN: tunnel = tb_tunnel_discover_dp(tb, port, alloc_hopids); - /* - * In case of DP tunnel exists, change host router's - * 1st children TMU mode to HiFi for CL0s to work. - */ - if (tunnel) - tb_switch_enable_tmu_1st_child(tb->root_switch, - TB_SWITCH_TMU_RATE_HIFI); + tb_increase_tmu_accuracy(tunnel); break; case TB_TYPE_PCIE_DOWN: @@ -357,25 +492,6 @@ static void tb_scan_xdomain(struct tb_port *port) } } -static int tb_enable_tmu(struct tb_switch *sw) -{ - int ret; - - /* If it is already enabled in correct mode, don't touch it */ - if (tb_switch_tmu_is_enabled(sw, sw->tmu.unidirectional_request)) - return 0; - - ret = tb_switch_tmu_disable(sw); - if (ret) - return ret; - - ret = tb_switch_tmu_post_time(sw); - if (ret) - return ret; - - return tb_switch_tmu_enable(sw); -} - /** * tb_find_unused_port() - return the first inactive port on @sw * @sw: Switch to find the port on @@ -480,7 +596,8 @@ static int tb_available_bandwidth(struct tb *tb, struct tb_port *src_port, usb3_consumed_down = 0; } - *available_up = *available_down = 40000; + /* Maximum possible bandwidth asymmetric Gen 4 link is 120 Gb/s */ + *available_up = *available_down = 120000; /* Find the minimum available bandwidth over all links */ tb_for_each_port_on_path(src_port, dst_port, port) { @@ -491,18 +608,45 @@ static int tb_available_bandwidth(struct tb *tb, struct tb_port *src_port, 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 { + 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 = port->bonded ? 2 : 1; + 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 { + up_bw = link_speed * link_width * 1000; + down_bw = up_bw; + } + } - up_bw = link_speed * link_width * 1000; /* Mb/s */ /* Leave 10% guard band */ up_bw -= up_bw / 10; - down_bw = up_bw; + down_bw -= down_bw / 10; tb_port_dbg(port, "link total bandwidth %d/%d Mb/s\n", up_bw, down_bw); @@ -628,7 +772,7 @@ static int tb_tunnel_usb3(struct tb *tb, struct tb_switch *sw) * Look up available down port. Since we are chaining it should * be found right above this switch. */ - port = tb_port_at(tb_route(sw), parent); + port = tb_switch_downstream_port(sw); down = tb_find_usb3_down(parent, port); if (!down) return 0; @@ -739,7 +883,6 @@ static void tb_scan_port(struct tb_port *port) struct tb_port *upstream_port; bool discovery = false; struct tb_switch *sw; - int ret; if (tb_is_upstream_port(port)) return; @@ -838,28 +981,20 @@ static void tb_scan_port(struct tb_port *port) * CL0s and CL1 are enabled and supported together. * Silently ignore CLx enabling in case CLx is not supported. */ - if (discovery) { + if (discovery) tb_sw_dbg(sw, "discovery, not touching CL states\n"); - } else { - ret = tb_switch_enable_clx(sw, TB_CL1); - if (ret && ret != -EOPNOTSUPP) - tb_sw_warn(sw, "failed to enable %s on upstream port\n", - tb_switch_clx_name(TB_CL1)); - } - - if (tb_switch_is_clx_enabled(sw, TB_CL1)) - /* - * To support highest CLx state, we set router's TMU to - * Normal-Uni mode. - */ - tb_switch_tmu_configure(sw, TB_SWITCH_TMU_RATE_NORMAL, true); - else - /* If CLx disabled, configure router's TMU to HiFi-Bidir mode*/ - tb_switch_tmu_configure(sw, TB_SWITCH_TMU_RATE_HIFI, false); + else if (tb_enable_clx(sw)) + tb_sw_warn(sw, "failed to enable CL states\n"); if (tb_enable_tmu(sw)) tb_sw_warn(sw, "failed to enable TMU\n"); + /* + * Configuration valid needs to be set after the TMU has been + * enabled for the upstream port of the router so we do it here. + */ + tb_switch_configuration_valid(sw); + /* Scan upstream retimers */ tb_retimer_scan(upstream_port, true); @@ -1034,7 +1169,7 @@ tb_recalc_estimated_bandwidth_for_group(struct tb_bandwidth_group *group) struct tb_tunnel *tunnel; struct tb_port *out; - if (!usb4_dp_port_bw_mode_enabled(in)) + if (!usb4_dp_port_bandwidth_mode_enabled(in)) continue; tunnel = tb_find_tunnel(tb, TB_TUNNEL_DP, in, NULL); @@ -1082,7 +1217,7 @@ tb_recalc_estimated_bandwidth_for_group(struct tb_bandwidth_group *group) else estimated_bw = estimated_up; - if (usb4_dp_port_set_estimated_bw(in, estimated_bw)) + if (usb4_dp_port_set_estimated_bandwidth(in, estimated_bw)) tb_port_warn(in, "failed to update estimated bandwidth\n"); } @@ -1263,8 +1398,7 @@ static void tb_tunnel_dp(struct tb *tb) * In case of DP tunnel exists, change host router's 1st children * TMU mode to HiFi for CL0s to work. */ - tb_switch_enable_tmu_1st_child(tb->root_switch, TB_SWITCH_TMU_RATE_HIFI); - + tb_increase_tmu_accuracy(tunnel); return; err_free: @@ -1378,7 +1512,6 @@ static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw) { struct tb_port *up, *down, *port; struct tb_cm *tcm = tb_priv(tb); - struct tb_switch *parent_sw; struct tb_tunnel *tunnel; up = tb_switch_find_port(sw, TB_TYPE_PCIE_UP); @@ -1389,9 +1522,8 @@ static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw) * Look up available down port. Since we are chaining it should * be found right above this switch. */ - parent_sw = tb_to_switch(sw->dev.parent); - port = tb_port_at(tb_route(sw), parent_sw); - down = tb_find_pcie_down(parent_sw, port); + port = tb_switch_downstream_port(sw); + down = tb_find_pcie_down(tb_switch_parent(sw), port); if (!down) return 0; @@ -1428,30 +1560,45 @@ static int tb_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, struct tb_port *nhi_port, *dst_port; struct tb_tunnel *tunnel; struct tb_switch *sw; + int ret; sw = tb_to_switch(xd->dev.parent); dst_port = tb_port_at(xd->route, sw); nhi_port = tb_switch_find_port(tb->root_switch, TB_TYPE_NHI); mutex_lock(&tb->lock); + + /* + * When tunneling DMA paths the link should not enter CL states + * so disable them now. + */ + tb_disable_clx(sw); + tunnel = tb_tunnel_alloc_dma(tb, nhi_port, dst_port, transmit_path, transmit_ring, receive_path, receive_ring); if (!tunnel) { - mutex_unlock(&tb->lock); - return -ENOMEM; + ret = -ENOMEM; + goto err_clx; } if (tb_tunnel_activate(tunnel)) { tb_port_info(nhi_port, "DMA tunnel activation failed, aborting\n"); - tb_tunnel_free(tunnel); - mutex_unlock(&tb->lock); - return -EIO; + ret = -EIO; + goto err_free; } list_add_tail(&tunnel->list, &tcm->tunnel_list); mutex_unlock(&tb->lock); return 0; + +err_free: + tb_tunnel_free(tunnel); +err_clx: + tb_enable_clx(sw); + mutex_unlock(&tb->lock); + + return ret; } static void __tb_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, @@ -1477,6 +1624,13 @@ static void __tb_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, receive_path, receive_ring)) tb_deactivate_and_free_tunnel(tunnel); } + + /* + * Try to re-enable CL states now, it is OK if this fails + * because we may still have another DMA tunnel active through + * the same host router USB4 downstream port. + */ + tb_enable_clx(sw); } static int tb_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, @@ -1758,12 +1912,12 @@ static void tb_handle_dp_bandwidth_request(struct work_struct *work) tb_port_dbg(in, "handling bandwidth allocation request\n"); - if (!usb4_dp_port_bw_mode_enabled(in)) { + if (!usb4_dp_port_bandwidth_mode_enabled(in)) { tb_port_warn(in, "bandwidth allocation mode not enabled\n"); goto unlock; } - ret = usb4_dp_port_requested_bw(in); + ret = usb4_dp_port_requested_bandwidth(in); if (ret < 0) { if (ret == -ENODATA) tb_port_dbg(in, "no bandwidth request active\n"); @@ -1830,17 +1984,26 @@ static void tb_queue_dp_bandwidth_request(struct tb *tb, u64 route, u8 port) static void tb_handle_notification(struct tb *tb, u64 route, const struct cfg_error_pkg *error) { - if (tb_cfg_ack_notification(tb->ctl, route, error)) - tb_warn(tb, "could not ack notification on %llx\n", route); switch (error->error) { + case TB_CFG_ERROR_PCIE_WAKE: + case TB_CFG_ERROR_DP_CON_CHANGE: + case TB_CFG_ERROR_DPTX_DISCOVERY: + if (tb_cfg_ack_notification(tb->ctl, route, error)) + tb_warn(tb, "could not ack notification on %llx\n", + route); + break; + case TB_CFG_ERROR_DP_BW: + if (tb_cfg_ack_notification(tb->ctl, route, error)) + tb_warn(tb, "could not ack notification on %llx\n", + route); tb_queue_dp_bandwidth_request(tb, route, error->port); break; default: - /* Ack is enough */ - return; + /* Ignore for now */ + break; } } @@ -1955,8 +2118,7 @@ static int tb_start(struct tb *tb) * To support highest CLx state, we set host router's TMU to * Normal mode. */ - tb_switch_tmu_configure(tb->root_switch, TB_SWITCH_TMU_RATE_NORMAL, - false); + tb_switch_tmu_configure(tb->root_switch, TB_SWITCH_TMU_MODE_LOWRES); /* Enable TMU if it is off */ tb_switch_tmu_enable(tb->root_switch); /* Full scan to discover devices added before the driver was loaded. */ @@ -1997,34 +2159,19 @@ static int tb_suspend_noirq(struct tb *tb) static void tb_restore_children(struct tb_switch *sw) { struct tb_port *port; - int ret; /* No need to restore if the router is already unplugged */ if (sw->is_unplugged) return; - /* - * CL0s and CL1 are enabled and supported together. - * Silently ignore CLx re-enabling in case CLx is not supported. - */ - ret = tb_switch_enable_clx(sw, TB_CL1); - if (ret && ret != -EOPNOTSUPP) - tb_sw_warn(sw, "failed to re-enable %s on upstream port\n", - tb_switch_clx_name(TB_CL1)); - - if (tb_switch_is_clx_enabled(sw, TB_CL1)) - /* - * To support highest CLx state, we set router's TMU to - * Normal-Uni mode. - */ - tb_switch_tmu_configure(sw, TB_SWITCH_TMU_RATE_NORMAL, true); - else - /* If CLx disabled, configure router's TMU to HiFi-Bidir mode*/ - tb_switch_tmu_configure(sw, TB_SWITCH_TMU_RATE_HIFI, false); + if (tb_enable_clx(sw)) + tb_sw_warn(sw, "failed to re-enable CL states\n"); if (tb_enable_tmu(sw)) tb_sw_warn(sw, "failed to restore TMU configuration\n"); + tb_switch_configuration_valid(sw); + tb_switch_for_each_port(sw, port) { if (!tb_port_has_remote(port) && !port->xdomain) continue; diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 275ff5219a3a..57a9b272cb94 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -19,10 +19,6 @@ #include "ctl.h" #include "dma_port.h" -#define NVM_MIN_SIZE SZ_32K -#define NVM_MAX_SIZE SZ_512K -#define NVM_DATA_DWORDS 16 - /* Keep link controller awake during update */ #define QUIRK_FORCE_POWER_LINK_CONTROLLER BIT(0) /* Disable CLx if not supported */ @@ -77,51 +73,37 @@ enum tb_nvm_write_ops { #define USB4_SWITCH_MAX_DEPTH 5 /** - * enum tb_switch_tmu_rate - TMU refresh rate - * @TB_SWITCH_TMU_RATE_OFF: %0 (Disable Time Sync handshake) - * @TB_SWITCH_TMU_RATE_HIFI: %16 us time interval between successive - * transmission of the Delay Request TSNOS - * (Time Sync Notification Ordered Set) on a Link - * @TB_SWITCH_TMU_RATE_NORMAL: %1 ms time interval between successive - * transmission of the Delay Request TSNOS on - * a Link + * enum tb_switch_tmu_mode - TMU mode + * @TB_SWITCH_TMU_MODE_OFF: TMU is off + * @TB_SWITCH_TMU_MODE_LOWRES: Uni-directional, normal mode + * @TB_SWITCH_TMU_MODE_HIFI_UNI: Uni-directional, HiFi mode + * @TB_SWITCH_TMU_MODE_HIFI_BI: Bi-directional, HiFi mode + * @TB_SWITCH_TMU_MODE_MEDRES_ENHANCED_UNI: Enhanced Uni-directional, MedRes mode + * + * Ordering is based on TMU accuracy level (highest last). */ -enum tb_switch_tmu_rate { - TB_SWITCH_TMU_RATE_OFF = 0, - TB_SWITCH_TMU_RATE_HIFI = 16, - TB_SWITCH_TMU_RATE_NORMAL = 1000, +enum tb_switch_tmu_mode { + TB_SWITCH_TMU_MODE_OFF, + TB_SWITCH_TMU_MODE_LOWRES, + TB_SWITCH_TMU_MODE_HIFI_UNI, + TB_SWITCH_TMU_MODE_HIFI_BI, + TB_SWITCH_TMU_MODE_MEDRES_ENHANCED_UNI, }; /** - * struct tb_switch_tmu - Structure holding switch TMU configuration + * struct tb_switch_tmu - Structure holding router TMU configuration * @cap: Offset to the TMU capability (%0 if not found) * @has_ucap: Does the switch support uni-directional mode - * @rate: TMU refresh rate related to upstream switch. In case of root - * switch this holds the domain rate. Reflects the HW setting. - * @unidirectional: Is the TMU in uni-directional or bi-directional mode - * related to upstream switch. Don't care for root switch. - * Reflects the HW setting. - * @unidirectional_request: Is the new TMU mode: uni-directional or bi-directional - * that is requested to be set. Related to upstream switch. - * Don't care for root switch. - * @rate_request: TMU new refresh rate related to upstream switch that is - * requested to be set. In case of root switch, this holds - * the new domain rate that is requested to be set. + * @mode: TMU mode related to the upstream router. Reflects the HW + * setting. Don't care for host router. + * @mode_request: TMU mode requested to set. Related to upstream router. + * Don't care for host router. */ struct tb_switch_tmu { int cap; bool has_ucap; - enum tb_switch_tmu_rate rate; - bool unidirectional; - bool unidirectional_request; - enum tb_switch_tmu_rate rate_request; -}; - -enum tb_clx { - TB_CLX_DISABLE, - /* CL0s and CL1 are enabled and supported together */ - TB_CL1 = BIT(0), - TB_CL2 = BIT(1), + enum tb_switch_tmu_mode mode; + enum tb_switch_tmu_mode mode_request; }; /** @@ -142,7 +124,7 @@ enum tb_clx { * @vendor_name: Name of the vendor (or %NULL if not known) * @device_name: Name of the device (or %NULL if not known) * @link_speed: Speed of the link in Gb/s - * @link_width: Width of the link (1 or 2) + * @link_width: Width of the upstream facing link * @link_usb4: Upstream link is USB4 * @generation: Switch Thunderbolt generation * @cap_plug_events: Offset to the plug events capability (%0 if not found) @@ -174,12 +156,17 @@ enum tb_clx { * @min_dp_main_credits: Router preferred minimum number of buffers for DP MAIN * @max_pcie_credits: Router preferred number of buffers for PCIe * @max_dma_credits: Router preferred number of buffers for DMA/P2P - * @clx: CLx state on the upstream link of the router + * @clx: CLx states on the upstream link of the router * * When the switch is being added or removed to the domain (other * 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; @@ -195,7 +182,7 @@ struct tb_switch { const char *vendor_name; const char *device_name; unsigned int link_speed; - unsigned int link_width; + enum tb_link_width link_width; bool link_usb4; unsigned int generation; int cap_plug_events; @@ -225,7 +212,7 @@ struct tb_switch { unsigned int min_dp_main_credits; unsigned int max_pcie_credits; unsigned int max_dma_credits; - enum tb_clx clx; + unsigned int clx; }; /** @@ -455,6 +442,11 @@ struct tb_path { #define TB_WAKE_ON_PCIE BIT(4) #define TB_WAKE_ON_DP BIT(5) +/* CL states */ +#define TB_CL0S BIT(0) +#define TB_CL1 BIT(1) +#define TB_CL2 BIT(2) + /** * struct tb_cm_ops - Connection manager specific operations vector * @driver_ready: Called right after control channel is started. Used by @@ -802,6 +794,7 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent, struct tb_switch *tb_switch_alloc_safe_mode(struct tb *tb, struct device *parent, u64 route); int tb_switch_configure(struct tb_switch *sw); +int tb_switch_configuration_valid(struct tb_switch *sw); int tb_switch_add(struct tb_switch *sw); void tb_switch_remove(struct tb_switch *sw); void tb_switch_suspend(struct tb_switch *sw, bool runtime); @@ -857,6 +850,20 @@ static inline struct tb_switch *tb_switch_parent(struct tb_switch *sw) return tb_to_switch(sw->dev.parent); } +/** + * tb_switch_downstream_port() - Return downstream facing port of parent router + * @sw: Device router pointer + * + * Only call for device routers. Returns the downstream facing port of + * the parent router. + */ +static inline struct tb_port *tb_switch_downstream_port(struct tb_switch *sw) +{ + if (WARN_ON(!tb_route(sw))) + return NULL; + return tb_port_at(tb_route(sw), tb_switch_parent(sw)); +} + static inline bool tb_switch_is_light_ridge(const struct tb_switch *sw) { return sw->config.vendor_id == PCI_VENDOR_ID_INTEL && @@ -936,17 +943,6 @@ static inline bool tb_switch_is_tiger_lake(const struct tb_switch *sw) } /** - * tb_switch_is_usb4() - Is the switch USB4 compliant - * @sw: Switch to check - * - * Returns true if the @sw is USB4 compliant router, false otherwise. - */ -static inline bool tb_switch_is_usb4(const struct tb_switch *sw) -{ - return sw->config.thunderbolt_version == USB4_VERSION_1_0; -} - -/** * tb_switch_is_icm() - Is the switch handled by ICM firmware * @sw: Switch to check * @@ -973,68 +969,58 @@ int tb_switch_tmu_init(struct tb_switch *sw); int tb_switch_tmu_post_time(struct tb_switch *sw); int tb_switch_tmu_disable(struct tb_switch *sw); int tb_switch_tmu_enable(struct tb_switch *sw); -void tb_switch_tmu_configure(struct tb_switch *sw, - enum tb_switch_tmu_rate rate, - bool unidirectional); -void tb_switch_enable_tmu_1st_child(struct tb_switch *sw, - enum tb_switch_tmu_rate rate); +int tb_switch_tmu_configure(struct tb_switch *sw, enum tb_switch_tmu_mode mode); + /** - * tb_switch_tmu_is_enabled() - Checks if the specified TMU mode is enabled - * @sw: Router whose TMU mode to check - * @unidirectional: If uni-directional (bi-directional otherwise) + * tb_switch_tmu_is_configured() - Is given TMU mode configured + * @sw: Router whose mode to check + * @mode: Mode to check * - * Return true if hardware TMU configuration matches the one passed in - * as parameter. That is HiFi/Normal and either uni-directional or bi-directional. + * Checks if given router TMU mode is configured to @mode. Note the + * router TMU might not be enabled to this mode. */ -static inline bool tb_switch_tmu_is_enabled(const struct tb_switch *sw, - bool unidirectional) +static inline bool tb_switch_tmu_is_configured(const struct tb_switch *sw, + enum tb_switch_tmu_mode mode) { - return sw->tmu.rate == sw->tmu.rate_request && - sw->tmu.unidirectional == unidirectional; + return sw->tmu.mode_request == mode; } -static inline const char *tb_switch_clx_name(enum tb_clx clx) +/** + * tb_switch_tmu_is_enabled() - Checks if the specified TMU mode is enabled + * @sw: Router whose TMU mode to check + * + * Return true if hardware TMU configuration matches the requested + * configuration (and is not %TB_SWITCH_TMU_MODE_OFF). + */ +static inline bool tb_switch_tmu_is_enabled(const struct tb_switch *sw) { - switch (clx) { - /* CL0s and CL1 are enabled and supported together */ - case TB_CL1: - return "CL0s/CL1"; - default: - return "unknown"; - } + return sw->tmu.mode != TB_SWITCH_TMU_MODE_OFF && + sw->tmu.mode == sw->tmu.mode_request; } -int tb_switch_enable_clx(struct tb_switch *sw, enum tb_clx clx); -int tb_switch_disable_clx(struct tb_switch *sw, enum tb_clx clx); +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); /** - * tb_switch_is_clx_enabled() - Checks if the CLx is enabled + * tb_switch_clx_is_enabled() - Checks if the CLx is enabled * @sw: Router to check for the CLx - * @clx: The CLx state to check for + * @clx: The CLx states to check for * * Checks if the specified CLx is enabled on the router upstream link. + * Returns true if any of the given states is enabled. + * * Not applicable for a host router. */ -static inline bool tb_switch_is_clx_enabled(const struct tb_switch *sw, - enum tb_clx clx) +static inline bool tb_switch_clx_is_enabled(const struct tb_switch *sw, + unsigned int clx) { - return sw->clx == clx; + return sw->clx & clx; } -/** - * tb_switch_is_clx_supported() - Is CLx supported on this type of router - * @sw: The router to check CLx support for - */ -static inline bool tb_switch_is_clx_supported(const struct tb_switch *sw) -{ - if (sw->quirks & QUIRK_NO_CLX) - return false; - - return tb_switch_is_usb4(sw) || tb_switch_is_titan_ridge(sw); -} - -int tb_switch_mask_clx_objections(struct tb_switch *sw); - int tb_switch_pcie_l1_enable(struct tb_switch *sw); int tb_switch_xhci_connect(struct tb_switch *sw); @@ -1073,14 +1059,12 @@ static inline bool tb_port_use_credit_allocation(const struct tb_port *port) int tb_port_get_link_speed(struct tb_port *port); int tb_port_get_link_width(struct tb_port *port); -int tb_port_set_link_width(struct tb_port *port, unsigned int width); -int tb_port_set_lane_bonding(struct tb_port *port, bool bonding); +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, int width, +int tb_port_wait_for_link_width(struct tb_port *port, unsigned int width_mask, int timeout_msec); int tb_port_update_credits(struct tb_port *port); -bool tb_port_is_clx_enabled(struct tb_port *port, unsigned int clx); int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec); int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap); @@ -1183,6 +1167,17 @@ static inline struct tb_switch *tb_xdomain_parent(struct tb_xdomain *xd) return tb_to_switch(xd->dev.parent); } +/** + * tb_xdomain_downstream_port() - Return downstream facing port of parent router + * @xd: Xdomain pointer + * + * Returns the downstream port the XDomain is connected to. + */ +static inline struct tb_port *tb_xdomain_downstream_port(struct tb_xdomain *xd) +{ + return tb_port_at(xd->route, tb_xdomain_parent(xd)); +} + int tb_retimer_nvm_read(struct tb_retimer *rt, unsigned int address, void *buf, size_t size); int tb_retimer_scan(struct tb_port *port, bool add); @@ -1200,7 +1195,31 @@ static inline struct tb_retimer *tb_to_retimer(struct device *dev) return NULL; } +/** + * usb4_switch_version() - Returns USB4 version of the router + * @sw: Router to check + * + * Returns major version of USB4 router (%1 for v1, %2 for v2 and so + * on). Can be called to pre-USB4 router too and in that case returns %0. + */ +static inline unsigned int usb4_switch_version(const struct tb_switch *sw) +{ + return FIELD_GET(USB4_VERSION_MAJOR_MASK, sw->config.thunderbolt_version); +} + +/** + * tb_switch_is_usb4() - Is the switch USB4 compliant + * @sw: Switch to check + * + * Returns true if the @sw is USB4 compliant router, false otherwise. + */ +static inline bool tb_switch_is_usb4(const struct tb_switch *sw) +{ + return usb4_switch_version(sw) > 0; +} + int usb4_switch_setup(struct tb_switch *sw); +int usb4_switch_configuration_valid(struct tb_switch *sw); int usb4_switch_read_uid(struct tb_switch *sw, u64 *uid); int usb4_switch_drom_read(struct tb_switch *sw, unsigned int address, void *buf, size_t size); @@ -1273,19 +1292,22 @@ int usb4_usb3_port_release_bandwidth(struct tb_port *port, int *upstream_bw, int *downstream_bw); int usb4_dp_port_set_cm_id(struct tb_port *port, int cm_id); -bool usb4_dp_port_bw_mode_supported(struct tb_port *port); -bool usb4_dp_port_bw_mode_enabled(struct tb_port *port); -int usb4_dp_port_set_cm_bw_mode_supported(struct tb_port *port, bool supported); +bool usb4_dp_port_bandwidth_mode_supported(struct tb_port *port); +bool usb4_dp_port_bandwidth_mode_enabled(struct tb_port *port); +int usb4_dp_port_set_cm_bandwidth_mode_supported(struct tb_port *port, + bool supported); int usb4_dp_port_group_id(struct tb_port *port); int usb4_dp_port_set_group_id(struct tb_port *port, int group_id); int usb4_dp_port_nrd(struct tb_port *port, int *rate, int *lanes); int usb4_dp_port_set_nrd(struct tb_port *port, int rate, int lanes); int usb4_dp_port_granularity(struct tb_port *port); int usb4_dp_port_set_granularity(struct tb_port *port, int granularity); -int usb4_dp_port_set_estimated_bw(struct tb_port *port, int bw); -int usb4_dp_port_allocated_bw(struct tb_port *port); -int usb4_dp_port_allocate_bw(struct tb_port *port, int bw); -int usb4_dp_port_requested_bw(struct tb_port *port); +int usb4_dp_port_set_estimated_bandwidth(struct tb_port *port, int bw); +int usb4_dp_port_allocated_bandwidth(struct tb_port *port); +int usb4_dp_port_allocate_bandwidth(struct tb_port *port, int bw); +int usb4_dp_port_requested_bandwidth(struct tb_port *port); + +int usb4_pci_port_set_ext_encapsulation(struct tb_port *port, bool enable); static inline bool tb_is_usb4_port_device(const struct device *dev) { @@ -1303,6 +1325,11 @@ struct usb4_port *usb4_port_device_add(struct tb_port *port); void usb4_port_device_remove(struct usb4_port *usb4); int usb4_port_device_resume(struct usb4_port *usb4); +static inline bool usb4_port_device_is_offline(const struct usb4_port *usb4) +{ + return usb4->offline; +} + void tb_check_quirks(struct tb_switch *sw); #ifdef CONFIG_ACPI diff --git a/drivers/thunderbolt/tb_msgs.h b/drivers/thunderbolt/tb_msgs.h index 3234bff07899..cd750e4b3440 100644 --- a/drivers/thunderbolt/tb_msgs.h +++ b/drivers/thunderbolt/tb_msgs.h @@ -30,6 +30,13 @@ enum tb_cfg_error { TB_CFG_ERROR_FLOW_CONTROL_ERROR = 13, TB_CFG_ERROR_LOCK = 15, TB_CFG_ERROR_DP_BW = 32, + TB_CFG_ERROR_ROP_CMPLT = 33, + TB_CFG_ERROR_POP_CMPLT = 34, + TB_CFG_ERROR_PCIE_WAKE = 35, + TB_CFG_ERROR_DP_CON_CHANGE = 36, + TB_CFG_ERROR_DPTX_DISCOVERY = 37, + TB_CFG_ERROR_LINK_RECOVERY = 38, + TB_CFG_ERROR_ASYM_LINK = 39, }; /* common header */ diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h index 2636423748cd..cf9f2370878a 100644 --- a/drivers/thunderbolt/tb_regs.h +++ b/drivers/thunderbolt/tb_regs.h @@ -190,11 +190,14 @@ struct tb_regs_switch_header { u32 thunderbolt_version:8; } __packed; -/* USB4 version 1.0 */ -#define USB4_VERSION_1_0 0x20 +/* Used with the router thunderbolt_version */ +#define USB4_VERSION_MAJOR_MASK GENMASK(7, 5) #define ROUTER_CS_1 0x01 #define ROUTER_CS_4 0x04 +/* Used with the router cmuv field */ +#define ROUTER_CS_4_CMUV_V1 0x10 +#define ROUTER_CS_4_CMUV_V2 0x20 #define ROUTER_CS_5 0x05 #define ROUTER_CS_5_SLP BIT(0) #define ROUTER_CS_5_WOP BIT(1) @@ -249,11 +252,13 @@ enum usb4_switch_op { #define TMU_RTR_CS_3_LOCAL_TIME_NS_MASK GENMASK(15, 0) #define TMU_RTR_CS_3_TS_PACKET_INTERVAL_MASK GENMASK(31, 16) #define TMU_RTR_CS_3_TS_PACKET_INTERVAL_SHIFT 16 -#define TMU_RTR_CS_15 0xf +#define TMU_RTR_CS_15 0x0f #define TMU_RTR_CS_15_FREQ_AVG_MASK GENMASK(5, 0) #define TMU_RTR_CS_15_DELAY_AVG_MASK GENMASK(11, 6) #define TMU_RTR_CS_15_OFFSET_AVG_MASK GENMASK(17, 12) #define TMU_RTR_CS_15_ERROR_AVG_MASK GENMASK(23, 18) +#define TMU_RTR_CS_18 0x12 +#define TMU_RTR_CS_18_DELTA_AVG_CONST_MASK GENMASK(23, 16) #define TMU_RTR_CS_22 0x16 #define TMU_RTR_CS_24 0x18 #define TMU_RTR_CS_25 0x19 @@ -319,6 +324,14 @@ struct tb_regs_port_header { #define TMU_ADP_CS_3_UDM BIT(29) #define TMU_ADP_CS_6 0x06 #define TMU_ADP_CS_6_DTS BIT(1) +#define TMU_ADP_CS_8 0x08 +#define TMU_ADP_CS_8_REPL_TIMEOUT_MASK GENMASK(14, 0) +#define TMU_ADP_CS_8_EUDM BIT(15) +#define TMU_ADP_CS_8_REPL_THRESHOLD_MASK GENMASK(25, 16) +#define TMU_ADP_CS_9 0x09 +#define TMU_ADP_CS_9_REPL_N_MASK GENMASK(7, 0) +#define TMU_ADP_CS_9_DIRSWITCH_N_MASK GENMASK(15, 8) +#define TMU_ADP_CS_9_ADP_TS_INTERVAL_MASK GENMASK(31, 16) /* Lane adapter registers */ #define LANE_ADP_CS_0 0x00 @@ -346,6 +359,7 @@ struct tb_regs_port_header { #define LANE_ADP_CS_1_CURRENT_SPEED_SHIFT 16 #define LANE_ADP_CS_1_CURRENT_SPEED_GEN2 0x8 #define LANE_ADP_CS_1_CURRENT_SPEED_GEN3 0x4 +#define LANE_ADP_CS_1_CURRENT_SPEED_GEN4 0x2 #define LANE_ADP_CS_1_CURRENT_WIDTH_MASK GENMASK(25, 20) #define LANE_ADP_CS_1_CURRENT_WIDTH_SHIFT 20 #define LANE_ADP_CS_1_PMS BIT(30) @@ -436,6 +450,9 @@ struct tb_regs_port_header { #define DP_COMMON_CAP_1_LANE 0x0 #define DP_COMMON_CAP_2_LANES 0x1 #define DP_COMMON_CAP_4_LANES 0x2 +#define DP_COMMON_CAP_UHBR10 BIT(17) +#define DP_COMMON_CAP_UHBR20 BIT(18) +#define DP_COMMON_CAP_UHBR13_5 BIT(19) #define DP_COMMON_CAP_LTTPR_NS BIT(27) #define DP_COMMON_CAP_BW_MODE BIT(28) #define DP_COMMON_CAP_DPRX_DONE BIT(31) @@ -447,6 +464,8 @@ struct tb_regs_port_header { /* PCIe adapter registers */ #define ADP_PCIE_CS_0 0x00 #define ADP_PCIE_CS_0_PE BIT(31) +#define ADP_PCIE_CS_1 0x01 +#define ADP_PCIE_CS_1_EE BIT(0) /* USB adapter registers */ #define ADP_USB3_CS_0 0x00 diff --git a/drivers/thunderbolt/test.c b/drivers/thunderbolt/test.c index 24c06e7354cd..9475c6698c7d 100644 --- a/drivers/thunderbolt/test.c +++ b/drivers/thunderbolt/test.c @@ -170,6 +170,23 @@ static struct tb_switch *alloc_host_usb4(struct kunit *test) return sw; } +static struct tb_switch *alloc_host_br(struct kunit *test) +{ + struct tb_switch *sw; + + sw = alloc_host_usb4(test); + if (!sw) + return NULL; + + sw->ports[10].config.type = TB_TYPE_DP_HDMI_IN; + sw->ports[10].config.max_in_hop_id = 9; + sw->ports[10].config.max_out_hop_id = 9; + sw->ports[10].cap_adap = -1; + sw->ports[10].disabled = false; + + return sw; +} + static struct tb_switch *alloc_dev_default(struct kunit *test, struct tb_switch *parent, u64 route, bool bonded) @@ -1583,6 +1600,71 @@ static void tb_test_tunnel_dp_max_length(struct kunit *test) tb_tunnel_free(tunnel); } +static void tb_test_tunnel_3dp(struct kunit *test) +{ + struct tb_switch *host, *dev1, *dev2, *dev3, *dev4, *dev5; + struct tb_port *in1, *in2, *in3, *out1, *out2, *out3; + struct tb_tunnel *tunnel1, *tunnel2, *tunnel3; + + /* + * Create 3 DP tunnels from Host to Devices #2, #5 and #4. + * + * [Host] + * 3 | + * 1 | + * [Device #1] + * 3 / | 5 \ 7 + * 1 / | \ 1 + * [Device #2] | [Device #4] + * | 1 + * [Device #3] + * | 5 + * | 1 + * [Device #5] + */ + host = alloc_host_br(test); + dev1 = alloc_dev_default(test, host, 0x3, true); + dev2 = alloc_dev_default(test, dev1, 0x303, true); + dev3 = alloc_dev_default(test, dev1, 0x503, true); + dev4 = alloc_dev_default(test, dev1, 0x703, true); + dev5 = alloc_dev_default(test, dev3, 0x50503, true); + + in1 = &host->ports[5]; + in2 = &host->ports[6]; + in3 = &host->ports[10]; + + out1 = &dev2->ports[13]; + out2 = &dev5->ports[13]; + out3 = &dev4->ports[14]; + + tunnel1 = tb_tunnel_alloc_dp(NULL, in1, out1, 1, 0, 0); + KUNIT_ASSERT_TRUE(test, tunnel1 != NULL); + KUNIT_EXPECT_EQ(test, tunnel1->type, TB_TUNNEL_DP); + KUNIT_EXPECT_PTR_EQ(test, tunnel1->src_port, in1); + KUNIT_EXPECT_PTR_EQ(test, tunnel1->dst_port, out1); + KUNIT_ASSERT_EQ(test, tunnel1->npaths, 3); + KUNIT_ASSERT_EQ(test, tunnel1->paths[0]->path_length, 3); + + tunnel2 = tb_tunnel_alloc_dp(NULL, in2, out2, 1, 0, 0); + KUNIT_ASSERT_TRUE(test, tunnel2 != NULL); + KUNIT_EXPECT_EQ(test, tunnel2->type, TB_TUNNEL_DP); + KUNIT_EXPECT_PTR_EQ(test, tunnel2->src_port, in2); + KUNIT_EXPECT_PTR_EQ(test, tunnel2->dst_port, out2); + KUNIT_ASSERT_EQ(test, tunnel2->npaths, 3); + KUNIT_ASSERT_EQ(test, tunnel2->paths[0]->path_length, 4); + + tunnel3 = tb_tunnel_alloc_dp(NULL, in3, out3, 1, 0, 0); + KUNIT_ASSERT_TRUE(test, tunnel3 != NULL); + KUNIT_EXPECT_EQ(test, tunnel3->type, TB_TUNNEL_DP); + KUNIT_EXPECT_PTR_EQ(test, tunnel3->src_port, in3); + KUNIT_EXPECT_PTR_EQ(test, tunnel3->dst_port, out3); + KUNIT_ASSERT_EQ(test, tunnel3->npaths, 3); + KUNIT_ASSERT_EQ(test, tunnel3->paths[0]->path_length, 3); + + tb_tunnel_free(tunnel2); + tb_tunnel_free(tunnel1); +} + static void tb_test_tunnel_usb3(struct kunit *test) { struct tb_switch *host, *dev1, *dev2; @@ -2790,6 +2872,7 @@ static struct kunit_case tb_test_cases[] = { KUNIT_CASE(tb_test_tunnel_dp_chain), KUNIT_CASE(tb_test_tunnel_dp_tree), KUNIT_CASE(tb_test_tunnel_dp_max_length), + KUNIT_CASE(tb_test_tunnel_3dp), KUNIT_CASE(tb_test_tunnel_port_on_path), KUNIT_CASE(tb_test_tunnel_usb3), KUNIT_CASE(tb_test_tunnel_dma), diff --git a/drivers/thunderbolt/tmu.c b/drivers/thunderbolt/tmu.c index 626aca3124b1..1269f417515b 100644 --- a/drivers/thunderbolt/tmu.c +++ b/drivers/thunderbolt/tmu.c @@ -11,23 +11,63 @@ #include "tb.h" +static const unsigned int tmu_rates[] = { + [TB_SWITCH_TMU_MODE_OFF] = 0, + [TB_SWITCH_TMU_MODE_LOWRES] = 1000, + [TB_SWITCH_TMU_MODE_HIFI_UNI] = 16, + [TB_SWITCH_TMU_MODE_HIFI_BI] = 16, + [TB_SWITCH_TMU_MODE_MEDRES_ENHANCED_UNI] = 16, +}; + +const struct { + unsigned int freq_meas_window; + unsigned int avg_const; + unsigned int delta_avg_const; + unsigned int repl_timeout; + unsigned int repl_threshold; + unsigned int repl_n; + unsigned int dirswitch_n; +} tmu_params[] = { + [TB_SWITCH_TMU_MODE_OFF] = { }, + [TB_SWITCH_TMU_MODE_LOWRES] = { 30, 4, }, + [TB_SWITCH_TMU_MODE_HIFI_UNI] = { 800, 8, }, + [TB_SWITCH_TMU_MODE_HIFI_BI] = { 800, 8, }, + [TB_SWITCH_TMU_MODE_MEDRES_ENHANCED_UNI] = { + 800, 4, 0, 3125, 25, 128, 255, + }, +}; + +static const char *tmu_mode_name(enum tb_switch_tmu_mode mode) +{ + switch (mode) { + case TB_SWITCH_TMU_MODE_OFF: + return "off"; + case TB_SWITCH_TMU_MODE_LOWRES: + return "uni-directional, LowRes"; + case TB_SWITCH_TMU_MODE_HIFI_UNI: + return "uni-directional, HiFi"; + case TB_SWITCH_TMU_MODE_HIFI_BI: + return "bi-directional, HiFi"; + case TB_SWITCH_TMU_MODE_MEDRES_ENHANCED_UNI: + return "enhanced uni-directional, MedRes"; + default: + return "unknown"; + } +} + +static bool tb_switch_tmu_enhanced_is_supported(const struct tb_switch *sw) +{ + return usb4_switch_version(sw) > 1; +} + static int tb_switch_set_tmu_mode_params(struct tb_switch *sw, - enum tb_switch_tmu_rate rate) + enum tb_switch_tmu_mode mode) { - u32 freq_meas_wind[2] = { 30, 800 }; - u32 avg_const[2] = { 4, 8 }; u32 freq, avg, val; int ret; - if (rate == TB_SWITCH_TMU_RATE_NORMAL) { - freq = freq_meas_wind[0]; - avg = avg_const[0]; - } else if (rate == TB_SWITCH_TMU_RATE_HIFI) { - freq = freq_meas_wind[1]; - avg = avg_const[1]; - } else { - return 0; - } + freq = tmu_params[mode].freq_meas_window; + avg = tmu_params[mode].avg_const; ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, sw->tmu.cap + TMU_RTR_CS_0, 1); @@ -56,37 +96,30 @@ static int tb_switch_set_tmu_mode_params(struct tb_switch *sw, FIELD_PREP(TMU_RTR_CS_15_OFFSET_AVG_MASK, avg) | FIELD_PREP(TMU_RTR_CS_15_ERROR_AVG_MASK, avg); - return tb_sw_write(sw, &val, TB_CFG_SWITCH, - sw->tmu.cap + TMU_RTR_CS_15, 1); -} - -static const char *tb_switch_tmu_mode_name(const struct tb_switch *sw) -{ - bool root_switch = !tb_route(sw); + ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, + sw->tmu.cap + TMU_RTR_CS_15, 1); + if (ret) + return ret; - switch (sw->tmu.rate) { - case TB_SWITCH_TMU_RATE_OFF: - return "off"; + if (tb_switch_tmu_enhanced_is_supported(sw)) { + u32 delta_avg = tmu_params[mode].delta_avg_const; - case TB_SWITCH_TMU_RATE_HIFI: - /* Root switch does not have upstream directionality */ - if (root_switch) - return "HiFi"; - if (sw->tmu.unidirectional) - return "uni-directional, HiFi"; - return "bi-directional, HiFi"; + ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, + sw->tmu.cap + TMU_RTR_CS_18, 1); + if (ret) + return ret; - case TB_SWITCH_TMU_RATE_NORMAL: - if (root_switch) - return "normal"; - return "uni-directional, normal"; + val &= ~TMU_RTR_CS_18_DELTA_AVG_CONST_MASK; + val |= FIELD_PREP(TMU_RTR_CS_18_DELTA_AVG_CONST_MASK, delta_avg); - default: - return "unknown"; + ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, + sw->tmu.cap + TMU_RTR_CS_18, 1); } + + return ret; } -static bool tb_switch_tmu_ucap_supported(struct tb_switch *sw) +static bool tb_switch_tmu_ucap_is_supported(struct tb_switch *sw) { int ret; u32 val; @@ -182,6 +215,103 @@ static bool tb_port_tmu_is_unidirectional(struct tb_port *port) return val & TMU_ADP_CS_3_UDM; } +static bool tb_port_tmu_is_enhanced(struct tb_port *port) +{ + int ret; + u32 val; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_tmu + TMU_ADP_CS_8, 1); + if (ret) + return false; + + return val & TMU_ADP_CS_8_EUDM; +} + +/* Can be called to non-v2 lane adapters too */ +static int tb_port_tmu_enhanced_enable(struct tb_port *port, bool enable) +{ + int ret; + u32 val; + + if (!tb_switch_tmu_enhanced_is_supported(port->sw)) + return 0; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_tmu + TMU_ADP_CS_8, 1); + if (ret) + return ret; + + if (enable) + val |= TMU_ADP_CS_8_EUDM; + else + val &= ~TMU_ADP_CS_8_EUDM; + + return tb_port_write(port, &val, TB_CFG_PORT, + port->cap_tmu + TMU_ADP_CS_8, 1); +} + +static int tb_port_set_tmu_mode_params(struct tb_port *port, + enum tb_switch_tmu_mode mode) +{ + u32 repl_timeout, repl_threshold, repl_n, dirswitch_n, val; + int ret; + + repl_timeout = tmu_params[mode].repl_timeout; + repl_threshold = tmu_params[mode].repl_threshold; + repl_n = tmu_params[mode].repl_n; + dirswitch_n = tmu_params[mode].dirswitch_n; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_tmu + TMU_ADP_CS_8, 1); + if (ret) + return ret; + + val &= ~TMU_ADP_CS_8_REPL_TIMEOUT_MASK; + val &= ~TMU_ADP_CS_8_REPL_THRESHOLD_MASK; + val |= FIELD_PREP(TMU_ADP_CS_8_REPL_TIMEOUT_MASK, repl_timeout); + val |= FIELD_PREP(TMU_ADP_CS_8_REPL_THRESHOLD_MASK, repl_threshold); + + ret = tb_port_write(port, &val, TB_CFG_PORT, + port->cap_tmu + TMU_ADP_CS_8, 1); + if (ret) + return ret; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_tmu + TMU_ADP_CS_9, 1); + if (ret) + return ret; + + val &= ~TMU_ADP_CS_9_REPL_N_MASK; + val &= ~TMU_ADP_CS_9_DIRSWITCH_N_MASK; + val |= FIELD_PREP(TMU_ADP_CS_9_REPL_N_MASK, repl_n); + val |= FIELD_PREP(TMU_ADP_CS_9_DIRSWITCH_N_MASK, dirswitch_n); + + return tb_port_write(port, &val, TB_CFG_PORT, + port->cap_tmu + TMU_ADP_CS_9, 1); +} + +/* Can be called to non-v2 lane adapters too */ +static int tb_port_tmu_rate_write(struct tb_port *port, int rate) +{ + int ret; + u32 val; + + if (!tb_switch_tmu_enhanced_is_supported(port->sw)) + return 0; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_tmu + TMU_ADP_CS_9, 1); + if (ret) + return ret; + + val &= ~TMU_ADP_CS_9_ADP_TS_INTERVAL_MASK; + val |= FIELD_PREP(TMU_ADP_CS_9_ADP_TS_INTERVAL_MASK, rate); + + return tb_port_write(port, &val, TB_CFG_PORT, + port->cap_tmu + TMU_ADP_CS_9, 1); +} + static int tb_port_tmu_time_sync(struct tb_port *port, bool time_sync) { u32 val = time_sync ? TMU_ADP_CS_6_DTS : 0; @@ -224,6 +354,50 @@ static int tb_switch_tmu_set_time_disruption(struct tb_switch *sw, bool set) return tb_sw_write(sw, &val, TB_CFG_SWITCH, offset, 1); } +static int tmu_mode_init(struct tb_switch *sw) +{ + bool enhanced, ucap; + int ret, rate; + + ucap = tb_switch_tmu_ucap_is_supported(sw); + if (ucap) + tb_sw_dbg(sw, "TMU: supports uni-directional mode\n"); + enhanced = tb_switch_tmu_enhanced_is_supported(sw); + if (enhanced) + tb_sw_dbg(sw, "TMU: supports enhanced uni-directional mode\n"); + + ret = tb_switch_tmu_rate_read(sw); + if (ret < 0) + return ret; + rate = ret; + + /* Off by default */ + sw->tmu.mode = TB_SWITCH_TMU_MODE_OFF; + + if (tb_route(sw)) { + struct tb_port *up = tb_upstream_port(sw); + + if (enhanced && tb_port_tmu_is_enhanced(up)) { + sw->tmu.mode = TB_SWITCH_TMU_MODE_MEDRES_ENHANCED_UNI; + } else if (ucap && tb_port_tmu_is_unidirectional(up)) { + if (tmu_rates[TB_SWITCH_TMU_MODE_LOWRES] == rate) + sw->tmu.mode = TB_SWITCH_TMU_MODE_LOWRES; + else if (tmu_rates[TB_SWITCH_TMU_MODE_LOWRES] == rate) + sw->tmu.mode = TB_SWITCH_TMU_MODE_HIFI_UNI; + } else if (rate) { + sw->tmu.mode = TB_SWITCH_TMU_MODE_HIFI_BI; + } + } else if (rate) { + sw->tmu.mode = TB_SWITCH_TMU_MODE_HIFI_BI; + } + + /* Update the initial request to match the current mode */ + sw->tmu.mode_request = sw->tmu.mode; + sw->tmu.has_ucap = ucap; + + return 0; +} + /** * tb_switch_tmu_init() - Initialize switch TMU structures * @sw: Switch to initialized @@ -252,27 +426,11 @@ int tb_switch_tmu_init(struct tb_switch *sw) port->cap_tmu = cap; } - ret = tb_switch_tmu_rate_read(sw); - if (ret < 0) + ret = tmu_mode_init(sw); + if (ret) return ret; - sw->tmu.rate = ret; - - sw->tmu.has_ucap = tb_switch_tmu_ucap_supported(sw); - if (sw->tmu.has_ucap) { - tb_sw_dbg(sw, "TMU: supports uni-directional mode\n"); - - if (tb_route(sw)) { - struct tb_port *up = tb_upstream_port(sw); - - sw->tmu.unidirectional = - tb_port_tmu_is_unidirectional(up); - } - } else { - sw->tmu.unidirectional = false; - } - - tb_sw_dbg(sw, "TMU: current mode: %s\n", tb_switch_tmu_mode_name(sw)); + tb_sw_dbg(sw, "TMU: current mode: %s\n", tmu_mode_name(sw->tmu.mode)); return 0; } @@ -308,7 +466,7 @@ int tb_switch_tmu_post_time(struct tb_switch *sw) return ret; for (i = 0; i < ARRAY_SIZE(gm_local_time); i++) - tb_sw_dbg(root_switch, "local_time[%d]=0x%08x\n", i, + tb_sw_dbg(root_switch, "TMU: local_time[%d]=0x%08x\n", i, gm_local_time[i]); /* Convert to nanoseconds (drop fractional part) */ @@ -375,6 +533,23 @@ out: return ret; } +static int disable_enhanced(struct tb_port *up, struct tb_port *down) +{ + int ret; + + /* + * Router may already been disconnected so ignore errors on the + * upstream port. + */ + tb_port_tmu_rate_write(up, 0); + tb_port_tmu_enhanced_enable(up, false); + + ret = tb_port_tmu_rate_write(down, 0); + if (ret) + return ret; + return tb_port_tmu_enhanced_enable(down, false); +} + /** * tb_switch_tmu_disable() - Disable TMU of a switch * @sw: Switch whose TMU to disable @@ -383,26 +558,15 @@ out: */ int tb_switch_tmu_disable(struct tb_switch *sw) { - /* - * No need to disable TMU on devices that don't support CLx since - * on these devices e.g. Alpine Ridge and earlier, the TMU mode - * HiFi bi-directional is enabled by default and we don't change it. - */ - if (!tb_switch_is_clx_supported(sw)) - return 0; - /* Already disabled? */ - if (sw->tmu.rate == TB_SWITCH_TMU_RATE_OFF) + if (sw->tmu.mode == TB_SWITCH_TMU_MODE_OFF) return 0; - if (tb_route(sw)) { - bool unidirectional = sw->tmu.unidirectional; - struct tb_switch *parent = tb_switch_parent(sw); struct tb_port *down, *up; int ret; - down = tb_port_at(tb_route(sw), parent); + down = tb_switch_downstream_port(sw); up = tb_upstream_port(sw); /* * In case of uni-directional time sync, TMU handshake is @@ -415,37 +579,49 @@ int tb_switch_tmu_disable(struct tb_switch *sw) * uni-directional mode and we don't want to change it's TMU * mode. */ - tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_OFF); + tb_switch_tmu_rate_write(sw, tmu_rates[TB_SWITCH_TMU_MODE_OFF]); tb_port_tmu_time_sync_disable(up); ret = tb_port_tmu_time_sync_disable(down); if (ret) return ret; - if (unidirectional) { + switch (sw->tmu.mode) { + case TB_SWITCH_TMU_MODE_LOWRES: + case TB_SWITCH_TMU_MODE_HIFI_UNI: /* The switch may be unplugged so ignore any errors */ tb_port_tmu_unidirectional_disable(up); ret = tb_port_tmu_unidirectional_disable(down); if (ret) return ret; + break; + + case TB_SWITCH_TMU_MODE_MEDRES_ENHANCED_UNI: + ret = disable_enhanced(up, down); + if (ret) + return ret; + break; + + default: + break; } } else { - tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_OFF); + tb_switch_tmu_rate_write(sw, tmu_rates[TB_SWITCH_TMU_MODE_OFF]); } - sw->tmu.unidirectional = false; - sw->tmu.rate = TB_SWITCH_TMU_RATE_OFF; + sw->tmu.mode = TB_SWITCH_TMU_MODE_OFF; tb_sw_dbg(sw, "TMU: disabled\n"); return 0; } -static void __tb_switch_tmu_off(struct tb_switch *sw, bool unidirectional) +/* Called only when there is failure enabling requested mode */ +static void tb_switch_tmu_off(struct tb_switch *sw) { - struct tb_switch *parent = tb_switch_parent(sw); + unsigned int rate = tmu_rates[TB_SWITCH_TMU_MODE_OFF]; struct tb_port *down, *up; - down = tb_port_at(tb_route(sw), parent); + down = tb_switch_downstream_port(sw); up = tb_upstream_port(sw); /* * In case of any failure in one of the steps when setting @@ -456,28 +632,38 @@ static void __tb_switch_tmu_off(struct tb_switch *sw, bool unidirectional) */ tb_port_tmu_time_sync_disable(down); tb_port_tmu_time_sync_disable(up); - if (unidirectional) - tb_switch_tmu_rate_write(parent, TB_SWITCH_TMU_RATE_OFF); - else - tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_OFF); - tb_switch_set_tmu_mode_params(sw, sw->tmu.rate); + switch (sw->tmu.mode_request) { + case TB_SWITCH_TMU_MODE_LOWRES: + case TB_SWITCH_TMU_MODE_HIFI_UNI: + tb_switch_tmu_rate_write(tb_switch_parent(sw), rate); + break; + case TB_SWITCH_TMU_MODE_MEDRES_ENHANCED_UNI: + disable_enhanced(up, down); + break; + default: + break; + } + + /* Always set the rate to 0 */ + tb_switch_tmu_rate_write(sw, rate); + + tb_switch_set_tmu_mode_params(sw, sw->tmu.mode); tb_port_tmu_unidirectional_disable(down); tb_port_tmu_unidirectional_disable(up); } /* * This function is called when the previous TMU mode was - * TB_SWITCH_TMU_RATE_OFF. + * TB_SWITCH_TMU_MODE_OFF. */ -static int __tb_switch_tmu_enable_bidirectional(struct tb_switch *sw) +static int tb_switch_tmu_enable_bidirectional(struct tb_switch *sw) { - struct tb_switch *parent = tb_switch_parent(sw); struct tb_port *up, *down; int ret; up = tb_upstream_port(sw); - down = tb_port_at(tb_route(sw), parent); + down = tb_switch_downstream_port(sw); ret = tb_port_tmu_unidirectional_disable(up); if (ret) @@ -487,7 +673,7 @@ static int __tb_switch_tmu_enable_bidirectional(struct tb_switch *sw) if (ret) goto out; - ret = tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_HIFI); + ret = tb_switch_tmu_rate_write(sw, tmu_rates[TB_SWITCH_TMU_MODE_HIFI_BI]); if (ret) goto out; @@ -502,12 +688,14 @@ static int __tb_switch_tmu_enable_bidirectional(struct tb_switch *sw) return 0; out: - __tb_switch_tmu_off(sw, false); + tb_switch_tmu_off(sw); return ret; } -static int tb_switch_tmu_objection_mask(struct tb_switch *sw) +/* Only needed for Titan Ridge */ +static int tb_switch_tmu_disable_objections(struct tb_switch *sw) { + struct tb_port *up = tb_upstream_port(sw); u32 val; int ret; @@ -518,36 +706,34 @@ static int tb_switch_tmu_objection_mask(struct tb_switch *sw) val &= ~TB_TIME_VSEC_3_CS_9_TMU_OBJ_MASK; - return tb_sw_write(sw, &val, TB_CFG_SWITCH, - sw->cap_vsec_tmu + TB_TIME_VSEC_3_CS_9, 1); -} - -static int tb_switch_tmu_unidirectional_enable(struct tb_switch *sw) -{ - struct tb_port *up = tb_upstream_port(sw); + ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, + sw->cap_vsec_tmu + TB_TIME_VSEC_3_CS_9, 1); + if (ret) + return ret; return tb_port_tmu_write(up, TMU_ADP_CS_6, TMU_ADP_CS_6_DISABLE_TMU_OBJ_MASK, - TMU_ADP_CS_6_DISABLE_TMU_OBJ_MASK); + TMU_ADP_CS_6_DISABLE_TMU_OBJ_CL1 | + TMU_ADP_CS_6_DISABLE_TMU_OBJ_CL2); } /* * This function is called when the previous TMU mode was - * TB_SWITCH_TMU_RATE_OFF. + * TB_SWITCH_TMU_MODE_OFF. */ -static int __tb_switch_tmu_enable_unidirectional(struct tb_switch *sw) +static int tb_switch_tmu_enable_unidirectional(struct tb_switch *sw) { - struct tb_switch *parent = tb_switch_parent(sw); struct tb_port *up, *down; int ret; up = tb_upstream_port(sw); - down = tb_port_at(tb_route(sw), parent); - ret = tb_switch_tmu_rate_write(parent, sw->tmu.rate_request); + down = tb_switch_downstream_port(sw); + ret = tb_switch_tmu_rate_write(tb_switch_parent(sw), + tmu_rates[sw->tmu.mode_request]); if (ret) return ret; - ret = tb_switch_set_tmu_mode_params(sw, sw->tmu.rate_request); + ret = tb_switch_set_tmu_mode_params(sw, sw->tmu.mode_request); if (ret) return ret; @@ -570,16 +756,65 @@ static int __tb_switch_tmu_enable_unidirectional(struct tb_switch *sw) return 0; out: - __tb_switch_tmu_off(sw, true); + tb_switch_tmu_off(sw); + return ret; +} + +/* + * This function is called when the previous TMU mode was + * TB_SWITCH_TMU_RATE_OFF. + */ +static int tb_switch_tmu_enable_enhanced(struct tb_switch *sw) +{ + unsigned int rate = tmu_rates[sw->tmu.mode_request]; + struct tb_port *up, *down; + int ret; + + /* Router specific parameters first */ + ret = tb_switch_set_tmu_mode_params(sw, sw->tmu.mode_request); + if (ret) + return ret; + + up = tb_upstream_port(sw); + down = tb_switch_downstream_port(sw); + + ret = tb_port_set_tmu_mode_params(up, sw->tmu.mode_request); + if (ret) + goto out; + + ret = tb_port_tmu_rate_write(up, rate); + if (ret) + goto out; + + ret = tb_port_tmu_enhanced_enable(up, true); + if (ret) + goto out; + + ret = tb_port_set_tmu_mode_params(down, sw->tmu.mode_request); + if (ret) + goto out; + + ret = tb_port_tmu_rate_write(down, rate); + if (ret) + goto out; + + ret = tb_port_tmu_enhanced_enable(down, true); + if (ret) + goto out; + + return 0; + +out: + tb_switch_tmu_off(sw); return ret; } -static void __tb_switch_tmu_change_mode_prev(struct tb_switch *sw) +static void tb_switch_tmu_change_mode_prev(struct tb_switch *sw) { - struct tb_switch *parent = tb_switch_parent(sw); + unsigned int rate = tmu_rates[sw->tmu.mode]; struct tb_port *down, *up; - down = tb_port_at(tb_route(sw), parent); + down = tb_switch_downstream_port(sw); up = tb_upstream_port(sw); /* * In case of any failure in one of the steps when change mode, @@ -587,42 +822,97 @@ static void __tb_switch_tmu_change_mode_prev(struct tb_switch *sw) * In case of additional failures in the functions below, * ignore them since the caller shall already report a failure. */ - tb_port_tmu_set_unidirectional(down, sw->tmu.unidirectional); - if (sw->tmu.unidirectional_request) - tb_switch_tmu_rate_write(parent, sw->tmu.rate); - else - tb_switch_tmu_rate_write(sw, sw->tmu.rate); + switch (sw->tmu.mode) { + case TB_SWITCH_TMU_MODE_LOWRES: + case TB_SWITCH_TMU_MODE_HIFI_UNI: + tb_port_tmu_set_unidirectional(down, true); + tb_switch_tmu_rate_write(tb_switch_parent(sw), rate); + break; + + case TB_SWITCH_TMU_MODE_HIFI_BI: + tb_port_tmu_set_unidirectional(down, false); + tb_switch_tmu_rate_write(sw, rate); + break; + + default: + break; + } + + tb_switch_set_tmu_mode_params(sw, sw->tmu.mode); + + switch (sw->tmu.mode) { + case TB_SWITCH_TMU_MODE_LOWRES: + case TB_SWITCH_TMU_MODE_HIFI_UNI: + tb_port_tmu_set_unidirectional(up, true); + break; + + case TB_SWITCH_TMU_MODE_HIFI_BI: + tb_port_tmu_set_unidirectional(up, false); + break; - tb_switch_set_tmu_mode_params(sw, sw->tmu.rate); - tb_port_tmu_set_unidirectional(up, sw->tmu.unidirectional); + default: + break; + } } -static int __tb_switch_tmu_change_mode(struct tb_switch *sw) +static int tb_switch_tmu_change_mode(struct tb_switch *sw) { - struct tb_switch *parent = tb_switch_parent(sw); + unsigned int rate = tmu_rates[sw->tmu.mode_request]; struct tb_port *up, *down; int ret; up = tb_upstream_port(sw); - down = tb_port_at(tb_route(sw), parent); - ret = tb_port_tmu_set_unidirectional(down, sw->tmu.unidirectional_request); - if (ret) - goto out; + down = tb_switch_downstream_port(sw); - if (sw->tmu.unidirectional_request) - ret = tb_switch_tmu_rate_write(parent, sw->tmu.rate_request); - else - ret = tb_switch_tmu_rate_write(sw, sw->tmu.rate_request); - if (ret) - return ret; + /* Program the upstream router downstream facing lane adapter */ + switch (sw->tmu.mode_request) { + case TB_SWITCH_TMU_MODE_LOWRES: + case TB_SWITCH_TMU_MODE_HIFI_UNI: + ret = tb_port_tmu_set_unidirectional(down, true); + if (ret) + goto out; + ret = tb_switch_tmu_rate_write(tb_switch_parent(sw), rate); + if (ret) + goto out; + break; - ret = tb_switch_set_tmu_mode_params(sw, sw->tmu.rate_request); + case TB_SWITCH_TMU_MODE_HIFI_BI: + ret = tb_port_tmu_set_unidirectional(down, false); + if (ret) + goto out; + ret = tb_switch_tmu_rate_write(sw, rate); + if (ret) + goto out; + break; + + default: + /* Not allowed to change modes from other than above */ + return -EINVAL; + } + + ret = tb_switch_set_tmu_mode_params(sw, sw->tmu.mode_request); if (ret) return ret; - ret = tb_port_tmu_set_unidirectional(up, sw->tmu.unidirectional_request); - if (ret) - goto out; + /* Program the new mode and the downstream router lane adapter */ + switch (sw->tmu.mode_request) { + case TB_SWITCH_TMU_MODE_LOWRES: + case TB_SWITCH_TMU_MODE_HIFI_UNI: + ret = tb_port_tmu_set_unidirectional(up, true); + if (ret) + goto out; + break; + + case TB_SWITCH_TMU_MODE_HIFI_BI: + ret = tb_port_tmu_set_unidirectional(up, false); + if (ret) + goto out; + break; + + default: + /* Not allowed to change modes from other than above */ + return -EINVAL; + } ret = tb_port_tmu_time_sync_enable(down); if (ret) @@ -635,7 +925,7 @@ static int __tb_switch_tmu_change_mode(struct tb_switch *sw) return 0; out: - __tb_switch_tmu_change_mode_prev(sw); + tb_switch_tmu_change_mode_prev(sw); return ret; } @@ -643,45 +933,21 @@ out: * tb_switch_tmu_enable() - Enable TMU on a router * @sw: Router whose TMU to enable * - * Enables TMU of a router to be in uni-directional Normal/HiFi - * or bi-directional HiFi mode. Calling tb_switch_tmu_configure() is required - * before calling this function, to select the mode Normal/HiFi and - * directionality (uni-directional/bi-directional). - * In HiFi mode all tunneling should work. In Normal mode, DP tunneling can't - * work. Uni-directional mode is required for CLx (Link Low-Power) to work. + * Enables TMU of a router to be in uni-directional Normal/HiFi or + * bi-directional HiFi mode. Calling tb_switch_tmu_configure() is + * required before calling this function. */ int tb_switch_tmu_enable(struct tb_switch *sw) { - bool unidirectional = sw->tmu.unidirectional_request; int ret; - if (unidirectional && !sw->tmu.has_ucap) - return -EOPNOTSUPP; - - /* - * No need to enable TMU on devices that don't support CLx since on - * these devices e.g. Alpine Ridge and earlier, the TMU mode HiFi - * bi-directional is enabled by default. - */ - if (!tb_switch_is_clx_supported(sw)) - return 0; - - if (tb_switch_tmu_is_enabled(sw, sw->tmu.unidirectional_request)) + if (tb_switch_tmu_is_enabled(sw)) return 0; - if (tb_switch_is_titan_ridge(sw) && unidirectional) { - /* - * Titan Ridge supports CL0s and CL1 only. CL0s and CL1 are - * enabled and supported together. - */ - if (!tb_switch_is_clx_enabled(sw, TB_CL1)) - return -EOPNOTSUPP; - - ret = tb_switch_tmu_objection_mask(sw); - if (ret) - return ret; - - ret = tb_switch_tmu_unidirectional_enable(sw); + if (tb_switch_is_titan_ridge(sw) && + (sw->tmu.mode_request == TB_SWITCH_TMU_MODE_LOWRES || + sw->tmu.mode_request == TB_SWITCH_TMU_MODE_HIFI_UNI)) { + ret = tb_switch_tmu_disable_objections(sw); if (ret) return ret; } @@ -696,19 +962,30 @@ int tb_switch_tmu_enable(struct tb_switch *sw) * HiFi-Uni/HiFi-BiDir/Normal-Uni or from Normal-Uni to * HiFi-Uni. */ - if (sw->tmu.rate == TB_SWITCH_TMU_RATE_OFF) { - if (unidirectional) - ret = __tb_switch_tmu_enable_unidirectional(sw); - else - ret = __tb_switch_tmu_enable_bidirectional(sw); - if (ret) - return ret; - } else if (sw->tmu.rate == TB_SWITCH_TMU_RATE_NORMAL) { - ret = __tb_switch_tmu_change_mode(sw); - if (ret) - return ret; + if (sw->tmu.mode == TB_SWITCH_TMU_MODE_OFF) { + switch (sw->tmu.mode_request) { + case TB_SWITCH_TMU_MODE_LOWRES: + case TB_SWITCH_TMU_MODE_HIFI_UNI: + ret = tb_switch_tmu_enable_unidirectional(sw); + break; + + case TB_SWITCH_TMU_MODE_HIFI_BI: + ret = tb_switch_tmu_enable_bidirectional(sw); + break; + case TB_SWITCH_TMU_MODE_MEDRES_ENHANCED_UNI: + ret = tb_switch_tmu_enable_enhanced(sw); + break; + default: + ret = -EINVAL; + break; + } + } else if (sw->tmu.mode == TB_SWITCH_TMU_MODE_LOWRES || + sw->tmu.mode == TB_SWITCH_TMU_MODE_HIFI_UNI || + sw->tmu.mode == TB_SWITCH_TMU_MODE_HIFI_BI) { + ret = tb_switch_tmu_change_mode(sw); + } else { + ret = -EINVAL; } - sw->tmu.unidirectional = unidirectional; } else { /* * Host router port configurations are written as @@ -716,58 +993,68 @@ int tb_switch_tmu_enable(struct tb_switch *sw) * of the child node - see above. * Here only the host router' rate configuration is written. */ - ret = tb_switch_tmu_rate_write(sw, sw->tmu.rate_request); - if (ret) - return ret; + ret = tb_switch_tmu_rate_write(sw, tmu_rates[sw->tmu.mode_request]); } - sw->tmu.rate = sw->tmu.rate_request; + if (ret) { + tb_sw_warn(sw, "TMU: failed to enable mode %s: %d\n", + tmu_mode_name(sw->tmu.mode_request), ret); + } else { + sw->tmu.mode = sw->tmu.mode_request; + tb_sw_dbg(sw, "TMU: mode set to: %s\n", tmu_mode_name(sw->tmu.mode)); + } - tb_sw_dbg(sw, "TMU: mode set to: %s\n", tb_switch_tmu_mode_name(sw)); return tb_switch_tmu_set_time_disruption(sw, false); } /** - * tb_switch_tmu_configure() - Configure the TMU rate and directionality + * tb_switch_tmu_configure() - Configure the TMU mode * @sw: Router whose mode to change - * @rate: Rate to configure Off/Normal/HiFi - * @unidirectional: If uni-directional (bi-directional otherwise) + * @mode: Mode to configure * - * Selects the rate of the TMU and directionality (uni-directional or - * bi-directional). Must be called before tb_switch_tmu_enable(). + * Selects the TMU mode that is enabled when tb_switch_tmu_enable() is + * next called. + * + * Returns %0 in success and negative errno otherwise. Specifically + * returns %-EOPNOTSUPP if the requested mode is not possible (not + * supported by the router and/or topology). */ -void tb_switch_tmu_configure(struct tb_switch *sw, - enum tb_switch_tmu_rate rate, bool unidirectional) +int tb_switch_tmu_configure(struct tb_switch *sw, enum tb_switch_tmu_mode mode) { - sw->tmu.unidirectional_request = unidirectional; - sw->tmu.rate_request = rate; -} + switch (mode) { + case TB_SWITCH_TMU_MODE_OFF: + break; -static int tb_switch_tmu_config_enable(struct device *dev, void *rate) -{ - if (tb_is_switch(dev)) { - struct tb_switch *sw = tb_to_switch(dev); + case TB_SWITCH_TMU_MODE_LOWRES: + case TB_SWITCH_TMU_MODE_HIFI_UNI: + if (!sw->tmu.has_ucap) + return -EOPNOTSUPP; + break; - tb_switch_tmu_configure(sw, *(enum tb_switch_tmu_rate *)rate, - tb_switch_is_clx_enabled(sw, TB_CL1)); - if (tb_switch_tmu_enable(sw)) - tb_sw_dbg(sw, "fail switching TMU mode for 1st depth router\n"); + case TB_SWITCH_TMU_MODE_HIFI_BI: + break; + + case TB_SWITCH_TMU_MODE_MEDRES_ENHANCED_UNI: { + const struct tb_switch *parent_sw = tb_switch_parent(sw); + + if (!parent_sw || !tb_switch_tmu_enhanced_is_supported(parent_sw)) + return -EOPNOTSUPP; + if (!tb_switch_tmu_enhanced_is_supported(sw)) + return -EOPNOTSUPP; + + break; } - return 0; -} + default: + tb_sw_warn(sw, "TMU: unsupported mode %u\n", mode); + return -EINVAL; + } -/** - * tb_switch_enable_tmu_1st_child - Configure and enable TMU for 1st chidren - * @sw: The router to configure and enable it's children TMU - * @rate: Rate of the TMU to configure the router's chidren to - * - * Configures and enables the TMU mode of 1st depth children of the specified - * router to the specified rate. - */ -void tb_switch_enable_tmu_1st_child(struct tb_switch *sw, - enum tb_switch_tmu_rate rate) -{ - device_for_each_child(&sw->dev, &rate, - tb_switch_tmu_config_enable); + if (sw->tmu.mode_request != mode) { + tb_sw_dbg(sw, "TMU: mode change %s -> %s requested\n", + tmu_mode_name(sw->tmu.mode), tmu_mode_name(mode)); + sw->tmu.mode_request = mode; + } + + return 0; } diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 4f222673d651..a6810fb36860 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -10,6 +10,7 @@ #include <linux/slab.h> #include <linux/list.h> #include <linux/ktime.h> +#include <linux/string_helpers.h> #include "tunnel.h" #include "tb.h" @@ -41,9 +42,14 @@ * Number of credits we try to allocate for each DMA path if not limited * by the host router baMaxHI. */ -#define TB_DMA_CREDITS 14U +#define TB_DMA_CREDITS 14 /* Minimum number of credits for DMA path */ -#define TB_MIN_DMA_CREDITS 1U +#define TB_MIN_DMA_CREDITS 1 + +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: " + __MODULE_STRING(TB_DMA_CREDITS) ")"); static bool bw_alloc_mode = true; module_param(bw_alloc_mode, bool, 0444); @@ -95,7 +101,7 @@ static unsigned int tb_available_credits(const struct tb_port *port, pcie = tb_acpi_may_tunnel_pcie() ? sw->max_pcie_credits : 0; if (tb_acpi_is_xdomain_allowed()) { - spare = min_not_zero(sw->max_dma_credits, TB_DMA_CREDITS); + spare = min_not_zero(sw->max_dma_credits, dma_credits); /* Add some credits for potential second DMA tunnel */ spare += TB_MIN_DMA_CREDITS; } else { @@ -148,18 +154,49 @@ static struct tb_tunnel *tb_tunnel_alloc(struct tb *tb, size_t npaths, return tunnel; } +static int tb_pci_set_ext_encapsulation(struct tb_tunnel *tunnel, bool enable) +{ + 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) + return 0; + + ret = usb4_pci_port_set_ext_encapsulation(tunnel->src_port, enable); + if (ret) + return ret; + + ret = usb4_pci_port_set_ext_encapsulation(tunnel->dst_port, enable); + if (ret) + return ret; + + tb_tunnel_dbg(tunnel, "extended encapsulation %s\n", + str_enabled_disabled(enable)); + return 0; +} + static int tb_pci_activate(struct tb_tunnel *tunnel, bool activate) { int res; + if (activate) { + res = tb_pci_set_ext_encapsulation(tunnel, activate); + if (res) + return res; + } + res = tb_pci_port_enable(tunnel->src_port, activate); if (res) return res; - if (tb_port_is_pcie_up(tunnel->dst_port)) - return tb_pci_port_enable(tunnel->dst_port, activate); + if (tb_port_is_pcie_up(tunnel->dst_port)) { + res = tb_pci_port_enable(tunnel->dst_port, activate); + if (res) + return res; + } - return 0; + return activate ? 0 : tb_pci_set_ext_encapsulation(tunnel, activate); } static int tb_pci_init_credits(struct tb_path_hop *hop) @@ -381,6 +418,10 @@ static int tb_dp_cm_handshake(struct tb_port *in, struct tb_port *out, return -ETIMEDOUT; } +/* + * Returns maximum possible rate from capability supporting only DP 2.0 + * and below. Used when DP BW allocation mode is not enabled. + */ static inline u32 tb_dp_cap_get_rate(u32 val) { u32 rate = (val & DP_COMMON_CAP_RATE_MASK) >> DP_COMMON_CAP_RATE_SHIFT; @@ -399,6 +440,28 @@ static inline u32 tb_dp_cap_get_rate(u32 val) } } +/* + * Returns maximum possible rate from capability supporting DP 2.1 + * UHBR20, 13.5 and 10 rates as well. Use only when DP BW allocation + * mode is enabled. + */ +static inline u32 tb_dp_cap_get_rate_ext(u32 val) +{ + if (val & DP_COMMON_CAP_UHBR20) + return 20000; + else if (val & DP_COMMON_CAP_UHBR13_5) + return 13500; + else if (val & DP_COMMON_CAP_UHBR10) + return 10000; + + return tb_dp_cap_get_rate(val); +} + +static inline bool tb_dp_is_uhbr_rate(unsigned int rate) +{ + return rate >= 10000; +} + static inline u32 tb_dp_cap_set_rate(u32 val, u32 rate) { val &= ~DP_COMMON_CAP_RATE_MASK; @@ -461,7 +524,9 @@ static inline u32 tb_dp_cap_set_lanes(u32 val, u32 lanes) static unsigned int tb_dp_bandwidth(unsigned int rate, unsigned int lanes) { - /* Tunneling removes the DP 8b/10b encoding */ + /* Tunneling removes the DP 8b/10b 128/132b encoding */ + if (tb_dp_is_uhbr_rate(rate)) + return rate * lanes * 128 / 132; return rate * lanes * 8 / 10; } @@ -604,7 +669,7 @@ static int tb_dp_xchg_caps(struct tb_tunnel *tunnel) in->cap_adap + DP_REMOTE_CAP, 1); } -static int tb_dp_bw_alloc_mode_enable(struct tb_tunnel *tunnel) +static int tb_dp_bandwidth_alloc_mode_enable(struct tb_tunnel *tunnel) { int ret, estimated_bw, granularity, tmp; struct tb_port *out = tunnel->dst_port; @@ -616,7 +681,7 @@ static int tb_dp_bw_alloc_mode_enable(struct tb_tunnel *tunnel) if (!bw_alloc_mode) return 0; - ret = usb4_dp_port_set_cm_bw_mode_supported(in, true); + ret = usb4_dp_port_set_cm_bandwidth_mode_supported(in, true); if (ret) return ret; @@ -654,6 +719,19 @@ static int tb_dp_bw_alloc_mode_enable(struct tb_tunnel *tunnel) if (ret) return ret; + /* + * Pick up granularity that supports maximum possible bandwidth. + * For that we use the UHBR rates too. + */ + in_rate = tb_dp_cap_get_rate_ext(in_dp_cap); + out_rate = tb_dp_cap_get_rate_ext(out_dp_cap); + 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); + for (granularity = 250; tmp / granularity > 255 && granularity <= 1000; granularity *= 2) ; @@ -680,12 +758,12 @@ static int tb_dp_bw_alloc_mode_enable(struct tb_tunnel *tunnel) tb_port_dbg(in, "estimated bandwidth %d Mb/s\n", estimated_bw); - ret = usb4_dp_port_set_estimated_bw(in, estimated_bw); + ret = usb4_dp_port_set_estimated_bandwidth(in, estimated_bw); if (ret) return ret; /* Initial allocation should be 0 according the spec */ - ret = usb4_dp_port_allocate_bw(in, 0); + ret = usb4_dp_port_allocate_bandwidth(in, 0); if (ret) return ret; @@ -707,7 +785,7 @@ static int tb_dp_init(struct tb_tunnel *tunnel) if (!tb_switch_is_usb4(sw)) return 0; - if (!usb4_dp_port_bw_mode_supported(in)) + if (!usb4_dp_port_bandwidth_mode_supported(in)) return 0; tb_port_dbg(in, "bandwidth allocation mode supported\n"); @@ -716,17 +794,17 @@ static int tb_dp_init(struct tb_tunnel *tunnel) if (ret) return ret; - return tb_dp_bw_alloc_mode_enable(tunnel); + return tb_dp_bandwidth_alloc_mode_enable(tunnel); } static void tb_dp_deinit(struct tb_tunnel *tunnel) { struct tb_port *in = tunnel->src_port; - if (!usb4_dp_port_bw_mode_supported(in)) + if (!usb4_dp_port_bandwidth_mode_supported(in)) return; - if (usb4_dp_port_bw_mode_enabled(in)) { - usb4_dp_port_set_cm_bw_mode_supported(in, false); + 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"); } } @@ -769,15 +847,42 @@ static int tb_dp_activate(struct tb_tunnel *tunnel, bool active) } /* max_bw is rounded up to next granularity */ -static int tb_dp_nrd_bandwidth(struct tb_tunnel *tunnel, int *max_bw) +static int tb_dp_bandwidth_mode_maximum_bandwidth(struct tb_tunnel *tunnel, + int *max_bw) { struct tb_port *in = tunnel->src_port; int ret, rate, lanes, nrd_bw; + u32 cap; - ret = usb4_dp_port_nrd(in, &rate, &lanes); + /* + * DP IN adapter DP_LOCAL_CAP gets updated to the lowest AUX + * read parameter values so this so we can use this to determine + * the maximum possible bandwidth over this link. + * + * See USB4 v2 spec 1.0 10.4.4.5. + */ + ret = tb_port_read(in, &cap, TB_CFG_PORT, + in->cap_adap + DP_LOCAL_CAP, 1); if (ret) return ret; + rate = tb_dp_cap_get_rate_ext(cap); + if (tb_dp_is_uhbr_rate(rate)) { + /* + * When UHBR is used there is no reduction in lanes so + * we can use this directly. + */ + lanes = tb_dp_cap_get_lanes(cap); + } else { + /* + * If there is no UHBR supported then check the + * non-reduced rate and lanes. + */ + ret = usb4_dp_port_nrd(in, &rate, &lanes); + if (ret) + return ret; + } + nrd_bw = tb_dp_bandwidth(rate, lanes); if (max_bw) { @@ -790,26 +895,27 @@ static int tb_dp_nrd_bandwidth(struct tb_tunnel *tunnel, int *max_bw) return nrd_bw; } -static int tb_dp_bw_mode_consumed_bandwidth(struct tb_tunnel *tunnel, - int *consumed_up, int *consumed_down) +static int tb_dp_bandwidth_mode_consumed_bandwidth(struct tb_tunnel *tunnel, + int *consumed_up, + int *consumed_down) { struct tb_port *out = tunnel->dst_port; struct tb_port *in = tunnel->src_port; int ret, allocated_bw, max_bw; - if (!usb4_dp_port_bw_mode_enabled(in)) + if (!usb4_dp_port_bandwidth_mode_enabled(in)) return -EOPNOTSUPP; if (!tunnel->bw_mode) return -EOPNOTSUPP; /* Read what was allocated previously if any */ - ret = usb4_dp_port_allocated_bw(in); + ret = usb4_dp_port_allocated_bandwidth(in); if (ret < 0) return ret; allocated_bw = ret; - ret = tb_dp_nrd_bandwidth(tunnel, &max_bw); + ret = tb_dp_bandwidth_mode_maximum_bandwidth(tunnel, &max_bw); if (ret < 0) return ret; if (allocated_bw == max_bw) @@ -839,15 +945,15 @@ static int tb_dp_allocated_bandwidth(struct tb_tunnel *tunnel, int *allocated_up * If we have already set the allocated bandwidth then use that. * Otherwise we read it from the DPRX. */ - if (usb4_dp_port_bw_mode_enabled(in) && tunnel->bw_mode) { + if (usb4_dp_port_bandwidth_mode_enabled(in) && tunnel->bw_mode) { int ret, allocated_bw, max_bw; - ret = usb4_dp_port_allocated_bw(in); + ret = usb4_dp_port_allocated_bandwidth(in); if (ret < 0) return ret; allocated_bw = ret; - ret = tb_dp_nrd_bandwidth(tunnel, &max_bw); + ret = tb_dp_bandwidth_mode_maximum_bandwidth(tunnel, &max_bw); if (ret < 0) return ret; if (allocated_bw == max_bw) @@ -874,23 +980,23 @@ static int tb_dp_alloc_bandwidth(struct tb_tunnel *tunnel, int *alloc_up, struct tb_port *in = tunnel->src_port; int max_bw, ret, tmp; - if (!usb4_dp_port_bw_mode_enabled(in)) + if (!usb4_dp_port_bandwidth_mode_enabled(in)) return -EOPNOTSUPP; - ret = tb_dp_nrd_bandwidth(tunnel, &max_bw); + ret = tb_dp_bandwidth_mode_maximum_bandwidth(tunnel, &max_bw); if (ret < 0) return ret; if (in->sw->config.depth < out->sw->config.depth) { tmp = min(*alloc_down, max_bw); - ret = usb4_dp_port_allocate_bw(in, tmp); + ret = usb4_dp_port_allocate_bandwidth(in, tmp); if (ret) return ret; *alloc_down = tmp; *alloc_up = 0; } else { tmp = min(*alloc_up, max_bw); - ret = usb4_dp_port_allocate_bw(in, tmp); + ret = usb4_dp_port_allocate_bandwidth(in, tmp); if (ret) return ret; *alloc_down = 0; @@ -900,6 +1006,9 @@ 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; } @@ -974,23 +1083,20 @@ static int tb_dp_maximum_bandwidth(struct tb_tunnel *tunnel, int *max_up, int *max_down) { struct tb_port *in = tunnel->src_port; - u32 rate, lanes; int ret; - /* - * DP IN adapter DP_LOCAL_CAP gets updated to the lowest AUX read - * parameter values so this so we can use this to determine the - * maximum possible bandwidth over this link. - */ - ret = tb_dp_read_cap(tunnel, DP_LOCAL_CAP, &rate, &lanes); - if (ret) + if (!usb4_dp_port_bandwidth_mode_enabled(in)) + return -EOPNOTSUPP; + + ret = tb_dp_bandwidth_mode_maximum_bandwidth(tunnel, NULL); + if (ret < 0) return ret; if (in->sw->config.depth < tunnel->dst_port->sw->config.depth) { *max_up = 0; - *max_down = tb_dp_bandwidth(rate, lanes); + *max_down = ret; } else { - *max_up = tb_dp_bandwidth(rate, lanes); + *max_up = ret; *max_down = 0; } @@ -1011,8 +1117,8 @@ static int tb_dp_consumed_bandwidth(struct tb_tunnel *tunnel, int *consumed_up, * mode is enabled first and then read the bandwidth * through those registers. */ - ret = tb_dp_bw_mode_consumed_bandwidth(tunnel, consumed_up, - consumed_down); + ret = tb_dp_bandwidth_mode_consumed_bandwidth(tunnel, consumed_up, + consumed_down); if (ret < 0) { if (ret != -EOPNOTSUPP) return ret; @@ -1132,6 +1238,47 @@ static int tb_dp_init_video_path(struct tb_path *path) return 0; } +static void tb_dp_dump(struct tb_tunnel *tunnel) +{ + struct tb_port *in, *out; + u32 dp_cap, rate, lanes; + + in = tunnel->src_port; + out = tunnel->dst_port; + + if (tb_port_read(in, &dp_cap, TB_CFG_PORT, + in->cap_adap + DP_LOCAL_CAP, 1)) + return; + + 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)); + + out = tunnel->dst_port; + + if (tb_port_read(out, &dp_cap, TB_CFG_PORT, + out->cap_adap + DP_LOCAL_CAP, 1)) + return; + + 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)); + + if (tb_port_read(in, &dp_cap, TB_CFG_PORT, + in->cap_adap + DP_REMOTE_CAP, 1)) + return; + + 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_discover_dp() - Discover existing Display Port tunnels * @tb: Pointer to the domain structure @@ -1209,6 +1356,8 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, goto err_deactivate; } + tb_dp_dump(tunnel); + tb_tunnel_dbg(tunnel, "discovered\n"); return tunnel; @@ -1452,6 +1601,10 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, struct tb_path *path; int credits; + /* Ring 0 is reserved for control channel */ + if (WARN_ON(!receive_ring || !transmit_ring)) + return NULL; + if (receive_ring > 0) npaths++; if (transmit_ring > 0) @@ -1468,7 +1621,7 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, tunnel->dst_port = dst; tunnel->deinit = tb_dma_deinit; - credits = min_not_zero(TB_DMA_CREDITS, nhi->sw->max_dma_credits); + credits = min_not_zero(dma_credits, nhi->sw->max_dma_credits); if (receive_ring > 0) { path = tb_path_alloc(tb, dst, receive_path, nhi, receive_ring, 0, diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 485b6e430686..05ddb224c464 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -15,6 +15,7 @@ #include "tb.h" #define USB4_DATA_RETRIES 3 +#define USB4_DATA_DWORDS 16 enum usb4_sb_target { USB4_SB_TARGET_ROUTER, @@ -112,7 +113,7 @@ static int __usb4_switch_op(struct tb_switch *sw, u16 opcode, u32 *metadata, { const struct tb_cm_ops *cm_ops = sw->tb->cm_ops; - if (tx_dwords > NVM_DATA_DWORDS || rx_dwords > NVM_DATA_DWORDS) + if (tx_dwords > USB4_DATA_DWORDS || rx_dwords > USB4_DATA_DWORDS) return -EINVAL; /* @@ -231,11 +232,14 @@ static bool link_is_usb4(struct tb_port *port) * is not available for some reason (like that there is Thunderbolt 3 * switch upstream) then the internal xHCI controller is enabled * instead. + * + * This does not set the configuration valid bit of the router. To do + * that call usb4_switch_configuration_valid(). */ int usb4_switch_setup(struct tb_switch *sw) { - struct tb_port *downstream_port; - struct tb_switch *parent; + struct tb_switch *parent = tb_switch_parent(sw); + struct tb_port *down; bool tbt3, xhci; u32 val = 0; int ret; @@ -249,9 +253,8 @@ int usb4_switch_setup(struct tb_switch *sw) if (ret) return ret; - parent = tb_switch_parent(sw); - downstream_port = tb_port_at(tb_route(sw), parent); - sw->link_usb4 = link_is_usb4(downstream_port); + down = tb_switch_downstream_port(sw); + sw->link_usb4 = link_is_usb4(down); tb_sw_dbg(sw, "link: %s\n", sw->link_usb4 ? "USB4" : "TBT"); xhci = val & ROUTER_CS_6_HCI; @@ -288,7 +291,33 @@ int usb4_switch_setup(struct tb_switch *sw) /* TBT3 supported by the CM */ val |= ROUTER_CS_5_C3S; - /* Tunneling configuration is ready now */ + + return tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1); +} + +/** + * usb4_switch_configuration_valid() - Set tunneling configuration to be valid + * @sw: USB4 router + * + * Sets configuration valid bit for the router. Must be called before + * any tunnels can be set through the router and after + * usb4_switch_setup() has been called. Can be called to host and device + * routers (does nothing for the latter). + * + * Returns %0 in success and negative errno otherwise. + */ +int usb4_switch_configuration_valid(struct tb_switch *sw) +{ + u32 val; + int ret; + + if (!tb_route(sw)) + return 0; + + ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1); + if (ret) + return ret; + val |= ROUTER_CS_5_CV; ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1); @@ -703,7 +732,7 @@ int usb4_switch_credits_init(struct tb_switch *sw) int max_usb3, min_dp_aux, min_dp_main, max_pcie, max_dma; int ret, length, i, nports; const struct tb_port *port; - u32 data[NVM_DATA_DWORDS]; + u32 data[USB4_DATA_DWORDS]; u32 metadata = 0; u8 status = 0; @@ -1199,7 +1228,7 @@ static int usb4_port_wait_for_bit(struct tb_port *port, u32 offset, u32 bit, static int usb4_port_read_data(struct tb_port *port, void *data, size_t dwords) { - if (dwords > NVM_DATA_DWORDS) + if (dwords > USB4_DATA_DWORDS) return -EINVAL; return tb_port_read(port, data, TB_CFG_PORT, port->cap_usb4 + PORT_CS_2, @@ -1209,7 +1238,7 @@ static int usb4_port_read_data(struct tb_port *port, void *data, size_t dwords) static int usb4_port_write_data(struct tb_port *port, const void *data, size_t dwords) { - if (dwords > NVM_DATA_DWORDS) + if (dwords > USB4_DATA_DWORDS) return -EINVAL; return tb_port_write(port, data, TB_CFG_PORT, port->cap_usb4 + PORT_CS_2, @@ -1845,7 +1874,7 @@ static int usb4_port_retimer_nvm_read_block(void *data, unsigned int dwaddress, int ret; metadata = dwaddress << USB4_NVM_READ_OFFSET_SHIFT; - if (dwords < NVM_DATA_DWORDS) + if (dwords < USB4_DATA_DWORDS) metadata |= dwords << USB4_NVM_READ_LENGTH_SHIFT; ret = usb4_port_retimer_write(port, index, USB4_SB_METADATA, &metadata, @@ -2265,13 +2294,14 @@ int usb4_dp_port_set_cm_id(struct tb_port *port, int cm_id) } /** - * usb4_dp_port_bw_mode_supported() - Is the bandwidth allocation mode supported + * usb4_dp_port_bandwidth_mode_supported() - Is the bandwidth allocation mode + * supported * @port: DP IN adapter to check * * Can be called to any DP IN adapter. Returns true if the adapter * supports USB4 bandwidth allocation mode, false otherwise. */ -bool usb4_dp_port_bw_mode_supported(struct tb_port *port) +bool usb4_dp_port_bandwidth_mode_supported(struct tb_port *port) { int ret; u32 val; @@ -2288,13 +2318,14 @@ bool usb4_dp_port_bw_mode_supported(struct tb_port *port) } /** - * usb4_dp_port_bw_mode_enabled() - Is the bandwidth allocation mode enabled + * usb4_dp_port_bandwidth_mode_enabled() - Is the bandwidth allocation mode + * enabled * @port: DP IN adapter to check * * Can be called to any DP IN adapter. Returns true if the bandwidth * allocation mode has been enabled, false otherwise. */ -bool usb4_dp_port_bw_mode_enabled(struct tb_port *port) +bool usb4_dp_port_bandwidth_mode_enabled(struct tb_port *port) { int ret; u32 val; @@ -2311,7 +2342,8 @@ bool usb4_dp_port_bw_mode_enabled(struct tb_port *port) } /** - * usb4_dp_port_set_cm_bw_mode_supported() - Set/clear CM support for bandwidth allocation mode + * usb4_dp_port_set_cm_bandwidth_mode_supported() - Set/clear CM support for + * bandwidth allocation mode * @port: DP IN adapter * @supported: Does the CM support bandwidth allocation mode * @@ -2320,7 +2352,8 @@ bool usb4_dp_port_bw_mode_enabled(struct tb_port *port) * otherwise. Specifically returns %-OPNOTSUPP if the passed in adapter * does not support this. */ -int usb4_dp_port_set_cm_bw_mode_supported(struct tb_port *port, bool supported) +int usb4_dp_port_set_cm_bandwidth_mode_supported(struct tb_port *port, + bool supported) { u32 val; int ret; @@ -2594,7 +2627,7 @@ int usb4_dp_port_set_granularity(struct tb_port *port, int granularity) } /** - * usb4_dp_port_set_estimated_bw() - Set estimated bandwidth + * usb4_dp_port_set_estimated_bandwidth() - Set estimated bandwidth * @port: DP IN adapter * @bw: Estimated bandwidth in Mb/s. * @@ -2604,7 +2637,7 @@ int usb4_dp_port_set_granularity(struct tb_port *port, int granularity) * and negative errno otherwise. Specifically returns %-EOPNOTSUPP if * the adapter does not support this. */ -int usb4_dp_port_set_estimated_bw(struct tb_port *port, int bw) +int usb4_dp_port_set_estimated_bandwidth(struct tb_port *port, int bw) { u32 val, granularity; int ret; @@ -2630,14 +2663,14 @@ int usb4_dp_port_set_estimated_bw(struct tb_port *port, int bw) } /** - * usb4_dp_port_allocated_bw() - Return allocated bandwidth + * usb4_dp_port_allocated_bandwidth() - Return allocated bandwidth * @port: DP IN adapter * * Reads and returns allocated bandwidth for @port in Mb/s (taking into * account the programmed granularity). Returns negative errno in case * of error. */ -int usb4_dp_port_allocated_bw(struct tb_port *port) +int usb4_dp_port_allocated_bandwidth(struct tb_port *port) { u32 val, granularity; int ret; @@ -2723,7 +2756,7 @@ static int usb4_dp_port_wait_and_clear_cm_ack(struct tb_port *port, } /** - * usb4_dp_port_allocate_bw() - Set allocated bandwidth + * usb4_dp_port_allocate_bandwidth() - Set allocated bandwidth * @port: DP IN adapter * @bw: New allocated bandwidth in Mb/s * @@ -2731,7 +2764,7 @@ static int usb4_dp_port_wait_and_clear_cm_ack(struct tb_port *port, * driver). Takes into account the programmed granularity. Returns %0 in * success and negative errno in case of error. */ -int usb4_dp_port_allocate_bw(struct tb_port *port, int bw) +int usb4_dp_port_allocate_bandwidth(struct tb_port *port, int bw) { u32 val, granularity; int ret; @@ -2765,7 +2798,7 @@ int usb4_dp_port_allocate_bw(struct tb_port *port, int bw) } /** - * usb4_dp_port_requested_bw() - Read requested bandwidth + * usb4_dp_port_requested_bandwidth() - Read requested bandwidth * @port: DP IN adapter * * Reads the DPCD (graphics driver) requested bandwidth and returns it @@ -2774,7 +2807,7 @@ int usb4_dp_port_allocate_bw(struct tb_port *port, int bw) * the adapter does not support bandwidth allocation mode, and %ENODATA * if there is no active bandwidth request from the graphics driver. */ -int usb4_dp_port_requested_bw(struct tb_port *port) +int usb4_dp_port_requested_bandwidth(struct tb_port *port) { u32 val, granularity; int ret; @@ -2797,3 +2830,34 @@ int usb4_dp_port_requested_bw(struct tb_port *port) return (val & ADP_DP_CS_8_REQUESTED_BW_MASK) * granularity; } + +/** + * usb4_pci_port_set_ext_encapsulation() - Enable/disable extended encapsulation + * @port: PCIe adapter + * @enable: Enable/disable extended encapsulation + * + * Enables or disables extended encapsulation used in PCIe tunneling. Caller + * needs to make sure both adapters support this before enabling. Returns %0 on + * success and negative errno otherwise. + */ +int usb4_pci_port_set_ext_encapsulation(struct tb_port *port, bool enable) +{ + u32 val; + int ret; + + if (!tb_port_is_pcie_up(port) && !tb_port_is_pcie_down(port)) + return -EINVAL; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_PCIE_CS_1, 1); + if (ret) + return ret; + + if (enable) + val |= ADP_PCIE_CS_1_EE; + else + val &= ~ADP_PCIE_CS_1_EE; + + return tb_port_write(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_PCIE_CS_1, 1); +} diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index e2b54887d331..5b5566862318 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -537,9 +537,8 @@ static int tb_xdp_link_state_status_request(struct tb_ctl *ctl, u64 route, static int tb_xdp_link_state_status_response(struct tb *tb, struct tb_ctl *ctl, struct tb_xdomain *xd, u8 sequence) { - struct tb_switch *sw = tb_to_switch(xd->dev.parent); struct tb_xdp_link_state_status_response res; - struct tb_port *port = tb_port_at(xd->route, sw); + struct tb_port *port = tb_xdomain_downstream_port(xd); u32 val[2]; int ret; @@ -1137,7 +1136,7 @@ static int tb_xdomain_update_link_attributes(struct tb_xdomain *xd) struct tb_port *port; int ret; - port = tb_port_at(xd->route, tb_xdomain_parent(xd)); + port = tb_xdomain_downstream_port(xd); ret = tb_port_get_link_speed(port); if (ret < 0) @@ -1251,8 +1250,7 @@ static int tb_xdomain_get_link_status(struct tb_xdomain *xd) static int tb_xdomain_link_state_change(struct tb_xdomain *xd, unsigned int width) { - struct tb_switch *sw = tb_to_switch(xd->dev.parent); - struct tb_port *port = tb_port_at(xd->route, sw); + struct tb_port *port = tb_xdomain_downstream_port(xd); struct tb *tb = xd->tb; u8 tlw, tls; u32 val; @@ -1292,13 +1290,16 @@ static int tb_xdomain_link_state_change(struct tb_xdomain *xd, static int tb_xdomain_bond_lanes_uuid_high(struct tb_xdomain *xd) { + unsigned int width, width_mask; struct tb_port *port; - int ret, width; + int ret; if (xd->target_link_width == LANE_ADP_CS_1_TARGET_WIDTH_SINGLE) { - width = 1; + width = TB_LINK_WIDTH_SINGLE; + width_mask = width; } else if (xd->target_link_width == LANE_ADP_CS_1_TARGET_WIDTH_DUAL) { - width = 2; + width = TB_LINK_WIDTH_DUAL; + width_mask = width | TB_LINK_WIDTH_ASYM_TX | TB_LINK_WIDTH_ASYM_RX; } else { if (xd->state_retries-- > 0) { dev_dbg(&xd->dev, @@ -1309,7 +1310,7 @@ static int tb_xdomain_bond_lanes_uuid_high(struct tb_xdomain *xd) return -ETIMEDOUT; } - port = tb_port_at(xd->route, tb_xdomain_parent(xd)); + port = tb_xdomain_downstream_port(xd); /* * We can't use tb_xdomain_lane_bonding_enable() here because it @@ -1330,15 +1331,16 @@ static int tb_xdomain_bond_lanes_uuid_high(struct tb_xdomain *xd) return ret; } - ret = tb_port_wait_for_link_width(port, width, XDOMAIN_BONDING_TIMEOUT); + ret = tb_port_wait_for_link_width(port, width_mask, + XDOMAIN_BONDING_TIMEOUT); if (ret) { dev_warn(&xd->dev, "error waiting for link width to become %d\n", - width); + width_mask); return ret; } - port->bonded = width == 2; - port->dual_link_port->bonded = width == 2; + port->bonded = width > TB_LINK_WIDTH_SINGLE; + port->dual_link_port->bonded = width > TB_LINK_WIDTH_SINGLE; tb_port_update_credits(port); tb_xdomain_update_link_attributes(xd); @@ -1425,7 +1427,7 @@ static int tb_xdomain_get_properties(struct tb_xdomain *xd) if (xd->bonding_possible) { struct tb_port *port; - port = tb_port_at(xd->route, tb_xdomain_parent(xd)); + port = tb_xdomain_downstream_port(xd); if (!port->bonded) tb_port_disable(port->dual_link_port); } @@ -1737,16 +1739,57 @@ static ssize_t speed_show(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR(rx_speed, 0444, speed_show, NULL); static DEVICE_ATTR(tx_speed, 0444, speed_show, NULL); -static ssize_t lanes_show(struct device *dev, struct device_attribute *attr, - char *buf) +static ssize_t rx_lanes_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct tb_xdomain *xd = container_of(dev, struct tb_xdomain, dev); + unsigned int width; - return sysfs_emit(buf, "%u\n", xd->link_width); + switch (xd->link_width) { + case TB_LINK_WIDTH_SINGLE: + case TB_LINK_WIDTH_ASYM_RX: + width = 1; + break; + case TB_LINK_WIDTH_DUAL: + width = 2; + break; + case TB_LINK_WIDTH_ASYM_TX: + width = 3; + break; + default: + WARN_ON_ONCE(1); + return -EINVAL; + } + + return sysfs_emit(buf, "%u\n", width); } +static DEVICE_ATTR(rx_lanes, 0444, rx_lanes_show, NULL); + +static ssize_t tx_lanes_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tb_xdomain *xd = container_of(dev, struct tb_xdomain, dev); + unsigned int width; -static DEVICE_ATTR(rx_lanes, 0444, lanes_show, NULL); -static DEVICE_ATTR(tx_lanes, 0444, lanes_show, NULL); + switch (xd->link_width) { + case TB_LINK_WIDTH_SINGLE: + case TB_LINK_WIDTH_ASYM_TX: + width = 1; + break; + case TB_LINK_WIDTH_DUAL: + width = 2; + break; + case TB_LINK_WIDTH_ASYM_RX: + width = 3; + break; + default: + WARN_ON_ONCE(1); + return -EINVAL; + } + + return sysfs_emit(buf, "%u\n", width); +} +static DEVICE_ATTR(tx_lanes, 0444, tx_lanes_show, NULL); static struct attribute *xdomain_attrs[] = { &dev_attr_device.attr, @@ -1976,10 +2019,11 @@ void tb_xdomain_remove(struct tb_xdomain *xd) */ int tb_xdomain_lane_bonding_enable(struct tb_xdomain *xd) { + unsigned int width_mask; struct tb_port *port; int ret; - port = tb_port_at(xd->route, tb_xdomain_parent(xd)); + port = tb_xdomain_downstream_port(xd); if (!port->dual_link_port) return -ENODEV; @@ -1999,7 +2043,12 @@ int tb_xdomain_lane_bonding_enable(struct tb_xdomain *xd) return ret; } - ret = tb_port_wait_for_link_width(port, 2, XDOMAIN_BONDING_TIMEOUT); + /* 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(port, width_mask, + XDOMAIN_BONDING_TIMEOUT); if (ret) { tb_port_warn(port, "failed to enable lane bonding\n"); return ret; @@ -2024,10 +2073,13 @@ void tb_xdomain_lane_bonding_disable(struct tb_xdomain *xd) { struct tb_port *port; - port = tb_port_at(xd->route, tb_xdomain_parent(xd)); + port = tb_xdomain_downstream_port(xd); if (port->dual_link_port) { + int ret; + tb_port_lane_bonding_disable(port); - if (tb_port_wait_for_link_width(port, 1, 100) == -ETIMEDOUT) + ret = tb_port_wait_for_link_width(port, TB_LINK_WIDTH_SINGLE, 100); + if (ret == -ETIMEDOUT) tb_port_warn(port, "timeout disabling lane bonding\n"); tb_port_disable(port->dual_link_port); tb_port_update_credits(port); diff --git a/drivers/usb/c67x00/c67x00-drv.c b/drivers/usb/c67x00/c67x00-drv.c index 6db5cb1b2dbb..bb9d5d7ffefc 100644 --- a/drivers/usb/c67x00/c67x00-drv.c +++ b/drivers/usb/c67x00/c67x00-drv.c @@ -177,7 +177,7 @@ static int c67x00_drv_probe(struct platform_device *pdev) return ret; } -static int c67x00_drv_remove(struct platform_device *pdev) +static void c67x00_drv_remove(struct platform_device *pdev) { struct c67x00_device *c67x00 = platform_get_drvdata(pdev); struct resource *res; @@ -197,13 +197,11 @@ static int c67x00_drv_remove(struct platform_device *pdev) release_mem_region(res->start, resource_size(res)); kfree(c67x00); - - return 0; } static struct platform_driver c67x00_driver = { .probe = c67x00_drv_probe, - .remove = c67x00_drv_remove, + .remove_new = c67x00_drv_remove, .driver = { .name = "c67x00", }, diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig index b98ca0a1352a..0a514b591527 100644 --- a/drivers/usb/cdns3/Kconfig +++ b/drivers/usb/cdns3/Kconfig @@ -78,6 +78,17 @@ config USB_CDNS3_IMX For example, imx8qm and imx8qxp. +config USB_CDNS3_STARFIVE + tristate "Cadence USB3 support on StarFive SoC platforms" + depends on ARCH_STARFIVE || COMPILE_TEST + help + Say 'Y' or 'M' here if you are building for StarFive SoCs + platforms that contain Cadence USB3 controller core. + + e.g. JH7110. + + If you choose to build this driver as module it will + be dynamically linked and module will be called cdns3-starfive.ko endif if USB_CDNS_SUPPORT diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile index 61edb2f89276..48dfae75b5aa 100644 --- a/drivers/usb/cdns3/Makefile +++ b/drivers/usb/cdns3/Makefile @@ -24,6 +24,7 @@ endif obj-$(CONFIG_USB_CDNS3_PCI_WRAP) += cdns3-pci-wrap.o obj-$(CONFIG_USB_CDNS3_TI) += cdns3-ti.o obj-$(CONFIG_USB_CDNS3_IMX) += cdns3-imx.o +obj-$(CONFIG_USB_CDNS3_STARFIVE) += cdns3-starfive.o cdnsp-udc-pci-y := cdnsp-pci.o diff --git a/drivers/usb/cdns3/cdns3-gadget.c b/drivers/usb/cdns3/cdns3-gadget.c index 1dcadef933e3..ea19253fd2d0 100644 --- a/drivers/usb/cdns3/cdns3-gadget.c +++ b/drivers/usb/cdns3/cdns3-gadget.c @@ -800,7 +800,8 @@ void cdns3_gadget_giveback(struct cdns3_endpoint *priv_ep, if (request->status == -EINPROGRESS) request->status = status; - usb_gadget_unmap_request_by_dev(priv_dev->sysdev, request, + if (likely(!(priv_req->flags & REQUEST_UNALIGNED))) + usb_gadget_unmap_request_by_dev(priv_dev->sysdev, request, priv_ep->dir); if ((priv_req->flags & REQUEST_UNALIGNED) && @@ -808,10 +809,10 @@ void cdns3_gadget_giveback(struct cdns3_endpoint *priv_ep, /* Make DMA buffer CPU accessible */ dma_sync_single_for_cpu(priv_dev->sysdev, priv_req->aligned_buf->dma, - priv_req->aligned_buf->size, + request->actual, priv_req->aligned_buf->dir); memcpy(request->buf, priv_req->aligned_buf->buf, - request->length); + request->actual); } priv_req->flags &= ~(REQUEST_PENDING | REQUEST_UNALIGNED); @@ -2543,10 +2544,12 @@ static int __cdns3_gadget_ep_queue(struct usb_ep *ep, if (ret < 0) return ret; - ret = usb_gadget_map_request_by_dev(priv_dev->sysdev, request, + if (likely(!(priv_req->flags & REQUEST_UNALIGNED))) { + ret = usb_gadget_map_request_by_dev(priv_dev->sysdev, request, usb_endpoint_dir_in(ep->desc)); - if (ret) - return ret; + if (ret) + return ret; + } list_add_tail(&request->list, &priv_ep->deferred_req_list); diff --git a/drivers/usb/cdns3/cdns3-imx.c b/drivers/usb/cdns3/cdns3-imx.c index 59860d1753fd..281de47e2a3b 100644 --- a/drivers/usb/cdns3/cdns3-imx.c +++ b/drivers/usb/cdns3/cdns3-imx.c @@ -105,11 +105,11 @@ static inline void cdns_imx_writel(struct cdns_imx *data, u32 offset, u32 value) } static const struct clk_bulk_data imx_cdns3_core_clks[] = { - { .id = "usb3_lpm_clk" }, - { .id = "usb3_bus_clk" }, - { .id = "usb3_aclk" }, - { .id = "usb3_ipg_clk" }, - { .id = "usb3_core_pclk" }, + { .id = "lpm" }, + { .id = "bus" }, + { .id = "aclk" }, + { .id = "ipg" }, + { .id = "core" }, }; static int cdns_imx_noncore_init(struct cdns_imx *data) @@ -218,7 +218,7 @@ err: return ret; } -static int cdns_imx_remove(struct platform_device *pdev) +static void cdns_imx_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct cdns_imx *data = dev_get_drvdata(dev); @@ -229,8 +229,6 @@ static int cdns_imx_remove(struct platform_device *pdev) pm_runtime_disable(dev); pm_runtime_put_noidle(dev); platform_set_drvdata(pdev, NULL); - - return 0; } #ifdef CONFIG_PM @@ -375,14 +373,22 @@ static inline bool cdns_imx_is_power_lost(struct cdns_imx *data) return false; } +static int __maybe_unused cdns_imx_system_suspend(struct device *dev) +{ + pm_runtime_put_sync(dev); + return 0; +} + static int __maybe_unused cdns_imx_system_resume(struct device *dev) { struct cdns_imx *data = dev_get_drvdata(dev); int ret; - ret = cdns_imx_resume(dev); - if (ret) + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + dev_err(dev, "Could not get runtime PM.\n"); return ret; + } if (cdns_imx_is_power_lost(data)) { dev_dbg(dev, "resume from power lost\n"); @@ -405,7 +411,7 @@ static int cdns_imx_platform_suspend(struct device *dev, static const struct dev_pm_ops cdns_imx_pm_ops = { SET_RUNTIME_PM_OPS(cdns_imx_suspend, cdns_imx_resume, NULL) - SET_SYSTEM_SLEEP_PM_OPS(cdns_imx_suspend, cdns_imx_system_resume) + SET_SYSTEM_SLEEP_PM_OPS(cdns_imx_system_suspend, cdns_imx_system_resume) }; static const struct of_device_id cdns_imx_of_match[] = { @@ -416,7 +422,7 @@ MODULE_DEVICE_TABLE(of, cdns_imx_of_match); static struct platform_driver cdns_imx_driver = { .probe = cdns_imx_probe, - .remove = cdns_imx_remove, + .remove_new = cdns_imx_remove, .driver = { .name = "cdns3-imx", .of_match_table = cdns_imx_of_match, diff --git a/drivers/usb/cdns3/cdns3-plat.c b/drivers/usb/cdns3/cdns3-plat.c index 2bc5d094548b..884e2301237f 100644 --- a/drivers/usb/cdns3/cdns3-plat.c +++ b/drivers/usb/cdns3/cdns3-plat.c @@ -175,7 +175,7 @@ err_phy3_init: * * Returns 0 on success otherwise negative errno */ -static int cdns3_plat_remove(struct platform_device *pdev) +static void cdns3_plat_remove(struct platform_device *pdev) { struct cdns *cdns = platform_get_drvdata(pdev); struct device *dev = cdns->dev; @@ -187,7 +187,6 @@ static int cdns3_plat_remove(struct platform_device *pdev) set_phy_power_off(cdns); phy_exit(cdns->usb2_phy); phy_exit(cdns->usb3_phy); - return 0; } #ifdef CONFIG_PM @@ -320,7 +319,7 @@ MODULE_DEVICE_TABLE(of, of_cdns3_match); static struct platform_driver cdns3_driver = { .probe = cdns3_plat_probe, - .remove = cdns3_plat_remove, + .remove_new = cdns3_plat_remove, .driver = { .name = "cdns-usb3", .of_match_table = of_match_ptr(of_cdns3_match), diff --git a/drivers/usb/cdns3/cdns3-starfive.c b/drivers/usb/cdns3/cdns3-starfive.c new file mode 100644 index 000000000000..fc1f003b145d --- /dev/null +++ b/drivers/usb/cdns3/cdns3-starfive.c @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * cdns3-starfive.c - StarFive specific Glue layer for Cadence USB Controller + * + * Copyright (C) 2023 StarFive Technology Co., Ltd. + * + * Author: Minda Chen <minda.chen@starfivetech.com> + */ + +#include <linux/bits.h> +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/mfd/syscon.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/of_platform.h> +#include <linux/reset.h> +#include <linux/regmap.h> +#include <linux/usb/otg.h> +#include "core.h" + +#define USB_STRAP_HOST BIT(17) +#define USB_STRAP_DEVICE BIT(18) +#define USB_STRAP_MASK GENMASK(18, 16) + +#define USB_SUSPENDM_HOST BIT(19) +#define USB_SUSPENDM_MASK BIT(19) + +#define USB_MISC_CFG_MASK GENMASK(23, 20) +#define USB_SUSPENDM_BYPS BIT(20) +#define USB_PLL_EN BIT(22) +#define USB_REFCLK_MODE BIT(23) + +struct cdns_starfive { + struct device *dev; + struct regmap *stg_syscon; + struct reset_control *resets; + struct clk_bulk_data *clks; + int num_clks; + u32 stg_usb_mode; +}; + +static void cdns_mode_init(struct platform_device *pdev, + struct cdns_starfive *data) +{ + enum usb_dr_mode mode; + + regmap_update_bits(data->stg_syscon, data->stg_usb_mode, + USB_MISC_CFG_MASK, + USB_SUSPENDM_BYPS | USB_PLL_EN | USB_REFCLK_MODE); + + /* dr mode setting */ + mode = usb_get_dr_mode(&pdev->dev); + + switch (mode) { + case USB_DR_MODE_HOST: + regmap_update_bits(data->stg_syscon, + data->stg_usb_mode, + USB_STRAP_MASK, + USB_STRAP_HOST); + regmap_update_bits(data->stg_syscon, + data->stg_usb_mode, + USB_SUSPENDM_MASK, + USB_SUSPENDM_HOST); + break; + + case USB_DR_MODE_PERIPHERAL: + regmap_update_bits(data->stg_syscon, data->stg_usb_mode, + USB_STRAP_MASK, USB_STRAP_DEVICE); + regmap_update_bits(data->stg_syscon, data->stg_usb_mode, + USB_SUSPENDM_MASK, 0); + break; + default: + break; + } +} + +static int cdns_clk_rst_init(struct cdns_starfive *data) +{ + int ret; + + ret = clk_bulk_prepare_enable(data->num_clks, data->clks); + if (ret) + return dev_err_probe(data->dev, ret, + "failed to enable clocks\n"); + + ret = reset_control_deassert(data->resets); + if (ret) { + dev_err(data->dev, "failed to reset clocks\n"); + goto err_clk_init; + } + + return ret; + +err_clk_init: + clk_bulk_disable_unprepare(data->num_clks, data->clks); + return ret; +} + +static void cdns_clk_rst_deinit(struct cdns_starfive *data) +{ + reset_control_assert(data->resets); + clk_bulk_disable_unprepare(data->num_clks, data->clks); +} + +static int cdns_starfive_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cdns_starfive *data; + unsigned int args; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = dev; + + data->stg_syscon = + syscon_regmap_lookup_by_phandle_args(pdev->dev.of_node, + "starfive,stg-syscon", 1, &args); + + if (IS_ERR(data->stg_syscon)) + return dev_err_probe(dev, PTR_ERR(data->stg_syscon), + "Failed to parse starfive,stg-syscon\n"); + + data->stg_usb_mode = args; + + data->num_clks = devm_clk_bulk_get_all(data->dev, &data->clks); + if (data->num_clks < 0) + return dev_err_probe(data->dev, -ENODEV, + "Failed to get clocks\n"); + + data->resets = devm_reset_control_array_get_exclusive(data->dev); + if (IS_ERR(data->resets)) + return dev_err_probe(data->dev, PTR_ERR(data->resets), + "Failed to get resets"); + + cdns_mode_init(pdev, data); + ret = cdns_clk_rst_init(data); + if (ret) + return ret; + + ret = of_platform_populate(dev->of_node, NULL, NULL, dev); + if (ret) { + dev_err(dev, "Failed to create children\n"); + cdns_clk_rst_deinit(data); + return ret; + } + + device_set_wakeup_capable(dev, true); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + platform_set_drvdata(pdev, data); + + return 0; +} + +static int cdns_starfive_remove_core(struct device *dev, void *c) +{ + struct platform_device *pdev = to_platform_device(dev); + + platform_device_unregister(pdev); + + return 0; +} + +static int cdns_starfive_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cdns_starfive *data = dev_get_drvdata(dev); + + pm_runtime_get_sync(dev); + device_for_each_child(dev, NULL, cdns_starfive_remove_core); + + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); + cdns_clk_rst_deinit(data); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_PM +static int cdns_starfive_runtime_resume(struct device *dev) +{ + struct cdns_starfive *data = dev_get_drvdata(dev); + + return clk_bulk_prepare_enable(data->num_clks, data->clks); +} + +static int cdns_starfive_runtime_suspend(struct device *dev) +{ + struct cdns_starfive *data = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(data->num_clks, data->clks); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int cdns_starfive_resume(struct device *dev) +{ + struct cdns_starfive *data = dev_get_drvdata(dev); + + return cdns_clk_rst_init(data); +} + +static int cdns_starfive_suspend(struct device *dev) +{ + struct cdns_starfive *data = dev_get_drvdata(dev); + + cdns_clk_rst_deinit(data); + + return 0; +} +#endif +#endif + +static const struct dev_pm_ops cdns_starfive_pm_ops = { + SET_RUNTIME_PM_OPS(cdns_starfive_runtime_suspend, + cdns_starfive_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(cdns_starfive_suspend, cdns_starfive_resume) +}; + +static const struct of_device_id cdns_starfive_of_match[] = { + { .compatible = "starfive,jh7110-usb", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, cdns_starfive_of_match); + +static struct platform_driver cdns_starfive_driver = { + .probe = cdns_starfive_probe, + .remove = cdns_starfive_remove, + .driver = { + .name = "cdns3-starfive", + .of_match_table = cdns_starfive_of_match, + .pm = &cdns_starfive_pm_ops, + }, +}; +module_platform_driver(cdns_starfive_driver); + +MODULE_ALIAS("platform:cdns3-starfive"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Cadence USB3 StarFive Glue Layer"); diff --git a/drivers/usb/cdns3/cdns3-ti.c b/drivers/usb/cdns3/cdns3-ti.c index 07c318770362..81b9132e3aaa 100644 --- a/drivers/usb/cdns3/cdns3-ti.c +++ b/drivers/usb/cdns3/cdns3-ti.c @@ -199,7 +199,7 @@ static int cdns_ti_remove_core(struct device *dev, void *c) return 0; } -static int cdns_ti_remove(struct platform_device *pdev) +static void cdns_ti_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -208,8 +208,6 @@ static int cdns_ti_remove(struct platform_device *pdev) pm_runtime_disable(dev); platform_set_drvdata(pdev, NULL); - - return 0; } static const struct of_device_id cdns_ti_of_match[] = { @@ -221,7 +219,7 @@ MODULE_DEVICE_TABLE(of, cdns_ti_of_match); static struct platform_driver cdns_ti_driver = { .probe = cdns_ti_probe, - .remove = cdns_ti_remove, + .remove_new = cdns_ti_remove, .driver = { .name = "cdns3-ti", .of_match_table = cdns_ti_of_match, diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 2855ac303001..336ef6dd8e7d 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -70,6 +70,10 @@ static const struct ci_hdrc_imx_platform_flag imx7ulp_usb_data = { CI_HDRC_PMQOS, }; +static const struct ci_hdrc_imx_platform_flag imx8ulp_usb_data = { + .flags = CI_HDRC_SUPPORTS_RUNTIME_PM, +}; + static const struct of_device_id ci_hdrc_imx_dt_ids[] = { { .compatible = "fsl,imx23-usb", .data = &imx23_usb_data}, { .compatible = "fsl,imx28-usb", .data = &imx28_usb_data}, @@ -80,6 +84,7 @@ static const struct of_device_id ci_hdrc_imx_dt_ids[] = { { .compatible = "fsl,imx6ul-usb", .data = &imx6ul_usb_data}, { .compatible = "fsl,imx7d-usb", .data = &imx7d_usb_data}, { .compatible = "fsl,imx7ulp-usb", .data = &imx7ulp_usb_data}, + { .compatible = "fsl,imx8ulp-usb", .data = &imx8ulp_usb_data}, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, ci_hdrc_imx_dt_ids); @@ -502,7 +507,7 @@ disable_hsic_regulator: return ret; } -static int ci_hdrc_imx_remove(struct platform_device *pdev) +static void ci_hdrc_imx_remove(struct platform_device *pdev) { struct ci_hdrc_imx_data *data = platform_get_drvdata(pdev); @@ -522,8 +527,6 @@ static int ci_hdrc_imx_remove(struct platform_device *pdev) if (data->hsic_pad_regulator) regulator_disable(data->hsic_pad_regulator); } - - return 0; } static void ci_hdrc_imx_shutdown(struct platform_device *pdev) @@ -650,7 +653,7 @@ static const struct dev_pm_ops ci_hdrc_imx_pm_ops = { }; static struct platform_driver ci_hdrc_imx_driver = { .probe = ci_hdrc_imx_probe, - .remove = ci_hdrc_imx_remove, + .remove_new = ci_hdrc_imx_remove, .shutdown = ci_hdrc_imx_shutdown, .driver = { .name = "imx_usb", diff --git a/drivers/usb/chipidea/ci_hdrc_msm.c b/drivers/usb/chipidea/ci_hdrc_msm.c index 46105457e1ca..7b5b47ce8a02 100644 --- a/drivers/usb/chipidea/ci_hdrc_msm.c +++ b/drivers/usb/chipidea/ci_hdrc_msm.c @@ -274,7 +274,7 @@ err_iface: return ret; } -static int ci_hdrc_msm_remove(struct platform_device *pdev) +static void ci_hdrc_msm_remove(struct platform_device *pdev) { struct ci_hdrc_msm *ci = platform_get_drvdata(pdev); @@ -282,8 +282,6 @@ static int ci_hdrc_msm_remove(struct platform_device *pdev) ci_hdrc_remove_device(ci->ci); clk_disable_unprepare(ci->iface_clk); clk_disable_unprepare(ci->core_clk); - - return 0; } static const struct of_device_id msm_ci_dt_match[] = { @@ -294,7 +292,7 @@ MODULE_DEVICE_TABLE(of, msm_ci_dt_match); static struct platform_driver ci_hdrc_msm_driver = { .probe = ci_hdrc_msm_probe, - .remove = ci_hdrc_msm_remove, + .remove_new = ci_hdrc_msm_remove, .driver = { .name = "msm_hsusb", .of_match_table = msm_ci_dt_match, diff --git a/drivers/usb/chipidea/ci_hdrc_tegra.c b/drivers/usb/chipidea/ci_hdrc_tegra.c index a72a9474afea..ca36d11a69ea 100644 --- a/drivers/usb/chipidea/ci_hdrc_tegra.c +++ b/drivers/usb/chipidea/ci_hdrc_tegra.c @@ -362,7 +362,7 @@ fail_power_off: return err; } -static int tegra_usb_remove(struct platform_device *pdev) +static void tegra_usb_remove(struct platform_device *pdev) { struct tegra_usb *usb = platform_get_drvdata(pdev); @@ -371,8 +371,6 @@ static int tegra_usb_remove(struct platform_device *pdev) pm_runtime_put_sync_suspend(&pdev->dev); pm_runtime_force_suspend(&pdev->dev); - - return 0; } static int __maybe_unused tegra_usb_runtime_resume(struct device *dev) @@ -410,7 +408,7 @@ static struct platform_driver tegra_usb_driver = { .pm = &tegra_usb_pm, }, .probe = tegra_usb_probe, - .remove = tegra_usb_remove, + .remove_new = tegra_usb_remove, }; module_platform_driver(tegra_usb_driver); diff --git a/drivers/usb/chipidea/ci_hdrc_usb2.c b/drivers/usb/chipidea/ci_hdrc_usb2.c index dc86b12060b5..1321ee67f3b8 100644 --- a/drivers/usb/chipidea/ci_hdrc_usb2.c +++ b/drivers/usb/chipidea/ci_hdrc_usb2.c @@ -106,20 +106,18 @@ clk_err: return ret; } -static int ci_hdrc_usb2_remove(struct platform_device *pdev) +static void ci_hdrc_usb2_remove(struct platform_device *pdev) { struct ci_hdrc_usb2_priv *priv = platform_get_drvdata(pdev); pm_runtime_disable(&pdev->dev); ci_hdrc_remove_device(priv->ci_pdev); clk_disable_unprepare(priv->clk); - - return 0; } static struct platform_driver ci_hdrc_usb2_driver = { .probe = ci_hdrc_usb2_probe, - .remove = ci_hdrc_usb2_remove, + .remove_new = ci_hdrc_usb2_remove, .driver = { .name = "chipidea-usb2", .of_match_table = of_match_ptr(ci_hdrc_usb2_of_match), diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 798cb077867a..51994d655b82 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -1227,7 +1227,7 @@ ulpi_exit: return ret; } -static int ci_hdrc_remove(struct platform_device *pdev) +static void ci_hdrc_remove(struct platform_device *pdev) { struct ci_hdrc *ci = platform_get_drvdata(pdev); @@ -1245,8 +1245,6 @@ static int ci_hdrc_remove(struct platform_device *pdev) ci_hdrc_enter_lpm(ci, true); ci_usb_phy_exit(ci); ci_ulpi_exit(ci); - - return 0; } #ifdef CONFIG_PM @@ -1485,7 +1483,7 @@ static const struct dev_pm_ops ci_pm_ops = { static struct platform_driver ci_hdrc_driver = { .probe = ci_hdrc_probe, - .remove = ci_hdrc_remove, + .remove_new = ci_hdrc_remove, .driver = { .name = "ci_hdrc", .pm = &ci_pm_ops, diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c index c57c1a71a513..9ee9621e2ccc 100644 --- a/drivers/usb/chipidea/usbmisc_imx.c +++ b/drivers/usb/chipidea/usbmisc_imx.c @@ -113,7 +113,6 @@ #define MX7D_USBNC_USB_CTRL2_DP_DM_MASK (BIT(12) | BIT(13) | \ BIT(14) | BIT(15)) -#define MX7D_USB_OTG_PHY_CFG1 0x30 #define MX7D_USB_OTG_PHY_CFG2_CHRG_CHRGSEL BIT(0) #define MX7D_USB_OTG_PHY_CFG2_CHRG_VDATDETENB0 BIT(1) #define MX7D_USB_OTG_PHY_CFG2_CHRG_VDATSRCENB0 BIT(2) @@ -135,7 +134,7 @@ #define TXVREFTUNE0_MASK (0xf << 20) #define MX6_USB_OTG_WAKEUP_BITS (MX6_BM_WAKEUP_ENABLE | MX6_BM_VBUS_WAKEUP | \ - MX6_BM_ID_WAKEUP) + MX6_BM_ID_WAKEUP | MX6SX_BM_DPDM_WAKEUP_EN) struct usbmisc_ops { /* It's called once when probe a usb device */ @@ -152,6 +151,7 @@ struct usbmisc_ops { int (*charger_detection)(struct imx_usbmisc_data *data); /* It's called when system resume from usb power lost */ int (*power_lost_check)(struct imx_usbmisc_data *data); + void (*vbus_comparator_on)(struct imx_usbmisc_data *data, bool on); }; struct imx_usbmisc { @@ -875,6 +875,33 @@ static int imx7d_charger_detection(struct imx_usbmisc_data *data) return ret; } +static void usbmisc_imx7d_vbus_comparator_on(struct imx_usbmisc_data *data, + bool on) +{ + unsigned long flags; + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + u32 val; + + if (data->hsic) + return; + + spin_lock_irqsave(&usbmisc->lock, flags); + /* + * Disable VBUS valid comparator when in suspend mode, + * when OTG is disabled and DRVVBUS0 is asserted case + * the Bandgap circuitry and VBUS Valid comparator are + * still powered, even in Suspend or Sleep mode. + */ + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + if (on) + val |= MX7D_USB_OTG_PHY_CFG2_DRVVBUS0; + else + val &= ~MX7D_USB_OTG_PHY_CFG2_DRVVBUS0; + + writel(val, usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + spin_unlock_irqrestore(&usbmisc->lock, flags); +} + static int usbmisc_imx7ulp_init(struct imx_usbmisc_data *data) { struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); @@ -1018,6 +1045,7 @@ static const struct usbmisc_ops imx7d_usbmisc_ops = { .set_wakeup = usbmisc_imx7d_set_wakeup, .charger_detection = imx7d_charger_detection, .power_lost_check = usbmisc_imx7d_power_lost_check, + .vbus_comparator_on = usbmisc_imx7d_vbus_comparator_on, }; static const struct usbmisc_ops imx7ulp_usbmisc_ops = { @@ -1132,6 +1160,9 @@ int imx_usbmisc_suspend(struct imx_usbmisc_data *data, bool wakeup) usbmisc = dev_get_drvdata(data->dev); + if (usbmisc->ops->vbus_comparator_on) + usbmisc->ops->vbus_comparator_on(data, false); + if (wakeup && usbmisc->ops->set_wakeup) ret = usbmisc->ops->set_wakeup(data, true); if (ret) { @@ -1185,6 +1216,9 @@ int imx_usbmisc_resume(struct imx_usbmisc_data *data, bool wakeup) goto hsic_set_clk_fail; } + if (usbmisc->ops->vbus_comparator_on) + usbmisc->ops->vbus_comparator_on(data, true); + return 0; hsic_set_clk_fail: diff --git a/drivers/usb/common/usb-conn-gpio.c b/drivers/usb/common/usb-conn-gpio.c index e20874caba36..766005d20bae 100644 --- a/drivers/usb/common/usb-conn-gpio.c +++ b/drivers/usb/common/usb-conn-gpio.c @@ -267,7 +267,7 @@ put_role_sw: return ret; } -static int usb_conn_remove(struct platform_device *pdev) +static void usb_conn_remove(struct platform_device *pdev) { struct usb_conn_info *info = platform_get_drvdata(pdev); @@ -277,8 +277,6 @@ static int usb_conn_remove(struct platform_device *pdev) regulator_disable(info->vbus); usb_role_switch_put(info->role_sw); - - return 0; } static int __maybe_unused usb_conn_suspend(struct device *dev) @@ -338,7 +336,7 @@ MODULE_DEVICE_TABLE(of, usb_conn_dt_match); static struct platform_driver usb_conn_driver = { .probe = usb_conn_probe, - .remove = usb_conn_remove, + .remove_new = usb_conn_remove, .driver = { .name = "usb-conn-gpio", .pm = &usb_conn_pm_ops, diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index fcf68818e999..1a16a8bdea60 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -746,6 +746,7 @@ static int driver_resume(struct usb_interface *intf) return 0; } +#ifdef CONFIG_PM /* The following routines apply to the entire device, not interfaces */ void usbfs_notify_suspend(struct usb_device *udev) { @@ -764,6 +765,7 @@ void usbfs_notify_resume(struct usb_device *udev) } mutex_unlock(&usbfs_mutex); } +#endif struct usb_driver usbfs_driver = { .name = "usbfs", @@ -2640,21 +2642,21 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd, snoop(&dev->dev, "%s: CONTROL\n", __func__); ret = proc_control(ps, p); if (ret >= 0) - inode->i_mtime = current_time(inode); + inode->i_mtime = inode->i_ctime = current_time(inode); break; case USBDEVFS_BULK: snoop(&dev->dev, "%s: BULK\n", __func__); ret = proc_bulk(ps, p); if (ret >= 0) - inode->i_mtime = current_time(inode); + inode->i_mtime = inode->i_ctime = current_time(inode); break; case USBDEVFS_RESETEP: snoop(&dev->dev, "%s: RESETEP\n", __func__); ret = proc_resetep(ps, p); if (ret >= 0) - inode->i_mtime = current_time(inode); + inode->i_mtime = inode->i_ctime = current_time(inode); break; case USBDEVFS_RESET: @@ -2666,7 +2668,7 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd, snoop(&dev->dev, "%s: CLEAR_HALT\n", __func__); ret = proc_clearhalt(ps, p); if (ret >= 0) - inode->i_mtime = current_time(inode); + inode->i_mtime = inode->i_ctime = current_time(inode); break; case USBDEVFS_GETDRIVER: @@ -2693,7 +2695,7 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd, snoop(&dev->dev, "%s: SUBMITURB\n", __func__); ret = proc_submiturb(ps, p); if (ret >= 0) - inode->i_mtime = current_time(inode); + inode->i_mtime = inode->i_ctime = current_time(inode); break; #ifdef CONFIG_COMPAT @@ -2701,14 +2703,14 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd, snoop(&dev->dev, "%s: CONTROL32\n", __func__); ret = proc_control_compat(ps, p); if (ret >= 0) - inode->i_mtime = current_time(inode); + inode->i_mtime = inode->i_ctime = current_time(inode); break; case USBDEVFS_BULK32: snoop(&dev->dev, "%s: BULK32\n", __func__); ret = proc_bulk_compat(ps, p); if (ret >= 0) - inode->i_mtime = current_time(inode); + inode->i_mtime = inode->i_ctime = current_time(inode); break; case USBDEVFS_DISCSIGNAL32: @@ -2720,7 +2722,7 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd, snoop(&dev->dev, "%s: SUBMITURB32\n", __func__); ret = proc_submiturb_compat(ps, p); if (ret >= 0) - inode->i_mtime = current_time(inode); + inode->i_mtime = inode->i_ctime = current_time(inode); break; case USBDEVFS_IOCTL32: diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index ab2f3737764e..990280688b25 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c @@ -415,12 +415,15 @@ static int check_root_hub_suspended(struct device *dev) return 0; } -static int suspend_common(struct device *dev, bool do_wakeup) +static int suspend_common(struct device *dev, pm_message_t msg) { struct pci_dev *pci_dev = to_pci_dev(dev); struct usb_hcd *hcd = pci_get_drvdata(pci_dev); + bool do_wakeup; int retval; + do_wakeup = PMSG_IS_AUTO(msg) ? true : device_may_wakeup(dev); + /* Root hub suspend should have stopped all downstream traffic, * and all bus master traffic. And done so for both the interface * and the stub usb_device (which we check here). But maybe it @@ -447,7 +450,7 @@ static int suspend_common(struct device *dev, bool do_wakeup) (retval == 0 && do_wakeup && hcd->shared_hcd && HCD_WAKEUP_PENDING(hcd->shared_hcd))) { if (hcd->driver->pci_resume) - hcd->driver->pci_resume(hcd, false); + hcd->driver->pci_resume(hcd, msg); retval = -EBUSY; } if (retval) @@ -470,7 +473,7 @@ static int suspend_common(struct device *dev, bool do_wakeup) return retval; } -static int resume_common(struct device *dev, int event) +static int resume_common(struct device *dev, pm_message_t msg) { struct pci_dev *pci_dev = to_pci_dev(dev); struct usb_hcd *hcd = pci_get_drvdata(pci_dev); @@ -498,12 +501,11 @@ static int resume_common(struct device *dev, int event) * No locking is needed because PCI controller drivers do not * get unbound during system resume. */ - if (pci_dev->class == CL_EHCI && event != PM_EVENT_AUTO_RESUME) + if (pci_dev->class == CL_EHCI && msg.event != PM_EVENT_AUTO_RESUME) for_each_companion(pci_dev, hcd, ehci_wait_for_companions); - retval = hcd->driver->pci_resume(hcd, - event == PM_EVENT_RESTORE); + retval = hcd->driver->pci_resume(hcd, msg); if (retval) { dev_err(dev, "PCI post-resume error %d!\n", retval); usb_hc_died(hcd); @@ -516,7 +518,7 @@ static int resume_common(struct device *dev, int event) static int hcd_pci_suspend(struct device *dev) { - return suspend_common(dev, device_may_wakeup(dev)); + return suspend_common(dev, PMSG_SUSPEND); } static int hcd_pci_suspend_noirq(struct device *dev) @@ -577,12 +579,12 @@ static int hcd_pci_resume_noirq(struct device *dev) static int hcd_pci_resume(struct device *dev) { - return resume_common(dev, PM_EVENT_RESUME); + return resume_common(dev, PMSG_RESUME); } static int hcd_pci_restore(struct device *dev) { - return resume_common(dev, PM_EVENT_RESTORE); + return resume_common(dev, PMSG_RESTORE); } #else @@ -600,7 +602,7 @@ static int hcd_pci_runtime_suspend(struct device *dev) { int retval; - retval = suspend_common(dev, true); + retval = suspend_common(dev, PMSG_AUTO_SUSPEND); if (retval == 0) powermac_set_asic(to_pci_dev(dev), 0); dev_dbg(dev, "hcd_pci_runtime_suspend: %d\n", retval); @@ -612,7 +614,7 @@ static int hcd_pci_runtime_resume(struct device *dev) int retval; powermac_set_asic(to_pci_dev(dev), 1); - retval = resume_common(dev, PM_EVENT_AUTO_RESUME); + retval = resume_common(dev, PMSG_AUTO_RESUME); dev_dbg(dev, "hcd_pci_runtime_resume: %d\n", retval); return retval; } diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 97a0f8faea6e..a739403a9e45 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2018,6 +2018,19 @@ bool usb_device_is_owned(struct usb_device *udev) return !!hub->ports[udev->portnum - 1]->port_owner; } +static void update_port_device_state(struct usb_device *udev) +{ + struct usb_hub *hub; + struct usb_port *port_dev; + + if (udev->parent) { + hub = usb_hub_to_struct_hub(udev->parent); + port_dev = hub->ports[udev->portnum - 1]; + WRITE_ONCE(port_dev->state, udev->state); + sysfs_notify_dirent(port_dev->state_kn); + } +} + static void recursively_mark_NOTATTACHED(struct usb_device *udev) { struct usb_hub *hub = usb_hub_to_struct_hub(udev); @@ -2030,6 +2043,7 @@ static void recursively_mark_NOTATTACHED(struct usb_device *udev) if (udev->state == USB_STATE_SUSPENDED) udev->active_duration -= jiffies; udev->state = USB_STATE_NOTATTACHED; + update_port_device_state(udev); } /** @@ -2086,6 +2100,7 @@ void usb_set_device_state(struct usb_device *udev, udev->state != USB_STATE_SUSPENDED) udev->active_duration += jiffies; udev->state = new_state; + update_port_device_state(udev); } else recursively_mark_NOTATTACHED(udev); spin_unlock_irqrestore(&device_state_lock, flags); diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h index e23833562e4f..37897afd1b64 100644 --- a/drivers/usb/core/hub.h +++ b/drivers/usb/core/hub.h @@ -84,6 +84,8 @@ struct usb_hub { * @peer: related usb2 and usb3 ports (share the same 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 + * @state_kn: kernfs_node of the sysfs attribute that accesses @state * @location: opaque representation of platform connector location * @status_lock: synchronize port_event() vs usb_port_{suspend|resume} * @portnum: port index num based one @@ -100,6 +102,8 @@ struct usb_port { struct usb_port *peer; struct dev_pm_qos_request *req; enum usb_port_connect_type connect_type; + enum usb_device_state state; + struct kernfs_node *state_kn; usb_port_location_t location; struct mutex status_lock; u32 over_current_count; diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index 06a8f1f84f6f..77be0dc28da9 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -160,6 +160,16 @@ static ssize_t connect_type_show(struct device *dev, } static DEVICE_ATTR_RO(connect_type); +static ssize_t state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_port *port_dev = to_usb_port(dev); + enum usb_device_state state = READ_ONCE(port_dev->state); + + return sysfs_emit(buf, "%s\n", usb_state_string(state)); +} +static DEVICE_ATTR_RO(state); + static ssize_t over_current_count_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -259,6 +269,7 @@ static DEVICE_ATTR_RW(usb3_lpm_permit); static struct attribute *port_dev_attrs[] = { &dev_attr_connect_type.attr, + &dev_attr_state.attr, &dev_attr_location.attr, &dev_attr_quirks.attr, &dev_attr_over_current_count.attr, @@ -705,19 +716,24 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1) return retval; } + port_dev->state_kn = sysfs_get_dirent(port_dev->dev.kobj.sd, "state"); + if (!port_dev->state_kn) { + dev_err(&port_dev->dev, "failed to sysfs_get_dirent 'state'\n"); + retval = -ENODEV; + goto err_unregister; + } + /* Set default policy of port-poweroff disabled. */ retval = dev_pm_qos_add_request(&port_dev->dev, port_dev->req, DEV_PM_QOS_FLAGS, PM_QOS_FLAG_NO_POWER_OFF); if (retval < 0) { - device_unregister(&port_dev->dev); - return retval; + goto err_put_kn; } retval = component_add(&port_dev->dev, &connector_ops); if (retval) { dev_warn(&port_dev->dev, "failed to add component\n"); - device_unregister(&port_dev->dev); - return retval; + goto err_put_kn; } find_and_link_peer(hub, port1); @@ -754,6 +770,13 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1) port_dev->req = NULL; } return 0; + +err_put_kn: + sysfs_put(port_dev->state_kn); +err_unregister: + device_unregister(&port_dev->dev); + + return retval; } void usb_hub_remove_port_device(struct usb_hub *hub, int port1) @@ -765,5 +788,6 @@ void usb_hub_remove_port_device(struct usb_hub *hub, int port1) if (peer) unlink_peers(port_dev, peer); component_del(&port_dev->dev, &connector_ops); + sysfs_put(port_dev->state_kn); device_unregister(&port_dev->dev); } diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c index 21d16533bd2f..4c7c3dd15f9b 100644 --- a/drivers/usb/dwc2/params.c +++ b/drivers/usb/dwc2/params.c @@ -161,6 +161,25 @@ static void dwc2_set_amlogic_g12a_params(struct dwc2_hsotg *hsotg) p->hird_threshold_en = false; } +static void dwc2_set_amlogic_a1_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->otg_caps.srp_support = false; + p->speed = DWC2_SPEED_PARAM_HIGH; + p->host_rx_fifo_size = 192; + p->host_nperio_tx_fifo_size = 128; + p->host_perio_tx_fifo_size = 128; + p->phy_type = DWC2_PHY_TYPE_PARAM_UTMI; + p->phy_utmi_width = 8; + p->ahbcfg = GAHBCFG_HBSTLEN_INCR8 << GAHBCFG_HBSTLEN_SHIFT; + p->lpm = false; + p->lpm_clock_gating = false; + p->besl = false; + p->hird_threshold_en = false; +} + static void dwc2_set_amcc_params(struct dwc2_hsotg *hsotg) { struct dwc2_core_params *p = &hsotg->params; @@ -258,6 +277,8 @@ const struct of_device_id dwc2_of_match_table[] = { .data = dwc2_set_amlogic_params }, { .compatible = "amlogic,meson-g12a-usb", .data = dwc2_set_amlogic_g12a_params }, + { .compatible = "amlogic,meson-a1-usb", + .data = dwc2_set_amlogic_a1_params }, { .compatible = "amcc,dwc-otg", .data = dwc2_set_amcc_params }, { .compatible = "apm,apm82181-dwc-otg", .data = dwc2_set_amcc_params }, { .compatible = "st,stm32f4x9-fsotg", diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index 5aee284018c0..0a806f80217e 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -203,6 +203,11 @@ int dwc2_lowlevel_hw_disable(struct dwc2_hsotg *hsotg) return ret; } +static void dwc2_reset_control_assert(void *data) +{ + reset_control_assert(data); +} + static int dwc2_lowlevel_hw_init(struct dwc2_hsotg *hsotg) { int i, ret; @@ -213,6 +218,10 @@ static int dwc2_lowlevel_hw_init(struct dwc2_hsotg *hsotg) "error getting reset control\n"); reset_control_deassert(hsotg->reset); + ret = devm_add_action_or_reset(hsotg->dev, dwc2_reset_control_assert, + hsotg->reset); + if (ret) + return ret; hsotg->reset_ecc = devm_reset_control_get_optional(hsotg->dev, "dwc2-ecc"); if (IS_ERR(hsotg->reset_ecc)) @@ -220,6 +229,10 @@ static int dwc2_lowlevel_hw_init(struct dwc2_hsotg *hsotg) "error getting reset control for ecc\n"); reset_control_deassert(hsotg->reset_ecc); + ret = devm_add_action_or_reset(hsotg->dev, dwc2_reset_control_assert, + hsotg->reset_ecc); + if (ret) + return ret; /* * Attempt to find a generic PHY, then look for an old style @@ -288,7 +301,7 @@ static int dwc2_lowlevel_hw_init(struct dwc2_hsotg *hsotg) * stops device processing. Any resources used on behalf of this device are * freed. */ -static int dwc2_driver_remove(struct platform_device *dev) +static void dwc2_driver_remove(struct platform_device *dev) { struct dwc2_hsotg *hsotg = platform_get_drvdata(dev); struct dwc2_gregs_backup *gr; @@ -338,11 +351,6 @@ static int dwc2_driver_remove(struct platform_device *dev) if (hsotg->ll_hw_enabled) dwc2_lowlevel_hw_disable(hsotg); - - reset_control_assert(hsotg->reset); - reset_control_assert(hsotg->reset_ecc); - - return 0; } /** @@ -746,7 +754,7 @@ static struct platform_driver dwc2_platform_driver = { .pm = &dwc2_dev_pm_ops, }, .probe = dwc2_driver_probe, - .remove = dwc2_driver_remove, + .remove_new = dwc2_driver_remove, .shutdown = dwc2_driver_shutdown, }; diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index d68958e151a7..f6689b731718 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1800,6 +1800,17 @@ static int dwc3_probe(struct platform_device *pdev) dwc_res = *res; dwc_res.start += DWC3_GLOBALS_REGS_START; + if (dev->of_node) { + struct device_node *parent = of_get_parent(dev->of_node); + + if (of_device_is_compatible(parent, "realtek,rtd-dwc3")) { + dwc_res.start -= DWC3_GLOBALS_REGS_START; + dwc_res.start += DWC3_RTK_RTD_GLOBALS_REGS_START; + } + + of_node_put(parent); + } + regs = devm_ioremap_resource(dev, &dwc_res); if (IS_ERR(regs)) return PTR_ERR(regs); @@ -1913,7 +1924,7 @@ err_put_psy: return ret; } -static int dwc3_remove(struct platform_device *pdev) +static void dwc3_remove(struct platform_device *pdev) { struct dwc3 *dwc = platform_get_drvdata(pdev); @@ -1940,8 +1951,6 @@ static int dwc3_remove(struct platform_device *pdev) if (dwc->usb_psy) power_supply_put(dwc->usb_psy); - - return 0; } #ifdef CONFIG_PM @@ -2252,7 +2261,7 @@ MODULE_DEVICE_TABLE(acpi, dwc3_acpi_match); static struct platform_driver dwc3_driver = { .probe = dwc3_probe, - .remove = dwc3_remove, + .remove_new = dwc3_remove, .driver = { .name = "dwc3", .of_match_table = of_match_ptr(of_dwc3_match), diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 1f043c31a096..8b1295e4dcdd 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -84,6 +84,8 @@ #define DWC3_OTG_REGS_START 0xcc00 #define DWC3_OTG_REGS_END 0xccff +#define DWC3_RTK_RTD_GLOBALS_REGS_START 0x8100 + /* Global Registers */ #define DWC3_GSBUSCFG0 0xc100 #define DWC3_GSBUSCFG1 0xc104 diff --git a/drivers/usb/dwc3/dwc3-am62.c b/drivers/usb/dwc3/dwc3-am62.c index cda9458c809b..1755f2f848c5 100644 --- a/drivers/usb/dwc3/dwc3-am62.c +++ b/drivers/usb/dwc3/dwc3-am62.c @@ -275,7 +275,7 @@ static int dwc3_ti_remove_core(struct device *dev, void *c) return 0; } -static int dwc3_ti_remove(struct platform_device *pdev) +static void dwc3_ti_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct dwc3_data *data = platform_get_drvdata(pdev); @@ -294,7 +294,6 @@ static int dwc3_ti_remove(struct platform_device *pdev) pm_runtime_set_suspended(dev); platform_set_drvdata(pdev, NULL); - return 0; } #ifdef CONFIG_PM @@ -362,7 +361,7 @@ MODULE_DEVICE_TABLE(of, dwc3_ti_of_match); static struct platform_driver dwc3_ti_driver = { .probe = dwc3_ti_probe, - .remove = dwc3_ti_remove, + .remove_new = dwc3_ti_remove, .driver = { .name = "dwc3-am62", .pm = DEV_PM_OPS, diff --git a/drivers/usb/dwc3/dwc3-exynos.c b/drivers/usb/dwc3/dwc3-exynos.c index 4be6a873bd07..f882dd647340 100644 --- a/drivers/usb/dwc3/dwc3-exynos.c +++ b/drivers/usb/dwc3/dwc3-exynos.c @@ -128,7 +128,7 @@ vdd33_err: return ret; } -static int dwc3_exynos_remove(struct platform_device *pdev) +static void dwc3_exynos_remove(struct platform_device *pdev) { struct dwc3_exynos *exynos = platform_get_drvdata(pdev); int i; @@ -143,8 +143,6 @@ static int dwc3_exynos_remove(struct platform_device *pdev) regulator_disable(exynos->vdd33); regulator_disable(exynos->vdd10); - - return 0; } static const struct dwc3_exynos_driverdata exynos5250_drvdata = { @@ -234,7 +232,7 @@ static const struct dev_pm_ops dwc3_exynos_dev_pm_ops = { static struct platform_driver dwc3_exynos_driver = { .probe = dwc3_exynos_probe, - .remove = dwc3_exynos_remove, + .remove_new = dwc3_exynos_remove, .driver = { .name = "exynos-dwc3", .of_match_table = exynos_dwc3_match, diff --git a/drivers/usb/dwc3/dwc3-imx8mp.c b/drivers/usb/dwc3/dwc3-imx8mp.c index 174f07614318..8b9a3bb587bf 100644 --- a/drivers/usb/dwc3/dwc3-imx8mp.c +++ b/drivers/usb/dwc3/dwc3-imx8mp.c @@ -266,7 +266,7 @@ disable_hsio_clk: return err; } -static int dwc3_imx8mp_remove(struct platform_device *pdev) +static void dwc3_imx8mp_remove(struct platform_device *pdev) { struct dwc3_imx8mp *dwc3_imx = platform_get_drvdata(pdev); struct device *dev = &pdev->dev; @@ -280,8 +280,6 @@ static int dwc3_imx8mp_remove(struct platform_device *pdev) pm_runtime_disable(dev); pm_runtime_put_noidle(dev); platform_set_drvdata(pdev, NULL); - - return 0; } static int __maybe_unused dwc3_imx8mp_suspend(struct dwc3_imx8mp *dwc3_imx, @@ -411,7 +409,7 @@ MODULE_DEVICE_TABLE(of, dwc3_imx8mp_of_match); static struct platform_driver dwc3_imx8mp_driver = { .probe = dwc3_imx8mp_probe, - .remove = dwc3_imx8mp_remove, + .remove_new = dwc3_imx8mp_remove, .driver = { .name = "imx8mp-dwc3", .pm = &dwc3_imx8mp_dev_pm_ops, diff --git a/drivers/usb/dwc3/dwc3-keystone.c b/drivers/usb/dwc3/dwc3-keystone.c index 1317959294e6..0a09aedc2573 100644 --- a/drivers/usb/dwc3/dwc3-keystone.c +++ b/drivers/usb/dwc3/dwc3-keystone.c @@ -181,7 +181,7 @@ static int kdwc3_remove_core(struct device *dev, void *c) return 0; } -static int kdwc3_remove(struct platform_device *pdev) +static void kdwc3_remove(struct platform_device *pdev) { struct dwc3_keystone *kdwc = platform_get_drvdata(pdev); struct device_node *node = pdev->dev.of_node; @@ -198,8 +198,6 @@ static int kdwc3_remove(struct platform_device *pdev) phy_pm_runtime_put_sync(kdwc->usb3_phy); platform_set_drvdata(pdev, NULL); - - return 0; } static const struct of_device_id kdwc3_of_match[] = { @@ -211,7 +209,7 @@ MODULE_DEVICE_TABLE(of, kdwc3_of_match); static struct platform_driver kdwc3_driver = { .probe = kdwc3_probe, - .remove = kdwc3_remove, + .remove_new = kdwc3_remove, .driver = { .name = "keystone-dwc3", .of_match_table = kdwc3_of_match, diff --git a/drivers/usb/dwc3/dwc3-meson-g12a.c b/drivers/usb/dwc3/dwc3-meson-g12a.c index b282ad0e69c6..e99c7489dba0 100644 --- a/drivers/usb/dwc3/dwc3-meson-g12a.c +++ b/drivers/usb/dwc3/dwc3-meson-g12a.c @@ -140,7 +140,6 @@ static const char * const meson_a1_phy_names[] = { struct dwc3_meson_g12a; struct dwc3_meson_g12a_drvdata { - bool otg_switch_supported; bool otg_phy_host_port_disable; struct clk_bulk_data *clks; int num_clks; @@ -189,7 +188,6 @@ static int dwc3_meson_gxl_usb_post_init(struct dwc3_meson_g12a *priv); */ static const struct dwc3_meson_g12a_drvdata gxl_drvdata = { - .otg_switch_supported = true, .otg_phy_host_port_disable = true, .clks = meson_gxl_clocks, .num_clks = ARRAY_SIZE(meson_g12a_clocks), @@ -203,7 +201,6 @@ static const struct dwc3_meson_g12a_drvdata gxl_drvdata = { }; static const struct dwc3_meson_g12a_drvdata gxm_drvdata = { - .otg_switch_supported = true, .otg_phy_host_port_disable = true, .clks = meson_gxl_clocks, .num_clks = ARRAY_SIZE(meson_g12a_clocks), @@ -217,7 +214,6 @@ static const struct dwc3_meson_g12a_drvdata gxm_drvdata = { }; static const struct dwc3_meson_g12a_drvdata axg_drvdata = { - .otg_switch_supported = true, .clks = meson_gxl_clocks, .num_clks = ARRAY_SIZE(meson_gxl_clocks), .phy_names = meson_a1_phy_names, @@ -230,7 +226,6 @@ static const struct dwc3_meson_g12a_drvdata axg_drvdata = { }; static const struct dwc3_meson_g12a_drvdata g12a_drvdata = { - .otg_switch_supported = true, .clks = meson_g12a_clocks, .num_clks = ARRAY_SIZE(meson_g12a_clocks), .phy_names = meson_g12a_phy_names, @@ -242,7 +237,6 @@ static const struct dwc3_meson_g12a_drvdata g12a_drvdata = { }; static const struct dwc3_meson_g12a_drvdata a1_drvdata = { - .otg_switch_supported = false, .clks = meson_a1_clocks, .num_clks = ARRAY_SIZE(meson_a1_clocks), .phy_names = meson_a1_phy_names, @@ -307,7 +301,7 @@ static int dwc3_meson_g12a_usb2_init_phy(struct dwc3_meson_g12a *priv, int i, U2P_R0_POWER_ON_RESET, U2P_R0_POWER_ON_RESET); - if (priv->drvdata->otg_switch_supported && i == USB2_OTG_PHY) { + if (i == USB2_OTG_PHY) { regmap_update_bits(priv->u2p_regmap[i], U2P_R0, U2P_R0_ID_PULLUP | U2P_R0_DRV_VBUS, U2P_R0_ID_PULLUP | U2P_R0_DRV_VBUS); @@ -490,7 +484,7 @@ static int dwc3_meson_g12a_otg_mode_set(struct dwc3_meson_g12a *priv, { int ret; - if (!priv->drvdata->otg_switch_supported || !priv->phys[USB2_OTG_PHY]) + if (!priv->phys[USB2_OTG_PHY]) return -EINVAL; if (mode == PHY_MODE_USB_HOST) @@ -589,9 +583,6 @@ static int dwc3_meson_g12a_otg_init(struct platform_device *pdev, int ret, irq; struct device *dev = &pdev->dev; - if (!priv->drvdata->otg_switch_supported) - return 0; - if (priv->otg_mode == USB_DR_MODE_OTG) { /* Ack irq before registering */ regmap_update_bits(priv->usb_glue_regmap, USB_R5, @@ -805,7 +796,7 @@ static int dwc3_meson_g12a_probe(struct platform_device *pdev) ret = dwc3_meson_g12a_otg_init(pdev, priv); if (ret) - goto err_phys_power; + goto err_plat_depopulate; pm_runtime_set_active(dev); pm_runtime_enable(dev); @@ -813,6 +804,9 @@ static int dwc3_meson_g12a_probe(struct platform_device *pdev) return 0; +err_plat_depopulate: + of_platform_depopulate(dev); + err_phys_power: for (i = 0 ; i < PHY_COUNT ; ++i) phy_power_off(priv->phys[i]); @@ -835,14 +829,13 @@ err_disable_clks: return ret; } -static int dwc3_meson_g12a_remove(struct platform_device *pdev) +static void dwc3_meson_g12a_remove(struct platform_device *pdev) { struct dwc3_meson_g12a *priv = platform_get_drvdata(pdev); struct device *dev = &pdev->dev; int i; - if (priv->drvdata->otg_switch_supported) - usb_role_switch_unregister(priv->role_switch); + usb_role_switch_unregister(priv->role_switch); of_platform_depopulate(dev); @@ -859,8 +852,6 @@ static int dwc3_meson_g12a_remove(struct platform_device *pdev) clk_bulk_disable_unprepare(priv->drvdata->num_clks, priv->drvdata->clks); - - return 0; } static int __maybe_unused dwc3_meson_g12a_runtime_suspend(struct device *dev) @@ -971,7 +962,7 @@ MODULE_DEVICE_TABLE(of, dwc3_meson_g12a_match); static struct platform_driver dwc3_meson_g12a_driver = { .probe = dwc3_meson_g12a_probe, - .remove = dwc3_meson_g12a_remove, + .remove_new = dwc3_meson_g12a_remove, .driver = { .name = "dwc3-meson-g12a", .of_match_table = dwc3_meson_g12a_match, diff --git a/drivers/usb/dwc3/dwc3-of-simple.c b/drivers/usb/dwc3/dwc3-of-simple.c index 71fd620c5161..7e6ad8fe61a5 100644 --- a/drivers/usb/dwc3/dwc3-of-simple.c +++ b/drivers/usb/dwc3/dwc3-of-simple.c @@ -112,13 +112,11 @@ static void __dwc3_of_simple_teardown(struct dwc3_of_simple *simple) pm_runtime_set_suspended(simple->dev); } -static int dwc3_of_simple_remove(struct platform_device *pdev) +static void dwc3_of_simple_remove(struct platform_device *pdev) { struct dwc3_of_simple *simple = platform_get_drvdata(pdev); __dwc3_of_simple_teardown(simple); - - return 0; } static void dwc3_of_simple_shutdown(struct platform_device *pdev) @@ -183,7 +181,7 @@ MODULE_DEVICE_TABLE(of, of_dwc3_simple_match); static struct platform_driver dwc3_of_simple_driver = { .probe = dwc3_of_simple_probe, - .remove = dwc3_of_simple_remove, + .remove_new = dwc3_of_simple_remove, .shutdown = dwc3_of_simple_shutdown, .driver = { .name = "dwc3-of-simple", diff --git a/drivers/usb/dwc3/dwc3-omap.c b/drivers/usb/dwc3/dwc3-omap.c index efaf0db595f4..d5c77db4daa9 100644 --- a/drivers/usb/dwc3/dwc3-omap.c +++ b/drivers/usb/dwc3/dwc3-omap.c @@ -534,7 +534,7 @@ err1: return ret; } -static int dwc3_omap_remove(struct platform_device *pdev) +static void dwc3_omap_remove(struct platform_device *pdev) { struct dwc3_omap *omap = platform_get_drvdata(pdev); @@ -543,8 +543,6 @@ static int dwc3_omap_remove(struct platform_device *pdev) of_platform_depopulate(omap->dev); pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); - - return 0; } static const struct of_device_id of_dwc3_match[] = { @@ -611,7 +609,7 @@ static const struct dev_pm_ops dwc3_omap_dev_pm_ops = { static struct platform_driver dwc3_omap_driver = { .probe = dwc3_omap_probe, - .remove = dwc3_omap_remove, + .remove_new = dwc3_omap_remove, .driver = { .name = "omap-dwc3", .of_match_table = of_dwc3_match, diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index 79b22abf9727..3de43df6bbe8 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -167,7 +167,8 @@ static int dwc3_qcom_register_extcon(struct dwc3_qcom *qcom) qcom->edev = extcon_get_edev_by_phandle(dev, 0); if (IS_ERR(qcom->edev)) - return PTR_ERR(qcom->edev); + return dev_err_probe(dev, PTR_ERR(qcom->edev), + "Failed to get extcon\n"); qcom->vbus_nb.notifier_call = dwc3_qcom_vbus_notifier; @@ -252,16 +253,14 @@ static int dwc3_qcom_interconnect_init(struct dwc3_qcom *qcom) qcom->icc_path_ddr = of_icc_get(dev, "usb-ddr"); if (IS_ERR(qcom->icc_path_ddr)) { - dev_err(dev, "failed to get usb-ddr path: %ld\n", - PTR_ERR(qcom->icc_path_ddr)); - return PTR_ERR(qcom->icc_path_ddr); + return dev_err_probe(dev, PTR_ERR(qcom->icc_path_ddr), + "failed to get usb-ddr path\n"); } qcom->icc_path_apps = of_icc_get(dev, "apps-usb"); if (IS_ERR(qcom->icc_path_apps)) { - dev_err(dev, "failed to get apps-usb path: %ld\n", - PTR_ERR(qcom->icc_path_apps)); - ret = PTR_ERR(qcom->icc_path_apps); + ret = dev_err_probe(dev, PTR_ERR(qcom->icc_path_apps), + "failed to get apps-usb path\n"); goto put_path_ddr; } @@ -800,6 +799,7 @@ static int dwc3_qcom_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct dwc3_qcom *qcom; struct resource *res, *parent_res = NULL; + struct resource local_res; int ret, i; bool ignore_pipe_clk; bool wakeup_source; @@ -821,9 +821,8 @@ static int dwc3_qcom_probe(struct platform_device *pdev) qcom->resets = devm_reset_control_array_get_optional_exclusive(dev); if (IS_ERR(qcom->resets)) { - ret = PTR_ERR(qcom->resets); - dev_err(&pdev->dev, "failed to get resets, err=%d\n", ret); - return ret; + return dev_err_probe(&pdev->dev, PTR_ERR(qcom->resets), + "failed to get resets\n"); } ret = reset_control_assert(qcom->resets); @@ -842,7 +841,7 @@ static int dwc3_qcom_probe(struct platform_device *pdev) ret = dwc3_qcom_clk_init(qcom, of_clk_get_parent_count(np)); if (ret) { - dev_err(dev, "failed to get clocks\n"); + dev_err_probe(dev, ret, "failed to get clocks\n"); goto reset_assert; } @@ -851,9 +850,8 @@ static int dwc3_qcom_probe(struct platform_device *pdev) if (np) { parent_res = res; } else { - parent_res = kmemdup(res, sizeof(struct resource), GFP_KERNEL); - if (!parent_res) - return -ENOMEM; + memcpy(&local_res, res, sizeof(struct resource)); + parent_res = &local_res; parent_res->start = res->start + qcom->acpi_pdata->qscratch_base_offset; @@ -865,9 +863,10 @@ static int dwc3_qcom_probe(struct platform_device *pdev) if (IS_ERR_OR_NULL(qcom->urs_usb)) { dev_err(dev, "failed to create URS USB platdev\n"); if (!qcom->urs_usb) - return -ENODEV; + ret = -ENODEV; else - return PTR_ERR(qcom->urs_usb); + ret = PTR_ERR(qcom->urs_usb); + goto clk_disable; } } } @@ -947,14 +946,18 @@ reset_assert: return ret; } -static int dwc3_qcom_remove(struct platform_device *pdev) +static void dwc3_qcom_remove(struct platform_device *pdev) { struct dwc3_qcom *qcom = platform_get_drvdata(pdev); + struct device_node *np = pdev->dev.of_node; struct device *dev = &pdev->dev; int i; device_remove_software_node(&qcom->dwc3->dev); - of_platform_depopulate(dev); + if (np) + of_platform_depopulate(&pdev->dev); + else + platform_device_put(pdev); for (i = qcom->num_clocks - 1; i >= 0; i--) { clk_disable_unprepare(qcom->clks[i]); @@ -967,8 +970,6 @@ static int dwc3_qcom_remove(struct platform_device *pdev) pm_runtime_allow(dev); pm_runtime_disable(dev); - - return 0; } static int __maybe_unused dwc3_qcom_pm_suspend(struct device *dev) @@ -1061,7 +1062,7 @@ MODULE_DEVICE_TABLE(acpi, dwc3_qcom_acpi_match); static struct platform_driver dwc3_qcom_driver = { .probe = dwc3_qcom_probe, - .remove = dwc3_qcom_remove, + .remove_new = dwc3_qcom_remove, .driver = { .name = "dwc3-qcom", .pm = &dwc3_qcom_dev_pm_ops, diff --git a/drivers/usb/dwc3/dwc3-st.c b/drivers/usb/dwc3/dwc3-st.c index fea5290de83f..211360eee95a 100644 --- a/drivers/usb/dwc3/dwc3-st.c +++ b/drivers/usb/dwc3/dwc3-st.c @@ -305,7 +305,7 @@ undo_platform_dev_alloc: return ret; } -static int st_dwc3_remove(struct platform_device *pdev) +static void st_dwc3_remove(struct platform_device *pdev) { struct st_dwc3 *dwc3_data = platform_get_drvdata(pdev); @@ -313,8 +313,6 @@ static int st_dwc3_remove(struct platform_device *pdev) reset_control_assert(dwc3_data->rstc_pwrdn); reset_control_assert(dwc3_data->rstc_rst); - - return 0; } #ifdef CONFIG_PM_SLEEP @@ -364,7 +362,7 @@ MODULE_DEVICE_TABLE(of, st_dwc3_match); static struct platform_driver st_dwc3_driver = { .probe = st_dwc3_probe, - .remove = st_dwc3_remove, + .remove_new = st_dwc3_remove, .driver = { .name = "usb-st-dwc3", .of_match_table = st_dwc3_match, diff --git a/drivers/usb/dwc3/dwc3-xilinx.c b/drivers/usb/dwc3/dwc3-xilinx.c index 2c36f97652ca..19307d24f3a0 100644 --- a/drivers/usb/dwc3/dwc3-xilinx.c +++ b/drivers/usb/dwc3/dwc3-xilinx.c @@ -305,7 +305,7 @@ err_clk_put: return ret; } -static int dwc3_xlnx_remove(struct platform_device *pdev) +static void dwc3_xlnx_remove(struct platform_device *pdev) { struct dwc3_xlnx *priv_data = platform_get_drvdata(pdev); struct device *dev = &pdev->dev; @@ -318,8 +318,6 @@ static int dwc3_xlnx_remove(struct platform_device *pdev) pm_runtime_disable(dev); pm_runtime_put_noidle(dev); pm_runtime_set_suspended(dev); - - return 0; } static int __maybe_unused dwc3_xlnx_runtime_suspend(struct device *dev) @@ -388,7 +386,7 @@ static const struct dev_pm_ops dwc3_xlnx_dev_pm_ops = { static struct platform_driver dwc3_xlnx_driver = { .probe = dwc3_xlnx_probe, - .remove = dwc3_xlnx_remove, + .remove_new = dwc3_xlnx_remove, .driver = { .name = "dwc3-xilinx", .of_match_table = dwc3_xlnx_of_match, diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 953b752a5052..b94243237293 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -1207,5 +1207,8 @@ void dwc3_ep0_interrupt(struct dwc3 *dwc, dep->flags &= ~DWC3_EP_TRANSFER_STARTED; } break; + default: + dev_err(dwc->dev, "unknown endpoint event %d\n", event->endpoint_event); + break; } } diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index b78599dd705c..5fd067151fbf 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2703,13 +2703,17 @@ static int dwc3_gadget_soft_disconnect(struct dwc3 *dwc) static int dwc3_gadget_soft_connect(struct dwc3 *dwc) { + int ret; + /* * In the Synopsys DWC_usb31 1.90a programming guide section * 4.1.9, it specifies that for a reconnect after a * device-initiated disconnect requires a core soft reset * (DCTL.CSftRst) before enabling the run/stop bit. */ - dwc3_core_soft_reset(dwc); + ret = dwc3_core_soft_reset(dwc); + if (ret) + return ret; dwc3_event_buffers_setup(dwc); __dwc3_gadget_start(dwc); @@ -2744,7 +2748,9 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) ret = pm_runtime_get_sync(dwc->dev); if (!ret || ret < 0) { pm_runtime_put(dwc->dev); - return 0; + if (ret < 0) + pm_runtime_set_suspended(dwc->dev); + return ret; } if (dwc->pullups_connected == is_on) { @@ -3809,6 +3815,9 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, break; case DWC3_DEPEVT_RXTXFIFOEVT: break; + default: + dev_err(dwc->dev, "unknown endpoint event %d\n", event->endpoint_event); + break; } } diff --git a/drivers/usb/fotg210/fotg210-core.c b/drivers/usb/fotg210/fotg210-core.c index cb75464ab290..958fc40eae86 100644 --- a/drivers/usb/fotg210/fotg210-core.c +++ b/drivers/usb/fotg210/fotg210-core.c @@ -165,7 +165,7 @@ static int fotg210_probe(struct platform_device *pdev) return ret; } -static int fotg210_remove(struct platform_device *pdev) +static void fotg210_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; enum usb_dr_mode mode; @@ -176,8 +176,6 @@ static int fotg210_remove(struct platform_device *pdev) fotg210_udc_remove(pdev); else fotg210_hcd_remove(pdev); - - return 0; } #ifdef CONFIG_OF @@ -196,7 +194,7 @@ static struct platform_driver fotg210_driver = { .of_match_table = of_match_ptr(fotg210_of_match), }, .probe = fotg210_probe, - .remove = fotg210_remove, + .remove_new = fotg210_remove, }; static int __init fotg210_init(void) diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c index 9f6b10134121..ea85e2c701a1 100644 --- a/drivers/usb/gadget/function/f_hid.c +++ b/drivers/usb/gadget/function/f_hid.c @@ -23,7 +23,11 @@ #define HIDG_MINORS 4 static int major, minors; -static struct class *hidg_class; + +static const struct class hidg_class = { + .name = "hidg", +}; + static DEFINE_IDA(hidg_ida); static DEFINE_MUTEX(hidg_ida_lock); /* protects access to hidg_ida */ @@ -1272,7 +1276,7 @@ static struct usb_function *hidg_alloc(struct usb_function_instance *fi) device_initialize(&hidg->dev); hidg->dev.release = hidg_release; - hidg->dev.class = hidg_class; + hidg->dev.class = &hidg_class; hidg->dev.devt = MKDEV(major, opts->minor); ret = dev_set_name(&hidg->dev, "hidg%d", opts->minor); if (ret) @@ -1325,17 +1329,13 @@ int ghid_setup(struct usb_gadget *g, int count) int status; dev_t dev; - hidg_class = class_create("hidg"); - if (IS_ERR(hidg_class)) { - status = PTR_ERR(hidg_class); - hidg_class = NULL; + status = class_register(&hidg_class); + if (status) return status; - } status = alloc_chrdev_region(&dev, 0, count, "hidg"); if (status) { - class_destroy(hidg_class); - hidg_class = NULL; + class_unregister(&hidg_class); return status; } @@ -1352,6 +1352,5 @@ void ghid_cleanup(void) major = minors = 0; } - class_destroy(hidg_class); - hidg_class = NULL; + class_unregister(&hidg_class); } diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c index 3a30feb47073..da07e45ae6df 100644 --- a/drivers/usb/gadget/function/f_mass_storage.c +++ b/drivers/usb/gadget/function/f_mass_storage.c @@ -2857,7 +2857,7 @@ int fsg_common_create_lun(struct fsg_common *common, struct fsg_lun_config *cfg, const char **name_pfx) { struct fsg_lun *lun; - char *pathbuf, *p; + char *pathbuf = NULL, *p = "(no medium)"; int rc = -ENOMEM; if (id >= ARRAY_SIZE(common->luns)) @@ -2907,12 +2907,9 @@ int fsg_common_create_lun(struct fsg_common *common, struct fsg_lun_config *cfg, rc = fsg_lun_open(lun, cfg->filename); if (rc) goto error_lun; - } - pathbuf = kmalloc(PATH_MAX, GFP_KERNEL); - p = "(no medium)"; - if (fsg_lun_is_open(lun)) { p = "(error)"; + pathbuf = kmalloc(PATH_MAX, GFP_KERNEL); if (pathbuf) { p = file_path(lun->filp, pathbuf, PATH_MAX); if (IS_ERR(p)) @@ -2931,7 +2928,6 @@ int fsg_common_create_lun(struct fsg_common *common, struct fsg_lun_config *cfg, error_lun: if (device_is_registered(&lun->dev)) device_unregister(&lun->dev); - fsg_lun_close(lun); common->luns[id] = NULL; error_sysfs: kfree(lun); diff --git a/drivers/usb/gadget/function/f_printer.c b/drivers/usb/gadget/function/f_printer.c index 28db3e336e7d..076dd4c1be96 100644 --- a/drivers/usb/gadget/function/f_printer.c +++ b/drivers/usb/gadget/function/f_printer.c @@ -54,7 +54,10 @@ #define DEFAULT_Q_LEN 10 /* same as legacy g_printer gadget */ static int major, minors; -static struct class *usb_gadget_class; +static const struct class usb_gadget_class = { + .name = "usb_printer_gadget", +}; + static DEFINE_IDA(printer_ida); static DEFINE_MUTEX(printer_ida_lock); /* protects access do printer_ida */ @@ -1120,7 +1123,7 @@ autoconf_fail: /* Setup the sysfs files for the printer gadget. */ devt = MKDEV(major, dev->minor); - pdev = device_create(usb_gadget_class, NULL, devt, + pdev = device_create(&usb_gadget_class, NULL, devt, NULL, "g_printer%d", dev->minor); if (IS_ERR(pdev)) { ERROR(dev, "Failed to create device: g_printer\n"); @@ -1143,7 +1146,7 @@ autoconf_fail: return 0; fail_cdev_add: - device_destroy(usb_gadget_class, devt); + device_destroy(&usb_gadget_class, devt); fail_rx_reqs: while (!list_empty(&dev->rx_reqs)) { @@ -1211,8 +1214,8 @@ static ssize_t f_printer_opts_pnp_string_show(struct config_item *item, if (!opts->pnp_string) goto unlock; - result = strlcpy(page, opts->pnp_string, PAGE_SIZE); - if (result >= PAGE_SIZE) { + result = strscpy(page, opts->pnp_string, PAGE_SIZE); + if (result < 1) { result = PAGE_SIZE; } else if (page[result - 1] != '\n' && result + 1 < PAGE_SIZE) { page[result++] = '\n'; @@ -1410,7 +1413,7 @@ static void printer_func_unbind(struct usb_configuration *c, dev = func_to_printer(f); - device_destroy(usb_gadget_class, MKDEV(major, dev->minor)); + device_destroy(&usb_gadget_class, MKDEV(major, dev->minor)); /* Remove Character Device */ cdev_del(&dev->printer_cdev); @@ -1512,19 +1515,14 @@ static int gprinter_setup(int count) int status; dev_t devt; - usb_gadget_class = class_create("usb_printer_gadget"); - if (IS_ERR(usb_gadget_class)) { - status = PTR_ERR(usb_gadget_class); - usb_gadget_class = NULL; - pr_err("unable to create usb_gadget class %d\n", status); + status = class_register(&usb_gadget_class); + if (status) return status; - } status = alloc_chrdev_region(&devt, 0, count, "USB printer gadget"); if (status) { pr_err("alloc_chrdev_region %d\n", status); - class_destroy(usb_gadget_class); - usb_gadget_class = NULL; + class_unregister(&usb_gadget_class); return status; } @@ -1540,6 +1538,5 @@ static void gprinter_cleanup(void) unregister_chrdev_region(MKDEV(major, 0), minors); major = minors = 0; } - class_destroy(usb_gadget_class); - usb_gadget_class = NULL; + class_unregister(&usb_gadget_class); } diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c index a0ca47fbff0f..1115396b46a0 100644 --- a/drivers/usb/gadget/function/u_serial.c +++ b/drivers/usb/gadget/function/u_serial.c @@ -539,16 +539,20 @@ static int gs_alloc_requests(struct usb_ep *ep, struct list_head *head, static int gs_start_io(struct gs_port *port) { struct list_head *head = &port->read_pool; - struct usb_ep *ep = port->port_usb->out; + struct usb_ep *ep; int status; unsigned started; + if (!port->port_usb || !port->port.tty) + return -EIO; + /* Allocate RX and TX I/O buffers. We can't easily do this much * earlier (with GFP_KERNEL) because the requests are coupled to * endpoints, as are the packet sizes we'll be using. Different * configurations may use different endpoints with a given port; * and high speed vs full speed changes packet sizes too. */ + ep = port->port_usb->out; status = gs_alloc_requests(ep, head, gs_read_complete, &port->read_allocated); if (status) @@ -916,8 +920,11 @@ static void __gs_console_push(struct gs_console *cons) } req->length = size; + + spin_unlock_irq(&cons->lock); if (usb_ep_queue(ep, req, GFP_ATOMIC)) req->length = 0; + spin_lock_irq(&cons->lock); } static void gs_console_work(struct work_struct *work) @@ -1420,10 +1427,19 @@ EXPORT_SYMBOL_GPL(gserial_disconnect); void gserial_suspend(struct gserial *gser) { - struct gs_port *port = gser->ioport; + struct gs_port *port; unsigned long flags; - spin_lock_irqsave(&port->port_lock, flags); + spin_lock_irqsave(&serial_port_lock, flags); + port = gser->ioport; + + if (!port) { + spin_unlock_irqrestore(&serial_port_lock, flags); + return; + } + + spin_lock(&port->port_lock); + spin_unlock(&serial_port_lock); port->suspended = true; spin_unlock_irqrestore(&port->port_lock, flags); } diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c index dd1c6b2ca7c6..91af3b1ef0d4 100644 --- a/drivers/usb/gadget/function/uvc_video.c +++ b/drivers/usb/gadget/function/uvc_video.c @@ -382,9 +382,12 @@ static void uvcg_video_pump(struct work_struct *work) { struct uvc_video *video = container_of(work, struct uvc_video, pump); struct uvc_video_queue *queue = &video->queue; + /* video->max_payload_size is only set when using bulk transfer */ + bool is_bulk = video->max_payload_size; struct usb_request *req = NULL; struct uvc_buffer *buf; unsigned long flags; + bool buf_done; int ret; while (video->ep->enabled) { @@ -408,20 +411,47 @@ static void uvcg_video_pump(struct work_struct *work) */ spin_lock_irqsave(&queue->irqlock, flags); buf = uvcg_queue_head(queue); - if (buf == NULL) { + + if (buf != NULL) { + video->encode(req, video, buf); + buf_done = buf->state == UVC_BUF_STATE_DONE; + } else if (!(queue->flags & UVC_QUEUE_DISCONNECTED) && !is_bulk) { + /* + * No video buffer available; the queue is still connected and + * we're transferring over ISOC. Queue a 0 length request to + * prevent missed ISOC transfers. + */ + req->length = 0; + buf_done = false; + } else { + /* + * Either the queue has been disconnected or no video buffer + * available for bulk transfer. Either way, stop processing + * further. + */ spin_unlock_irqrestore(&queue->irqlock, flags); break; } - video->encode(req, video, buf); - /* - * With usb3 we have more requests. This will decrease the - * interrupt load to a quarter but also catches the corner - * cases, which needs to be handled. + * With USB3 handling more requests at a higher speed, we can't + * afford to generate an interrupt for every request. Decide to + * interrupt: + * + * - When no more requests are available in the free queue, as + * this may be our last chance to refill the endpoint's + * request queue. + * + * - When this is request is the last request for the video + * buffer, as we want to start sending the next video buffer + * ASAP in case it doesn't get started already in the next + * iteration of this loop. + * + * - Four times over the length of the requests queue (as + * indicated by video->uvc_num_requests), as a trade-off + * between latency and interrupt load. */ - if (list_empty(&video->req_free) || - buf->state == UVC_BUF_STATE_DONE || + if (list_empty(&video->req_free) || buf_done || !(video->req_int_count % DIV_ROUND_UP(video->uvc_num_requests, 4))) { video->req_int_count = 0; @@ -441,8 +471,7 @@ static void uvcg_video_pump(struct work_struct *work) /* Endpoint now owns the request */ req = NULL; - if (buf->state != UVC_BUF_STATE_DONE) - video->req_int_count++; + video->req_int_count++; } if (!req) @@ -527,4 +556,3 @@ int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc) V4L2_BUF_TYPE_VIDEO_OUTPUT, &video->mutex); return 0; } - diff --git a/drivers/usb/gadget/legacy/g_ffs.c b/drivers/usb/gadget/legacy/g_ffs.c index 097854683e5b..a9544fea8723 100644 --- a/drivers/usb/gadget/legacy/g_ffs.c +++ b/drivers/usb/gadget/legacy/g_ffs.c @@ -389,8 +389,10 @@ static int gfs_bind(struct usb_composite_dev *cdev) struct usb_descriptor_header *usb_desc; usb_desc = usb_otg_descriptor_alloc(cdev->gadget); - if (!usb_desc) + if (!usb_desc) { + ret = -ENOMEM; goto error_rndis; + } usb_otg_descriptor_init(cdev->gadget, usb_desc); gfs_otg_desc[0] = usb_desc; gfs_otg_desc[1] = NULL; diff --git a/drivers/usb/gadget/legacy/hid.c b/drivers/usb/gadget/legacy/hid.c index 133daf88162e..4ca67b2f8f24 100644 --- a/drivers/usb/gadget/legacy/hid.c +++ b/drivers/usb/gadget/legacy/hid.c @@ -237,7 +237,7 @@ static int hidg_plat_driver_probe(struct platform_device *pdev) return 0; } -static int hidg_plat_driver_remove(struct platform_device *pdev) +static void hidg_plat_driver_remove(struct platform_device *pdev) { struct hidg_func_node *e, *n; @@ -245,8 +245,6 @@ static int hidg_plat_driver_remove(struct platform_device *pdev) list_del(&e->node); kfree(e); } - - return 0; } @@ -263,7 +261,7 @@ static struct usb_composite_driver hidg_driver = { }; static struct platform_driver hidg_plat_driver = { - .remove = hidg_plat_driver_remove, + .remove_new = hidg_plat_driver_remove, .driver = { .name = "hidg", }, diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index 83cae6bb12eb..aae1787320d4 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -463,6 +463,8 @@ config USB_ASPEED_UDC source "drivers/usb/gadget/udc/aspeed-vhub/Kconfig" +source "drivers/usb/gadget/udc/cdns2/Kconfig" + # # LAST -- dummy/emulated controller # diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile index ee569f63c74a..b52f93e9c61d 100644 --- a/drivers/usb/gadget/udc/Makefile +++ b/drivers/usb/gadget/udc/Makefile @@ -42,3 +42,4 @@ obj-$(CONFIG_USB_ASPEED_VHUB) += aspeed-vhub/ obj-$(CONFIG_USB_ASPEED_UDC) += aspeed_udc.o obj-$(CONFIG_USB_BDC_UDC) += bdc/ obj-$(CONFIG_USB_MAX3420_UDC) += max3420_udc.o +obj-$(CONFIG_USB_CDNS2_UDC) += cdns2/ diff --git a/drivers/usb/gadget/udc/aspeed-vhub/core.c b/drivers/usb/gadget/udc/aspeed-vhub/core.c index 86398a04a012..16f2db8c4a2b 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/core.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/core.c @@ -253,14 +253,14 @@ void ast_vhub_init_hw(struct ast_vhub *vhub) vhub->regs + AST_VHUB_IER); } -static int ast_vhub_remove(struct platform_device *pdev) +static void ast_vhub_remove(struct platform_device *pdev) { struct ast_vhub *vhub = platform_get_drvdata(pdev); unsigned long flags; int i; if (!vhub || !vhub->regs) - return 0; + return; /* Remove devices */ for (i = 0; i < vhub->max_ports; i++) @@ -289,8 +289,6 @@ static int ast_vhub_remove(struct platform_device *pdev) vhub->ep0_bufs, vhub->ep0_bufs_dma); vhub->ep0_bufs = NULL; - - return 0; } static int ast_vhub_probe(struct platform_device *pdev) @@ -431,7 +429,7 @@ MODULE_DEVICE_TABLE(of, ast_vhub_dt_ids); static struct platform_driver ast_vhub_driver = { .probe = ast_vhub_probe, - .remove = ast_vhub_remove, + .remove_new = ast_vhub_remove, .driver = { .name = KBUILD_MODNAME, .of_match_table = ast_vhub_dt_ids, diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c index 53ca38c4b3ec..6c0ed3fa5eb1 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.c +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -2369,7 +2369,7 @@ static int usba_udc_probe(struct platform_device *pdev) return 0; } -static int usba_udc_remove(struct platform_device *pdev) +static void usba_udc_remove(struct platform_device *pdev) { struct usba_udc *udc; int i; @@ -2382,8 +2382,6 @@ static int usba_udc_remove(struct platform_device *pdev) for (i = 1; i < udc->num_ep; i++) usba_ep_cleanup_debugfs(&udc->usba_ep[i]); usba_cleanup_debugfs(udc); - - return 0; } #ifdef CONFIG_PM_SLEEP @@ -2450,7 +2448,7 @@ static SIMPLE_DEV_PM_OPS(usba_udc_pm_ops, usba_udc_suspend, usba_udc_resume); static struct platform_driver udc_driver = { .probe = usba_udc_probe, - .remove = usba_udc_remove, + .remove_new = usba_udc_remove, .driver = { .name = "atmel_usba_udc", .pm = &usba_udc_pm_ops, diff --git a/drivers/usb/gadget/udc/bcm63xx_udc.c b/drivers/usb/gadget/udc/bcm63xx_udc.c index a3055dd4acfb..da7011d906e0 100644 --- a/drivers/usb/gadget/udc/bcm63xx_udc.c +++ b/drivers/usb/gadget/udc/bcm63xx_udc.c @@ -2354,7 +2354,7 @@ report_request_failure: * bcm63xx_udc_remove - Remove the device from the system. * @pdev: Platform device struct from the bcm63xx BSP code. */ -static int bcm63xx_udc_remove(struct platform_device *pdev) +static void bcm63xx_udc_remove(struct platform_device *pdev) { struct bcm63xx_udc *udc = platform_get_drvdata(pdev); @@ -2363,13 +2363,11 @@ static int bcm63xx_udc_remove(struct platform_device *pdev) BUG_ON(udc->driver); bcm63xx_uninit_udc_hw(udc); - - return 0; } static struct platform_driver bcm63xx_udc_driver = { .probe = bcm63xx_udc_probe, - .remove = bcm63xx_udc_remove, + .remove_new = bcm63xx_udc_remove, .driver = { .name = DRV_MODULE_NAME, }, diff --git a/drivers/usb/gadget/udc/bdc/bdc_core.c b/drivers/usb/gadget/udc/bdc/bdc_core.c index 9849e0c86e23..35a652807fca 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_core.c +++ b/drivers/usb/gadget/udc/bdc/bdc_core.c @@ -583,7 +583,7 @@ disable_clk: return ret; } -static int bdc_remove(struct platform_device *pdev) +static void bdc_remove(struct platform_device *pdev) { struct bdc *bdc; @@ -593,7 +593,6 @@ static int bdc_remove(struct platform_device *pdev) bdc_hw_exit(bdc); bdc_phy_exit(bdc); clk_disable_unprepare(bdc->clk); - return 0; } #ifdef CONFIG_PM_SLEEP @@ -648,7 +647,7 @@ static struct platform_driver bdc_driver = { .of_match_table = bdc_of_match, }, .probe = bdc_probe, - .remove = bdc_remove, + .remove_new = bdc_remove, }; module_platform_driver(bdc_driver); diff --git a/drivers/usb/gadget/udc/cdns2/Kconfig b/drivers/usb/gadget/udc/cdns2/Kconfig new file mode 100644 index 000000000000..c07d353903ea --- /dev/null +++ b/drivers/usb/gadget/udc/cdns2/Kconfig @@ -0,0 +1,11 @@ +config USB_CDNS2_UDC + tristate "Cadence USBHS Device Controller" + depends on USB_PCI && ACPI && HAS_DMA + help + Cadence USBHS Device controller is a PCI based USB peripheral + controller which supports both full and high speed USB 2.0 + data transfers. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "cdns2-udc-pci.ko" and to + force all gadget drivers to also be dynamically linked. diff --git a/drivers/usb/gadget/udc/cdns2/Makefile b/drivers/usb/gadget/udc/cdns2/Makefile new file mode 100644 index 000000000000..a1ffbbe2e768 --- /dev/null +++ b/drivers/usb/gadget/udc/cdns2/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# define_trace.h needs to know how to find our header +CFLAGS_cdns2-trace.o := -I$(src) + +obj-$(CONFIG_USB_CDNS2_UDC) += cdns2-udc-pci.o +cdns2-udc-pci-$(CONFIG_USB_CDNS2_UDC) += cdns2-pci.o cdns2-gadget.o cdns2-ep0.o +cdns2-udc-pci-$(CONFIG_TRACING) += cdns2-trace.o diff --git a/drivers/usb/gadget/udc/cdns2/cdns2-debug.h b/drivers/usb/gadget/udc/cdns2/cdns2-debug.h new file mode 100644 index 000000000000..be9ae0d28a40 --- /dev/null +++ b/drivers/usb/gadget/udc/cdns2/cdns2-debug.h @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Cadence USBHS-DEV Driver. + * Debug header file. + * + * Copyright (C) 2023 Cadence. + * + * Author: Pawel Laszczak <pawell@cadence.com> + */ + +#ifndef __LINUX_CDNS2_DEBUG +#define __LINUX_CDNS2_DEBUG + +static inline const char *cdns2_decode_usb_irq(char *str, size_t size, + u8 usb_irq, u8 ext_irq) +{ + int ret; + + ret = snprintf(str, size, "usbirq: 0x%02x - ", usb_irq); + + if (usb_irq & USBIRQ_SOF) + ret += snprintf(str + ret, size - ret, "SOF "); + if (usb_irq & USBIRQ_SUTOK) + ret += snprintf(str + ret, size - ret, "SUTOK "); + if (usb_irq & USBIRQ_SUDAV) + ret += snprintf(str + ret, size - ret, "SETUP "); + if (usb_irq & USBIRQ_SUSPEND) + ret += snprintf(str + ret, size - ret, "Suspend "); + if (usb_irq & USBIRQ_URESET) + ret += snprintf(str + ret, size - ret, "Reset "); + if (usb_irq & USBIRQ_HSPEED) + ret += snprintf(str + ret, size - ret, "HS "); + if (usb_irq & USBIRQ_LPM) + ret += snprintf(str + ret, size - ret, "LPM "); + + ret += snprintf(str + ret, size - ret, ", EXT: 0x%02x - ", ext_irq); + + if (ext_irq & EXTIRQ_WAKEUP) + ret += snprintf(str + ret, size - ret, "Wakeup "); + if (ext_irq & EXTIRQ_VBUSFAULT_FALL) + ret += snprintf(str + ret, size - ret, "VBUS_FALL "); + if (ext_irq & EXTIRQ_VBUSFAULT_RISE) + ret += snprintf(str + ret, size - ret, "VBUS_RISE "); + + if (ret >= size) + pr_info("CDNS2: buffer overflowed.\n"); + + return str; +} + +static inline const char *cdns2_decode_dma_irq(char *str, size_t size, + u32 ep_ists, u32 ep_sts, + const char *ep_name) +{ + int ret; + + ret = snprintf(str, size, "ISTS: %08x, %s: %08x ", + ep_ists, ep_name, ep_sts); + + if (ep_sts & DMA_EP_STS_IOC) + ret += snprintf(str + ret, size - ret, "IOC "); + if (ep_sts & DMA_EP_STS_ISP) + ret += snprintf(str + ret, size - ret, "ISP "); + if (ep_sts & DMA_EP_STS_DESCMIS) + ret += snprintf(str + ret, size - ret, "DESCMIS "); + if (ep_sts & DMA_EP_STS_TRBERR) + ret += snprintf(str + ret, size - ret, "TRBERR "); + if (ep_sts & DMA_EP_STS_OUTSMM) + ret += snprintf(str + ret, size - ret, "OUTSMM "); + if (ep_sts & DMA_EP_STS_ISOERR) + ret += snprintf(str + ret, size - ret, "ISOERR "); + if (ep_sts & DMA_EP_STS_DBUSY) + ret += snprintf(str + ret, size - ret, "DBUSY "); + if (DMA_EP_STS_CCS(ep_sts)) + ret += snprintf(str + ret, size - ret, "CCS "); + + if (ret >= size) + pr_info("CDNS2: buffer overflowed.\n"); + + return str; +} + +static inline const char *cdns2_decode_epx_irq(char *str, size_t size, + char *ep_name, u32 ep_ists, + u32 ep_sts) +{ + return cdns2_decode_dma_irq(str, size, ep_ists, ep_sts, ep_name); +} + +static inline const char *cdns2_decode_ep0_irq(char *str, size_t size, + u32 ep_ists, u32 ep_sts, + int dir) +{ + return cdns2_decode_dma_irq(str, size, ep_ists, ep_sts, + dir ? "ep0IN" : "ep0OUT"); +} + +static inline const char *cdns2_raw_ring(struct cdns2_endpoint *pep, + struct cdns2_trb *trbs, + char *str, size_t size) +{ + struct cdns2_ring *ring = &pep->ring; + struct cdns2_trb *trb; + dma_addr_t dma; + int ret; + int i; + + ret = snprintf(str, size, "\n\t\tTR for %s:", pep->name); + + trb = &trbs[ring->dequeue]; + dma = cdns2_trb_virt_to_dma(pep, trb); + ret += snprintf(str + ret, size - ret, + "\n\t\tRing deq index: %d, trb: V=%p, P=0x%pad\n", + ring->dequeue, trb, &dma); + + trb = &trbs[ring->enqueue]; + dma = cdns2_trb_virt_to_dma(pep, trb); + ret += snprintf(str + ret, size - ret, + "\t\tRing enq index: %d, trb: V=%p, P=0x%pad\n", + ring->enqueue, trb, &dma); + + ret += snprintf(str + ret, size - ret, + "\t\tfree trbs: %d, CCS=%d, PCS=%d\n", + ring->free_trbs, ring->ccs, ring->pcs); + + if (TRBS_PER_SEGMENT > 40) { + ret += snprintf(str + ret, size - ret, + "\t\tTransfer ring %d too big\n", TRBS_PER_SEGMENT); + return str; + } + + dma = ring->dma; + for (i = 0; i < TRBS_PER_SEGMENT; ++i) { + trb = &trbs[i]; + ret += snprintf(str + ret, size - ret, + "\t\t@%pad %08x %08x %08x\n", &dma, + le32_to_cpu(trb->buffer), + le32_to_cpu(trb->length), + le32_to_cpu(trb->control)); + dma += sizeof(*trb); + } + + if (ret >= size) + pr_info("CDNS2: buffer overflowed.\n"); + + return str; +} + +static inline const char *cdns2_trb_type_string(u8 type) +{ + switch (type) { + case TRB_NORMAL: + return "Normal"; + case TRB_LINK: + return "Link"; + default: + return "UNKNOWN"; + } +} + +static inline const char *cdns2_decode_trb(char *str, size_t size, u32 flags, + u32 length, u32 buffer) +{ + int type = TRB_FIELD_TO_TYPE(flags); + int ret; + + switch (type) { + case TRB_LINK: + ret = snprintf(str, size, + "LINK %08x type '%s' flags %c:%c:%c%c:%c", + buffer, cdns2_trb_type_string(type), + flags & TRB_CYCLE ? 'C' : 'c', + flags & TRB_TOGGLE ? 'T' : 't', + flags & TRB_CHAIN ? 'C' : 'c', + flags & TRB_CHAIN ? 'H' : 'h', + flags & TRB_IOC ? 'I' : 'i'); + break; + case TRB_NORMAL: + ret = snprintf(str, size, + "type: '%s', Buffer: %08x, length: %ld, burst len: %ld, " + "flags %c:%c:%c%c:%c", + cdns2_trb_type_string(type), + buffer, TRB_LEN(length), + TRB_FIELD_TO_BURST(length), + flags & TRB_CYCLE ? 'C' : 'c', + flags & TRB_ISP ? 'I' : 'i', + flags & TRB_CHAIN ? 'C' : 'c', + flags & TRB_CHAIN ? 'H' : 'h', + flags & TRB_IOC ? 'I' : 'i'); + break; + default: + ret = snprintf(str, size, "type '%s' -> raw %08x %08x %08x", + cdns2_trb_type_string(type), + buffer, length, flags); + } + + if (ret >= size) + pr_info("CDNS2: buffer overflowed.\n"); + + return str; +} + +#endif /*__LINUX_CDNS2_DEBUG*/ diff --git a/drivers/usb/gadget/udc/cdns2/cdns2-ep0.c b/drivers/usb/gadget/udc/cdns2/cdns2-ep0.c new file mode 100644 index 000000000000..fa12a5d46f2e --- /dev/null +++ b/drivers/usb/gadget/udc/cdns2/cdns2-ep0.c @@ -0,0 +1,659 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence USBHS-DEV driver. + * + * Copyright (C) 2023 Cadence Design Systems. + * + * Authors: Pawel Laszczak <pawell@cadence.com> + */ + +#include <linux/usb/composite.h> +#include <asm/unaligned.h> + +#include "cdns2-gadget.h" +#include "cdns2-trace.h" + +static struct usb_endpoint_descriptor cdns2_gadget_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = cpu_to_le16(64) +}; + +static int cdns2_w_index_to_ep_index(u16 wIndex) +{ + if (!(wIndex & USB_ENDPOINT_NUMBER_MASK)) + return 0; + + return ((wIndex & USB_ENDPOINT_NUMBER_MASK) * 2) + + (wIndex & USB_ENDPOINT_DIR_MASK ? 1 : 0) - 1; +} + +static bool cdns2_check_new_setup(struct cdns2_device *pdev) +{ + u8 reg; + + reg = readb(&pdev->ep0_regs->cs); + + return !!(reg & EP0CS_CHGSET); +} + +static void cdns2_ep0_enqueue(struct cdns2_device *pdev, dma_addr_t dma_addr, + unsigned int length, int zlp) +{ + struct cdns2_adma_regs __iomem *regs = pdev->adma_regs; + struct cdns2_endpoint *pep = &pdev->eps[0]; + struct cdns2_ring *ring = &pep->ring; + + ring->trbs[0].buffer = cpu_to_le32(TRB_BUFFER(dma_addr)); + ring->trbs[0].length = cpu_to_le32(TRB_LEN(length)); + + if (zlp) { + ring->trbs[0].control = cpu_to_le32(TRB_CYCLE | + TRB_TYPE(TRB_NORMAL)); + ring->trbs[1].buffer = cpu_to_le32(TRB_BUFFER(dma_addr)); + ring->trbs[1].length = cpu_to_le32(TRB_LEN(0)); + ring->trbs[1].control = cpu_to_le32(TRB_CYCLE | TRB_IOC | + TRB_TYPE(TRB_NORMAL)); + } else { + ring->trbs[0].control = cpu_to_le32(TRB_CYCLE | TRB_IOC | + TRB_TYPE(TRB_NORMAL)); + ring->trbs[1].control = 0; + } + + trace_cdns2_queue_trb(pep, ring->trbs); + + if (!pep->dir) + writel(0, &pdev->ep0_regs->rxbc); + + cdns2_select_ep(pdev, pep->dir); + + writel(DMA_EP_STS_TRBERR, ®s->ep_sts); + writel(pep->ring.dma, ®s->ep_traddr); + + trace_cdns2_doorbell_ep0(pep, readl(®s->ep_traddr)); + + writel(DMA_EP_CMD_DRDY, ®s->ep_cmd); +} + +static int cdns2_ep0_delegate_req(struct cdns2_device *pdev) +{ + int ret; + + spin_unlock(&pdev->lock); + ret = pdev->gadget_driver->setup(&pdev->gadget, &pdev->setup); + spin_lock(&pdev->lock); + + return ret; +} + +static void cdns2_ep0_stall(struct cdns2_device *pdev) +{ + struct cdns2_endpoint *pep = &pdev->eps[0]; + struct cdns2_request *preq; + + preq = cdns2_next_preq(&pep->pending_list); + set_reg_bit_8(&pdev->ep0_regs->cs, EP0CS_DSTALL); + + if (pdev->ep0_stage == CDNS2_DATA_STAGE && preq) + cdns2_gadget_giveback(pep, preq, -ECONNRESET); + else if (preq) + list_del_init(&preq->list); + + pdev->ep0_stage = CDNS2_SETUP_STAGE; + pep->ep_state |= EP_STALLED; +} + +static void cdns2_status_stage(struct cdns2_device *pdev) +{ + struct cdns2_endpoint *pep = &pdev->eps[0]; + struct cdns2_request *preq; + + preq = cdns2_next_preq(&pep->pending_list); + if (preq) + list_del_init(&preq->list); + + pdev->ep0_stage = CDNS2_SETUP_STAGE; + writeb(EP0CS_HSNAK, &pdev->ep0_regs->cs); +} + +static int cdns2_req_ep0_set_configuration(struct cdns2_device *pdev, + struct usb_ctrlrequest *ctrl_req) +{ + enum usb_device_state state = pdev->gadget.state; + u32 config = le16_to_cpu(ctrl_req->wValue); + int ret; + + if (state < USB_STATE_ADDRESS) { + dev_err(pdev->dev, "Set Configuration - bad device state\n"); + return -EINVAL; + } + + ret = cdns2_ep0_delegate_req(pdev); + if (ret) + return ret; + + trace_cdns2_device_state(config ? "configured" : "addressed"); + + if (!config) + usb_gadget_set_state(&pdev->gadget, USB_STATE_ADDRESS); + + return 0; +} + +static int cdns2_req_ep0_set_address(struct cdns2_device *pdev, u32 addr) +{ + enum usb_device_state device_state = pdev->gadget.state; + u8 reg; + + if (addr > USB_DEVICE_MAX_ADDRESS) { + dev_err(pdev->dev, + "Device address (%d) cannot be greater than %d\n", + addr, USB_DEVICE_MAX_ADDRESS); + return -EINVAL; + } + + if (device_state == USB_STATE_CONFIGURED) { + dev_err(pdev->dev, + "can't set_address from configured state\n"); + return -EINVAL; + } + + reg = readb(&pdev->usb_regs->fnaddr); + pdev->dev_address = reg; + + usb_gadget_set_state(&pdev->gadget, + (addr ? USB_STATE_ADDRESS : USB_STATE_DEFAULT)); + + trace_cdns2_device_state(addr ? "addressed" : "default"); + + return 0; +} + +static int cdns2_req_ep0_handle_status(struct cdns2_device *pdev, + struct usb_ctrlrequest *ctrl) +{ + struct cdns2_endpoint *pep; + __le16 *response_pkt; + u16 status = 0; + int ep_sts; + u32 recip; + + recip = ctrl->bRequestType & USB_RECIP_MASK; + + switch (recip) { + case USB_RECIP_DEVICE: + status = pdev->gadget.is_selfpowered; + status |= pdev->may_wakeup << USB_DEVICE_REMOTE_WAKEUP; + break; + case USB_RECIP_INTERFACE: + return cdns2_ep0_delegate_req(pdev); + case USB_RECIP_ENDPOINT: + ep_sts = cdns2_w_index_to_ep_index(le16_to_cpu(ctrl->wIndex)); + pep = &pdev->eps[ep_sts]; + + if (pep->ep_state & EP_STALLED) + status = BIT(USB_ENDPOINT_HALT); + break; + default: + return -EINVAL; + } + + put_unaligned_le16(status, (__le16 *)pdev->ep0_preq.request.buf); + + cdns2_ep0_enqueue(pdev, pdev->ep0_preq.request.dma, + sizeof(*response_pkt), 0); + + return 0; +} + +static int cdns2_ep0_handle_feature_device(struct cdns2_device *pdev, + struct usb_ctrlrequest *ctrl, + int set) +{ + enum usb_device_state state; + enum usb_device_speed speed; + int ret = 0; + u32 wValue; + u16 tmode; + + wValue = le16_to_cpu(ctrl->wValue); + state = pdev->gadget.state; + speed = pdev->gadget.speed; + + switch (wValue) { + case USB_DEVICE_REMOTE_WAKEUP: + pdev->may_wakeup = !!set; + break; + case USB_DEVICE_TEST_MODE: + if (state != USB_STATE_CONFIGURED || speed > USB_SPEED_HIGH) + return -EINVAL; + + tmode = le16_to_cpu(ctrl->wIndex); + + if (!set || (tmode & 0xff) != 0) + return -EINVAL; + + tmode >>= 8; + switch (tmode) { + case USB_TEST_J: + case USB_TEST_K: + case USB_TEST_SE0_NAK: + case USB_TEST_PACKET: + /* + * The USBHS controller automatically handles the + * Set_Feature(testmode) request. Standard test modes + * that use values of test mode selector from + * 01h to 04h (Test_J, Test_K, Test_SE0_NAK, + * Test_Packet) are supported by the + * controller(HS - ack, FS - stall). + */ + break; + default: + ret = -EINVAL; + } + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int cdns2_ep0_handle_feature_intf(struct cdns2_device *pdev, + struct usb_ctrlrequest *ctrl, + int set) +{ + int ret = 0; + u32 wValue; + + wValue = le16_to_cpu(ctrl->wValue); + + switch (wValue) { + case USB_INTRF_FUNC_SUSPEND: + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int cdns2_ep0_handle_feature_endpoint(struct cdns2_device *pdev, + struct usb_ctrlrequest *ctrl, + int set) +{ + struct cdns2_endpoint *pep; + u8 wValue; + + wValue = le16_to_cpu(ctrl->wValue); + pep = &pdev->eps[cdns2_w_index_to_ep_index(le16_to_cpu(ctrl->wIndex))]; + + if (wValue != USB_ENDPOINT_HALT) + return -EINVAL; + + if (!(le16_to_cpu(ctrl->wIndex) & ~USB_DIR_IN)) + return 0; + + switch (wValue) { + case USB_ENDPOINT_HALT: + if (set || !(pep->ep_state & EP_WEDGE)) + return cdns2_halt_endpoint(pdev, pep, set); + break; + default: + dev_warn(pdev->dev, "WARN Incorrect wValue %04x\n", wValue); + return -EINVAL; + } + + return 0; +} + +static int cdns2_req_ep0_handle_feature(struct cdns2_device *pdev, + struct usb_ctrlrequest *ctrl, + int set) +{ + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + return cdns2_ep0_handle_feature_device(pdev, ctrl, set); + case USB_RECIP_INTERFACE: + return cdns2_ep0_handle_feature_intf(pdev, ctrl, set); + case USB_RECIP_ENDPOINT: + return cdns2_ep0_handle_feature_endpoint(pdev, ctrl, set); + default: + return -EINVAL; + } +} + +static int cdns2_ep0_std_request(struct cdns2_device *pdev) +{ + struct usb_ctrlrequest *ctrl = &pdev->setup; + int ret; + + switch (ctrl->bRequest) { + case USB_REQ_SET_ADDRESS: + ret = cdns2_req_ep0_set_address(pdev, + le16_to_cpu(ctrl->wValue)); + break; + case USB_REQ_SET_CONFIGURATION: + ret = cdns2_req_ep0_set_configuration(pdev, ctrl); + break; + case USB_REQ_GET_STATUS: + ret = cdns2_req_ep0_handle_status(pdev, ctrl); + break; + case USB_REQ_CLEAR_FEATURE: + ret = cdns2_req_ep0_handle_feature(pdev, ctrl, 0); + break; + case USB_REQ_SET_FEATURE: + ret = cdns2_req_ep0_handle_feature(pdev, ctrl, 1); + break; + default: + ret = cdns2_ep0_delegate_req(pdev); + break; + } + + return ret; +} + +static void __pending_setup_status_handler(struct cdns2_device *pdev) +{ + struct usb_request *request = pdev->pending_status_request; + + if (pdev->status_completion_no_call && request && request->complete) { + request->complete(&pdev->eps[0].endpoint, request); + pdev->status_completion_no_call = 0; + } +} + +void cdns2_pending_setup_status_handler(struct work_struct *work) +{ + struct cdns2_device *pdev = container_of(work, struct cdns2_device, + pending_status_wq); + unsigned long flags; + + spin_lock_irqsave(&pdev->lock, flags); + __pending_setup_status_handler(pdev); + spin_unlock_irqrestore(&pdev->lock, flags); +} + +void cdns2_handle_setup_packet(struct cdns2_device *pdev) +{ + struct usb_ctrlrequest *ctrl = &pdev->setup; + struct cdns2_endpoint *pep = &pdev->eps[0]; + struct cdns2_request *preq; + int ret = 0; + u16 len; + u8 reg; + int i; + + writeb(EP0CS_CHGSET, &pdev->ep0_regs->cs); + + for (i = 0; i < 8; i++) + ((u8 *)&pdev->setup)[i] = readb(&pdev->ep0_regs->setupdat[i]); + + /* + * If SETUP packet was modified while reading just simple ignore it. + * The new one will be handled latter. + */ + if (cdns2_check_new_setup(pdev)) { + trace_cdns2_ep0_setup("overridden"); + return; + } + + trace_cdns2_ctrl_req(ctrl); + + if (!pdev->gadget_driver) + goto out; + + if (pdev->gadget.state == USB_STATE_NOTATTACHED) { + dev_err(pdev->dev, "ERR: Setup detected in unattached state\n"); + ret = -EINVAL; + goto out; + } + + pep = &pdev->eps[0]; + + /* Halt for Ep0 is cleared automatically when SETUP packet arrives. */ + pep->ep_state &= ~EP_STALLED; + + if (!list_empty(&pep->pending_list)) { + preq = cdns2_next_preq(&pep->pending_list); + cdns2_gadget_giveback(pep, preq, -ECONNRESET); + } + + len = le16_to_cpu(ctrl->wLength); + if (len) + pdev->ep0_stage = CDNS2_DATA_STAGE; + else + pdev->ep0_stage = CDNS2_STATUS_STAGE; + + pep->dir = ctrl->bRequestType & USB_DIR_IN; + + /* + * SET_ADDRESS request is acknowledged automatically by controller and + * in the worse case driver may not notice this request. To check + * whether this request has been processed driver can use + * fnaddr register. + */ + reg = readb(&pdev->usb_regs->fnaddr); + if (pdev->setup.bRequest != USB_REQ_SET_ADDRESS && + pdev->dev_address != reg) + cdns2_req_ep0_set_address(pdev, reg); + + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) + ret = cdns2_ep0_std_request(pdev); + else + ret = cdns2_ep0_delegate_req(pdev); + + if (ret == USB_GADGET_DELAYED_STATUS) { + trace_cdns2_ep0_status_stage("delayed"); + return; + } + +out: + if (ret < 0) + cdns2_ep0_stall(pdev); + else if (pdev->ep0_stage == CDNS2_STATUS_STAGE) + cdns2_status_stage(pdev); +} + +static void cdns2_transfer_completed(struct cdns2_device *pdev) +{ + struct cdns2_endpoint *pep = &pdev->eps[0]; + + if (!list_empty(&pep->pending_list)) { + struct cdns2_request *preq; + + trace_cdns2_complete_trb(pep, pep->ring.trbs); + preq = cdns2_next_preq(&pep->pending_list); + + preq->request.actual = + TRB_LEN(le32_to_cpu(pep->ring.trbs->length)); + cdns2_gadget_giveback(pep, preq, 0); + } + + cdns2_status_stage(pdev); +} + +void cdns2_handle_ep0_interrupt(struct cdns2_device *pdev, int dir) +{ + u32 ep_sts_reg; + + cdns2_select_ep(pdev, dir); + + trace_cdns2_ep0_irq(pdev); + + ep_sts_reg = readl(&pdev->adma_regs->ep_sts); + writel(ep_sts_reg, &pdev->adma_regs->ep_sts); + + __pending_setup_status_handler(pdev); + + if ((ep_sts_reg & DMA_EP_STS_IOC) || (ep_sts_reg & DMA_EP_STS_ISP)) { + pdev->eps[0].dir = dir; + cdns2_transfer_completed(pdev); + } +} + +/* + * Function shouldn't be called by gadget driver, + * endpoint 0 is allways active. + */ +static int cdns2_gadget_ep0_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + return -EINVAL; +} + +/* + * Function shouldn't be called by gadget driver, + * endpoint 0 is allways active. + */ +static int cdns2_gadget_ep0_disable(struct usb_ep *ep) +{ + return -EINVAL; +} + +static int cdns2_gadget_ep0_set_halt(struct usb_ep *ep, int value) +{ + struct cdns2_endpoint *pep = ep_to_cdns2_ep(ep); + struct cdns2_device *pdev = pep->pdev; + unsigned long flags; + + if (!value) + return 0; + + spin_lock_irqsave(&pdev->lock, flags); + cdns2_ep0_stall(pdev); + spin_unlock_irqrestore(&pdev->lock, flags); + + return 0; +} + +static int cdns2_gadget_ep0_set_wedge(struct usb_ep *ep) +{ + return cdns2_gadget_ep0_set_halt(ep, 1); +} + +static int cdns2_gadget_ep0_queue(struct usb_ep *ep, + struct usb_request *request, + gfp_t gfp_flags) +{ + struct cdns2_endpoint *pep = ep_to_cdns2_ep(ep); + struct cdns2_device *pdev = pep->pdev; + struct cdns2_request *preq; + unsigned long flags; + u8 zlp = 0; + int ret; + + spin_lock_irqsave(&pdev->lock, flags); + + preq = to_cdns2_request(request); + + trace_cdns2_request_enqueue(preq); + + /* Cancel the request if controller receive new SETUP packet. */ + if (cdns2_check_new_setup(pdev)) { + trace_cdns2_ep0_setup("overridden"); + spin_unlock_irqrestore(&pdev->lock, flags); + return -ECONNRESET; + } + + /* Send STATUS stage. Should be called only for SET_CONFIGURATION. */ + if (pdev->ep0_stage == CDNS2_STATUS_STAGE) { + cdns2_status_stage(pdev); + + request->actual = 0; + pdev->status_completion_no_call = true; + pdev->pending_status_request = request; + usb_gadget_set_state(&pdev->gadget, USB_STATE_CONFIGURED); + spin_unlock_irqrestore(&pdev->lock, flags); + + /* + * Since there is no completion interrupt for status stage, + * it needs to call ->completion in software after + * cdns2_gadget_ep0_queue is back. + */ + queue_work(system_freezable_wq, &pdev->pending_status_wq); + return 0; + } + + if (!list_empty(&pep->pending_list)) { + trace_cdns2_ep0_setup("pending"); + dev_err(pdev->dev, + "can't handle multiple requests for ep0\n"); + spin_unlock_irqrestore(&pdev->lock, flags); + return -EBUSY; + } + + ret = usb_gadget_map_request_by_dev(pdev->dev, request, pep->dir); + if (ret) { + spin_unlock_irqrestore(&pdev->lock, flags); + dev_err(pdev->dev, "failed to map request\n"); + return -EINVAL; + } + + request->status = -EINPROGRESS; + list_add_tail(&preq->list, &pep->pending_list); + + if (request->zero && request->length && + (request->length % ep->maxpacket == 0)) + zlp = 1; + + cdns2_ep0_enqueue(pdev, request->dma, request->length, zlp); + + spin_unlock_irqrestore(&pdev->lock, flags); + + return 0; +} + +static const struct usb_ep_ops cdns2_gadget_ep0_ops = { + .enable = cdns2_gadget_ep0_enable, + .disable = cdns2_gadget_ep0_disable, + .alloc_request = cdns2_gadget_ep_alloc_request, + .free_request = cdns2_gadget_ep_free_request, + .queue = cdns2_gadget_ep0_queue, + .dequeue = cdns2_gadget_ep_dequeue, + .set_halt = cdns2_gadget_ep0_set_halt, + .set_wedge = cdns2_gadget_ep0_set_wedge, +}; + +void cdns2_ep0_config(struct cdns2_device *pdev) +{ + struct cdns2_endpoint *pep; + + pep = &pdev->eps[0]; + + if (!list_empty(&pep->pending_list)) { + struct cdns2_request *preq; + + preq = cdns2_next_preq(&pep->pending_list); + list_del_init(&preq->list); + } + + writeb(EP0_FIFO_AUTO, &pdev->ep0_regs->fifo); + cdns2_select_ep(pdev, USB_DIR_OUT); + writel(DMA_EP_CFG_ENABLE, &pdev->adma_regs->ep_cfg); + + writeb(EP0_FIFO_IO_TX | EP0_FIFO_AUTO, &pdev->ep0_regs->fifo); + cdns2_select_ep(pdev, USB_DIR_IN); + writel(DMA_EP_CFG_ENABLE, &pdev->adma_regs->ep_cfg); + + writeb(pdev->gadget.ep0->maxpacket, &pdev->ep0_regs->maxpack); + writel(DMA_EP_IEN_EP_OUT0 | DMA_EP_IEN_EP_IN0, + &pdev->adma_regs->ep_ien); +} + +void cdns2_init_ep0(struct cdns2_device *pdev, + struct cdns2_endpoint *pep) +{ + u16 maxpacket = le16_to_cpu(cdns2_gadget_ep0_desc.wMaxPacketSize); + + usb_ep_set_maxpacket_limit(&pep->endpoint, maxpacket); + + pep->endpoint.ops = &cdns2_gadget_ep0_ops; + pep->endpoint.desc = &cdns2_gadget_ep0_desc; + pep->endpoint.caps.type_control = true; + pep->endpoint.caps.dir_in = true; + pep->endpoint.caps.dir_out = true; + + pdev->gadget.ep0 = &pep->endpoint; +} diff --git a/drivers/usb/gadget/udc/cdns2/cdns2-gadget.c b/drivers/usb/gadget/udc/cdns2/cdns2-gadget.c new file mode 100644 index 000000000000..0eed0e03842c --- /dev/null +++ b/drivers/usb/gadget/udc/cdns2/cdns2-gadget.c @@ -0,0 +1,2474 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence USBHS-DEV Driver - gadget side. + * + * Copyright (C) 2023 Cadence Design Systems. + * + * Authors: Pawel Laszczak <pawell@cadence.com> + */ + +/* + * Work around 1: + * At some situations, the controller may get stale data address in TRB + * at below sequences: + * 1. Controller read TRB includes data address + * 2. Software updates TRBs includes data address and Cycle bit + * 3. Controller read TRB which includes Cycle bit + * 4. DMA run with stale data address + * + * To fix this problem, driver needs to make the first TRB in TD as invalid. + * After preparing all TRBs driver needs to check the position of DMA and + * if the DMA point to the first just added TRB and doorbell is 1, + * then driver must defer making this TRB as valid. This TRB will be make + * as valid during adding next TRB only if DMA is stopped or at TRBERR + * interrupt. + * + */ + +#include <linux/dma-mapping.h> +#include <linux/pm_runtime.h> +#include <linux/interrupt.h> +#include <linux/property.h> +#include <linux/dmapool.h> +#include <linux/iopoll.h> + +#include "cdns2-gadget.h" +#include "cdns2-trace.h" + +/** + * set_reg_bit_32 - set bit in given 32 bits register. + * @ptr: register address. + * @mask: bits to set. + */ +static void set_reg_bit_32(void __iomem *ptr, u32 mask) +{ + mask = readl(ptr) | mask; + writel(mask, ptr); +} + +/* + * clear_reg_bit_32 - clear bit in given 32 bits register. + * @ptr: register address. + * @mask: bits to clear. + */ +static void clear_reg_bit_32(void __iomem *ptr, u32 mask) +{ + mask = readl(ptr) & ~mask; + writel(mask, ptr); +} + +/* Clear bit in given 8 bits register. */ +static void clear_reg_bit_8(void __iomem *ptr, u8 mask) +{ + mask = readb(ptr) & ~mask; + writeb(mask, ptr); +} + +/* Set bit in given 16 bits register. */ +void set_reg_bit_8(void __iomem *ptr, u8 mask) +{ + mask = readb(ptr) | mask; + writeb(mask, ptr); +} + +static int cdns2_get_dma_pos(struct cdns2_device *pdev, + struct cdns2_endpoint *pep) +{ + int dma_index; + + dma_index = readl(&pdev->adma_regs->ep_traddr) - pep->ring.dma; + + return dma_index / TRB_SIZE; +} + +/* Get next private request from list. */ +struct cdns2_request *cdns2_next_preq(struct list_head *list) +{ + return list_first_entry_or_null(list, struct cdns2_request, list); +} + +void cdns2_select_ep(struct cdns2_device *pdev, u32 ep) +{ + if (pdev->selected_ep == ep) + return; + + pdev->selected_ep = ep; + writel(ep, &pdev->adma_regs->ep_sel); +} + +dma_addr_t cdns2_trb_virt_to_dma(struct cdns2_endpoint *pep, + struct cdns2_trb *trb) +{ + u32 offset = (char *)trb - (char *)pep->ring.trbs; + + return pep->ring.dma + offset; +} + +static void cdns2_free_tr_segment(struct cdns2_endpoint *pep) +{ + struct cdns2_device *pdev = pep->pdev; + struct cdns2_ring *ring = &pep->ring; + + if (pep->ring.trbs) { + dma_pool_free(pdev->eps_dma_pool, ring->trbs, ring->dma); + memset(ring, 0, sizeof(*ring)); + } +} + +/* Allocates Transfer Ring segment. */ +static int cdns2_alloc_tr_segment(struct cdns2_endpoint *pep) +{ + struct cdns2_device *pdev = pep->pdev; + struct cdns2_trb *link_trb; + struct cdns2_ring *ring; + + ring = &pep->ring; + + if (!ring->trbs) { + ring->trbs = dma_pool_alloc(pdev->eps_dma_pool, + GFP_DMA32 | GFP_ATOMIC, + &ring->dma); + if (!ring->trbs) + return -ENOMEM; + } + + memset(ring->trbs, 0, TR_SEG_SIZE); + + if (!pep->num) + return 0; + + /* Initialize the last TRB as Link TRB */ + link_trb = (ring->trbs + (TRBS_PER_SEGMENT - 1)); + link_trb->buffer = cpu_to_le32(TRB_BUFFER(ring->dma)); + link_trb->control = cpu_to_le32(TRB_CYCLE | TRB_TYPE(TRB_LINK) | + TRB_TOGGLE); + + return 0; +} + +/* + * Stalls and flushes selected endpoint. + * Endpoint must be selected before invoking this function. + */ +static void cdns2_ep_stall_flush(struct cdns2_endpoint *pep) +{ + struct cdns2_device *pdev = pep->pdev; + int val; + + trace_cdns2_ep_halt(pep, 1, 1); + + writel(DMA_EP_CMD_DFLUSH, &pdev->adma_regs->ep_cmd); + + /* Wait for DFLUSH cleared. */ + readl_poll_timeout_atomic(&pdev->adma_regs->ep_cmd, val, + !(val & DMA_EP_CMD_DFLUSH), 1, 1000); + pep->ep_state |= EP_STALLED; + pep->ep_state &= ~EP_STALL_PENDING; +} + +/* + * Increment a trb index. + * + * The index should never point to the last link TRB in TR. After incrementing, + * if it point to the link TRB, wrap around to the beginning and revert + * cycle state bit. The link TRB is always at the last TRB entry. + */ +static void cdns2_ep_inc_trb(int *index, u8 *cs, int trb_in_seg) +{ + (*index)++; + if (*index == (trb_in_seg - 1)) { + *index = 0; + *cs ^= 1; + } +} + +static void cdns2_ep_inc_enq(struct cdns2_ring *ring) +{ + ring->free_trbs--; + cdns2_ep_inc_trb(&ring->enqueue, &ring->pcs, TRBS_PER_SEGMENT); +} + +static void cdns2_ep_inc_deq(struct cdns2_ring *ring) +{ + ring->free_trbs++; + cdns2_ep_inc_trb(&ring->dequeue, &ring->ccs, TRBS_PER_SEGMENT); +} + +/* + * Enable/disable LPM. + * + * If bit USBCS_LPMNYET is not set and device receive Extended Token packet, + * then controller answer with ACK handshake. + * If bit USBCS_LPMNYET is set and device receive Extended Token packet, + * then controller answer with NYET handshake. + */ +static void cdns2_enable_l1(struct cdns2_device *pdev, int enable) +{ + if (enable) { + clear_reg_bit_8(&pdev->usb_regs->usbcs, USBCS_LPMNYET); + writeb(LPMCLOCK_SLEEP_ENTRY, &pdev->usb_regs->lpmclock); + } else { + set_reg_bit_8(&pdev->usb_regs->usbcs, USBCS_LPMNYET); + } +} + +static enum usb_device_speed cdns2_get_speed(struct cdns2_device *pdev) +{ + u8 speed = readb(&pdev->usb_regs->speedctrl); + + if (speed & SPEEDCTRL_HS) + return USB_SPEED_HIGH; + else if (speed & SPEEDCTRL_FS) + return USB_SPEED_FULL; + + return USB_SPEED_UNKNOWN; +} + +static struct cdns2_trb *cdns2_next_trb(struct cdns2_endpoint *pep, + struct cdns2_trb *trb) +{ + if (trb == (pep->ring.trbs + (TRBS_PER_SEGMENT - 1))) + return pep->ring.trbs; + else + return ++trb; +} + +void cdns2_gadget_giveback(struct cdns2_endpoint *pep, + struct cdns2_request *preq, + int status) +{ + struct usb_request *request = &preq->request; + struct cdns2_device *pdev = pep->pdev; + + list_del_init(&preq->list); + + if (request->status == -EINPROGRESS) + request->status = status; + + usb_gadget_unmap_request_by_dev(pdev->dev, request, pep->dir); + + /* All TRBs have finished, clear the counter. */ + preq->finished_trb = 0; + + trace_cdns2_request_giveback(preq); + + if (request->complete) { + spin_unlock(&pdev->lock); + usb_gadget_giveback_request(&pep->endpoint, request); + spin_lock(&pdev->lock); + } + + if (request->buf == pdev->zlp_buf) + cdns2_gadget_ep_free_request(&pep->endpoint, request); +} + +static void cdns2_wa1_restore_cycle_bit(struct cdns2_endpoint *pep) +{ + /* Work around for stale data address in TRB. */ + if (pep->wa1_set) { + trace_cdns2_wa1(pep, "restore cycle bit"); + + pep->wa1_set = 0; + pep->wa1_trb_index = 0xFFFF; + if (pep->wa1_cycle_bit) + pep->wa1_trb->control |= cpu_to_le32(0x1); + else + pep->wa1_trb->control &= cpu_to_le32(~0x1); + } +} + +static int cdns2_wa1_update_guard(struct cdns2_endpoint *pep, + struct cdns2_trb *trb) +{ + struct cdns2_device *pdev = pep->pdev; + + if (!pep->wa1_set) { + u32 doorbell; + + doorbell = !!(readl(&pdev->adma_regs->ep_cmd) & DMA_EP_CMD_DRDY); + + if (doorbell) { + pep->wa1_cycle_bit = pep->ring.pcs ? TRB_CYCLE : 0; + pep->wa1_set = 1; + pep->wa1_trb = trb; + pep->wa1_trb_index = pep->ring.enqueue; + trace_cdns2_wa1(pep, "set guard"); + return 0; + } + } + return 1; +} + +static void cdns2_wa1_tray_restore_cycle_bit(struct cdns2_device *pdev, + struct cdns2_endpoint *pep) +{ + int dma_index; + u32 doorbell; + + doorbell = !!(readl(&pdev->adma_regs->ep_cmd) & DMA_EP_CMD_DRDY); + dma_index = cdns2_get_dma_pos(pdev, pep); + + if (!doorbell || dma_index != pep->wa1_trb_index) + cdns2_wa1_restore_cycle_bit(pep); +} + +static int cdns2_prepare_ring(struct cdns2_device *pdev, + struct cdns2_endpoint *pep, + int num_trbs) +{ + struct cdns2_trb *link_trb = NULL; + int doorbell, dma_index; + struct cdns2_ring *ring; + u32 ch_bit = 0; + + ring = &pep->ring; + + if (num_trbs > ring->free_trbs) { + pep->ep_state |= EP_RING_FULL; + trace_cdns2_no_room_on_ring("Ring full\n"); + return -ENOBUFS; + } + + if ((ring->enqueue + num_trbs) >= (TRBS_PER_SEGMENT - 1)) { + doorbell = !!(readl(&pdev->adma_regs->ep_cmd) & DMA_EP_CMD_DRDY); + dma_index = cdns2_get_dma_pos(pdev, pep); + + /* Driver can't update LINK TRB if it is current processed. */ + if (doorbell && dma_index == TRBS_PER_SEGMENT - 1) { + pep->ep_state |= EP_DEFERRED_DRDY; + return -ENOBUFS; + } + + /* Update C bt in Link TRB before starting DMA. */ + link_trb = ring->trbs + (TRBS_PER_SEGMENT - 1); + + /* + * For TRs size equal 2 enabling TRB_CHAIN for epXin causes + * that DMA stuck at the LINK TRB. + * On the other hand, removing TRB_CHAIN for longer TRs for + * epXout cause that DMA stuck after handling LINK TRB. + * To eliminate this strange behavioral driver set TRB_CHAIN + * bit only for TR size > 2. + */ + if (pep->type == USB_ENDPOINT_XFER_ISOC || TRBS_PER_SEGMENT > 2) + ch_bit = TRB_CHAIN; + + link_trb->control = cpu_to_le32(((ring->pcs) ? TRB_CYCLE : 0) | + TRB_TYPE(TRB_LINK) | TRB_TOGGLE | ch_bit); + } + + return 0; +} + +static void cdns2_dbg_request_trbs(struct cdns2_endpoint *pep, + struct cdns2_request *preq) +{ + struct cdns2_trb *link_trb = pep->ring.trbs + (TRBS_PER_SEGMENT - 1); + struct cdns2_trb *trb = preq->trb; + int num_trbs = preq->num_of_trb; + int i = 0; + + while (i < num_trbs) { + trace_cdns2_queue_trb(pep, trb + i); + if (trb + i == link_trb) { + trb = pep->ring.trbs; + num_trbs = num_trbs - i; + i = 0; + } else { + i++; + } + } +} + +static unsigned int cdns2_count_trbs(struct cdns2_endpoint *pep, + u64 addr, u64 len) +{ + unsigned int num_trbs = 1; + + if (pep->type == USB_ENDPOINT_XFER_ISOC) { + /* + * To speed up DMA performance address should not exceed 4KB. + * for high bandwidth transfer and driver will split + * such buffer into two TRBs. + */ + num_trbs = DIV_ROUND_UP(len + + (addr & (TRB_MAX_ISO_BUFF_SIZE - 1)), + TRB_MAX_ISO_BUFF_SIZE); + + if (pep->interval > 1) + num_trbs = pep->dir ? num_trbs * pep->interval : 1; + } else if (pep->dir) { + /* + * One extra link trb for IN direction. + * Sometimes DMA doesn't want advance to next TD and transfer + * hangs. This extra Link TRB force DMA to advance to next TD. + */ + num_trbs++; + } + + return num_trbs; +} + +static unsigned int cdns2_count_sg_trbs(struct cdns2_endpoint *pep, + struct usb_request *req) +{ + unsigned int i, len, full_len, num_trbs = 0; + struct scatterlist *sg; + int trb_len = 0; + + full_len = req->length; + + for_each_sg(req->sg, sg, req->num_sgs, i) { + len = sg_dma_len(sg); + num_trbs += cdns2_count_trbs(pep, sg_dma_address(sg), len); + len = min(len, full_len); + + /* + * For HS ISO transfer TRBs should not exceed max packet size. + * When DMA is working, and data exceed max packet size then + * some data will be read in single mode instead burst mode. + * This behavior will drastically reduce the copying speed. + * To avoid this we need one or two extra TRBs. + * This issue occurs for UVC class with sg_supported = 1 + * because buffers addresses are not aligned to 1024. + */ + if (pep->type == USB_ENDPOINT_XFER_ISOC) { + u8 temp; + + trb_len += len; + temp = trb_len >> 10; + + if (temp) { + if (trb_len % 1024) + num_trbs = num_trbs + temp; + else + num_trbs = num_trbs + temp - 1; + + trb_len = trb_len - (temp << 10); + } + } + + full_len -= len; + if (full_len == 0) + break; + } + + return num_trbs; +} + +/* + * Function prepares the array with optimized AXI burst value for different + * transfer lengths. Controller handles the final data which are less + * then AXI burst size as single byte transactions. + * e.g.: + * Let's assume that driver prepares trb with trb->length 700 and burst size + * will be set to 128. In this case the controller will handle a first 512 as + * single AXI transaction but the next 188 bytes will be handled + * as 47 separate AXI transaction. + * The better solution is to use the burst size equal 16 and then we will + * have only 25 AXI transaction (10 * 64 + 15 *4). + */ +static void cdsn2_isoc_burst_opt(struct cdns2_device *pdev) +{ + int axi_burst_option[] = {1, 2, 4, 8, 16, 32, 64, 128}; + int best_burst; + int array_size; + int opt_burst; + int trb_size; + int i, j; + + array_size = ARRAY_SIZE(axi_burst_option); + + for (i = 0; i <= MAX_ISO_SIZE; i++) { + trb_size = i / 4; + best_burst = trb_size ? trb_size : 1; + + for (j = 0; j < array_size; j++) { + opt_burst = trb_size / axi_burst_option[j]; + opt_burst += trb_size % axi_burst_option[j]; + + if (opt_burst < best_burst) { + best_burst = opt_burst; + pdev->burst_opt[i] = axi_burst_option[j]; + } + } + } +} + +static void cdns2_ep_tx_isoc(struct cdns2_endpoint *pep, + struct cdns2_request *preq, + int num_trbs) +{ + struct scatterlist *sg = NULL; + u32 remaining_packet_size = 0; + struct cdns2_trb *trb; + bool first_trb = true; + dma_addr_t trb_dma; + u32 trb_buff_len; + u32 block_length; + int td_idx = 0; + int split_size; + u32 full_len; + int enqd_len; + int sent_len; + int sg_iter; + u32 control; + int num_tds; + u32 length; + + /* + * For OUT direction 1 TD per interval is enough + * because TRBs are not dumped by controller. + */ + num_tds = pep->dir ? pep->interval : 1; + split_size = preq->request.num_sgs ? 1024 : 3072; + + for (td_idx = 0; td_idx < num_tds; td_idx++) { + if (preq->request.num_sgs) { + sg = preq->request.sg; + trb_dma = sg_dma_address(sg); + block_length = sg_dma_len(sg); + } else { + trb_dma = preq->request.dma; + block_length = preq->request.length; + } + + full_len = preq->request.length; + sg_iter = preq->request.num_sgs ? preq->request.num_sgs : 1; + remaining_packet_size = split_size; + + for (enqd_len = 0; enqd_len < full_len; + enqd_len += trb_buff_len) { + if (remaining_packet_size == 0) + remaining_packet_size = split_size; + + /* + * Calculate TRB length.- buffer can't across 4KB + * and max packet size. + */ + trb_buff_len = TRB_BUFF_LEN_UP_TO_BOUNDARY(trb_dma); + trb_buff_len = min(trb_buff_len, remaining_packet_size); + trb_buff_len = min(trb_buff_len, block_length); + + if (trb_buff_len > full_len - enqd_len) + trb_buff_len = full_len - enqd_len; + + control = TRB_TYPE(TRB_NORMAL); + + /* + * For IN direction driver has to set the IOC for + * last TRB in last TD. + * For OUT direction driver must set IOC and ISP + * only for last TRB in each TDs. + */ + if (enqd_len + trb_buff_len >= full_len || !pep->dir) + control |= TRB_IOC | TRB_ISP; + + /* + * Don't give the first TRB to the hardware (by toggling + * the cycle bit) until we've finished creating all the + * other TRBs. + */ + if (first_trb) { + first_trb = false; + if (pep->ring.pcs == 0) + control |= TRB_CYCLE; + } else { + control |= pep->ring.pcs; + } + + if (enqd_len + trb_buff_len < full_len) + control |= TRB_CHAIN; + + length = TRB_LEN(trb_buff_len) | + TRB_BURST(pep->pdev->burst_opt[trb_buff_len]); + + trb = pep->ring.trbs + pep->ring.enqueue; + trb->buffer = cpu_to_le32(TRB_BUFFER(trb_dma)); + trb->length = cpu_to_le32(length); + trb->control = cpu_to_le32(control); + + trb_dma += trb_buff_len; + sent_len = trb_buff_len; + + if (sg && sent_len >= block_length) { + /* New sg entry */ + --sg_iter; + sent_len -= block_length; + if (sg_iter != 0) { + sg = sg_next(sg); + trb_dma = sg_dma_address(sg); + block_length = sg_dma_len(sg); + } + } + + remaining_packet_size -= trb_buff_len; + block_length -= sent_len; + preq->end_trb = pep->ring.enqueue; + + cdns2_ep_inc_enq(&pep->ring); + } + } +} + +static void cdns2_ep_tx_bulk(struct cdns2_endpoint *pep, + struct cdns2_request *preq, + int trbs_per_td) +{ + struct scatterlist *sg = NULL; + struct cdns2_ring *ring; + struct cdns2_trb *trb; + dma_addr_t trb_dma; + int sg_iter = 0; + u32 control; + u32 length; + + if (preq->request.num_sgs) { + sg = preq->request.sg; + trb_dma = sg_dma_address(sg); + length = sg_dma_len(sg); + } else { + trb_dma = preq->request.dma; + length = preq->request.length; + } + + ring = &pep->ring; + + for (sg_iter = 0; sg_iter < trbs_per_td; sg_iter++) { + control = TRB_TYPE(TRB_NORMAL) | ring->pcs | TRB_ISP; + trb = pep->ring.trbs + ring->enqueue; + + if (pep->dir && sg_iter == trbs_per_td - 1) { + preq->end_trb = ring->enqueue; + control = ring->pcs | TRB_TYPE(TRB_LINK) | TRB_CHAIN + | TRB_IOC; + cdns2_ep_inc_enq(&pep->ring); + + if (ring->enqueue == 0) + control |= TRB_TOGGLE; + + /* Point to next bad TRB. */ + trb->buffer = cpu_to_le32(pep->ring.dma + + (ring->enqueue * TRB_SIZE)); + trb->length = 0; + trb->control = cpu_to_le32(control); + break; + } + + /* + * Don't give the first TRB to the hardware (by toggling + * the cycle bit) until we've finished creating all the + * other TRBs. + */ + if (sg_iter == 0) + control = control ^ TRB_CYCLE; + + /* For last TRB in TD. */ + if (sg_iter == (trbs_per_td - (pep->dir ? 2 : 1))) + control |= TRB_IOC; + else + control |= TRB_CHAIN; + + trb->buffer = cpu_to_le32(trb_dma); + trb->length = cpu_to_le32(TRB_BURST(pep->trb_burst_size) | + TRB_LEN(length)); + trb->control = cpu_to_le32(control); + + if (sg && sg_iter < (trbs_per_td - 1)) { + sg = sg_next(sg); + trb_dma = sg_dma_address(sg); + length = sg_dma_len(sg); + } + + preq->end_trb = ring->enqueue; + cdns2_ep_inc_enq(&pep->ring); + } +} + +static void cdns2_set_drdy(struct cdns2_device *pdev, + struct cdns2_endpoint *pep) +{ + trace_cdns2_ring(pep); + + /* + * Memory barrier - Cycle Bit must be set before doorbell. + */ + dma_wmb(); + + /* Clearing TRBERR and DESCMIS before setting DRDY. */ + writel(DMA_EP_STS_TRBERR | DMA_EP_STS_DESCMIS, + &pdev->adma_regs->ep_sts); + writel(DMA_EP_CMD_DRDY, &pdev->adma_regs->ep_cmd); + + if (readl(&pdev->adma_regs->ep_sts) & DMA_EP_STS_TRBERR) { + writel(DMA_EP_STS_TRBERR, &pdev->adma_regs->ep_sts); + writel(DMA_EP_CMD_DRDY, &pdev->adma_regs->ep_cmd); + } + + trace_cdns2_doorbell_epx(pep, readl(&pdev->adma_regs->ep_traddr)); +} + +static int cdns2_prepare_first_isoc_transfer(struct cdns2_device *pdev, + struct cdns2_endpoint *pep) +{ + struct cdns2_trb *trb; + u32 buffer; + u8 hw_ccs; + + if ((readl(&pdev->adma_regs->ep_cmd) & DMA_EP_CMD_DRDY)) + return -EBUSY; + + if (!pep->dir) { + set_reg_bit_32(&pdev->adma_regs->ep_cfg, DMA_EP_CFG_ENABLE); + writel(pep->ring.dma + pep->ring.dequeue, + &pdev->adma_regs->ep_traddr); + return 0; + } + + /* + * The first packet after doorbell can be corrupted so, + * driver prepares 0 length packet as first packet. + */ + buffer = pep->ring.dma + pep->ring.dequeue * TRB_SIZE; + hw_ccs = !!DMA_EP_STS_CCS(readl(&pdev->adma_regs->ep_sts)); + + trb = &pep->ring.trbs[TRBS_PER_SEGMENT]; + trb->length = 0; + trb->buffer = cpu_to_le32(TRB_BUFFER(buffer)); + trb->control = cpu_to_le32((hw_ccs ? TRB_CYCLE : 0) | TRB_TYPE(TRB_NORMAL)); + + /* + * LINK TRB is used to force updating cycle bit in controller and + * move to correct place in transfer ring. + */ + trb++; + trb->length = 0; + trb->buffer = cpu_to_le32(TRB_BUFFER(buffer)); + trb->control = cpu_to_le32((hw_ccs ? TRB_CYCLE : 0) | + TRB_TYPE(TRB_LINK) | TRB_CHAIN); + + if (hw_ccs != pep->ring.ccs) + trb->control |= cpu_to_le32(TRB_TOGGLE); + + set_reg_bit_32(&pdev->adma_regs->ep_cfg, DMA_EP_CFG_ENABLE); + writel(pep->ring.dma + (TRBS_PER_SEGMENT * TRB_SIZE), + &pdev->adma_regs->ep_traddr); + + return 0; +} + +/* Prepare and start transfer on no-default endpoint. */ +static int cdns2_ep_run_transfer(struct cdns2_endpoint *pep, + struct cdns2_request *preq) +{ + struct cdns2_device *pdev = pep->pdev; + struct cdns2_ring *ring; + u32 togle_pcs = 1; + int num_trbs; + int ret; + + cdns2_select_ep(pdev, pep->endpoint.address); + + if (preq->request.sg) + num_trbs = cdns2_count_sg_trbs(pep, &preq->request); + else + num_trbs = cdns2_count_trbs(pep, preq->request.dma, + preq->request.length); + + ret = cdns2_prepare_ring(pdev, pep, num_trbs); + if (ret) + return ret; + + ring = &pep->ring; + preq->start_trb = ring->enqueue; + preq->trb = ring->trbs + ring->enqueue; + + if (usb_endpoint_xfer_isoc(pep->endpoint.desc)) { + cdns2_ep_tx_isoc(pep, preq, num_trbs); + } else { + togle_pcs = cdns2_wa1_update_guard(pep, ring->trbs + ring->enqueue); + cdns2_ep_tx_bulk(pep, preq, num_trbs); + } + + preq->num_of_trb = num_trbs; + + /* + * Memory barrier - cycle bit must be set as the last operation. + */ + dma_wmb(); + + /* Give the TD to the consumer. */ + if (togle_pcs) + preq->trb->control = preq->trb->control ^ cpu_to_le32(1); + + cdns2_wa1_tray_restore_cycle_bit(pdev, pep); + cdns2_dbg_request_trbs(pep, preq); + + if (!pep->wa1_set && !(pep->ep_state & EP_STALLED) && !pep->skip) { + if (pep->type == USB_ENDPOINT_XFER_ISOC) { + ret = cdns2_prepare_first_isoc_transfer(pdev, pep); + if (ret) + return 0; + } + + cdns2_set_drdy(pdev, pep); + } + + return 0; +} + +/* Prepare and start transfer for all not started requests. */ +static int cdns2_start_all_request(struct cdns2_device *pdev, + struct cdns2_endpoint *pep) +{ + struct cdns2_request *preq; + int ret; + + while (!list_empty(&pep->deferred_list)) { + preq = cdns2_next_preq(&pep->deferred_list); + + ret = cdns2_ep_run_transfer(pep, preq); + if (ret) + return ret; + + list_move_tail(&preq->list, &pep->pending_list); + } + + pep->ep_state &= ~EP_RING_FULL; + + return 0; +} + +/* + * Check whether trb has been handled by DMA. + * + * Endpoint must be selected before invoking this function. + * + * Returns false if request has not been handled by DMA, else returns true. + * + * SR - start ring + * ER - end ring + * DQ = ring->dequeue - dequeue position + * EQ = ring->enqueue - enqueue position + * ST = preq->start_trb - index of first TRB in transfer ring + * ET = preq->end_trb - index of last TRB in transfer ring + * CI = current_index - index of processed TRB by DMA. + * + * As first step, we check if the TRB between the ST and ET. + * Then, we check if cycle bit for index pep->dequeue + * is correct. + * + * some rules: + * 1. ring->dequeue never equals to current_index. + * 2 ring->enqueue never exceed ring->dequeue + * 3. exception: ring->enqueue == ring->dequeue + * and ring->free_trbs is zero. + * This case indicate that TR is full. + * + * At below two cases, the request have been handled. + * Case 1 - ring->dequeue < current_index + * SR ... EQ ... DQ ... CI ... ER + * SR ... DQ ... CI ... EQ ... ER + * + * Case 2 - ring->dequeue > current_index + * This situation takes place when CI go through the LINK TRB at the end of + * transfer ring. + * SR ... CI ... EQ ... DQ ... ER + */ +static bool cdns2_trb_handled(struct cdns2_endpoint *pep, + struct cdns2_request *preq) +{ + struct cdns2_device *pdev = pep->pdev; + struct cdns2_ring *ring; + struct cdns2_trb *trb; + int current_index = 0; + int handled = 0; + int doorbell; + + ring = &pep->ring; + current_index = cdns2_get_dma_pos(pdev, pep); + doorbell = !!(readl(&pdev->adma_regs->ep_cmd) & DMA_EP_CMD_DRDY); + + /* + * Only ISO transfer can use 2 entries outside the standard + * Transfer Ring. First of them is used as zero length packet and the + * second as LINK TRB. + */ + if (current_index >= TRBS_PER_SEGMENT) + goto finish; + + /* Current trb doesn't belong to this request. */ + if (preq->start_trb < preq->end_trb) { + if (ring->dequeue > preq->end_trb) + goto finish; + + if (ring->dequeue < preq->start_trb) + goto finish; + } + + if (preq->start_trb > preq->end_trb && ring->dequeue > preq->end_trb && + ring->dequeue < preq->start_trb) + goto finish; + + if (preq->start_trb == preq->end_trb && ring->dequeue != preq->end_trb) + goto finish; + + trb = &ring->trbs[ring->dequeue]; + + if ((le32_to_cpu(trb->control) & TRB_CYCLE) != ring->ccs) + goto finish; + + if (doorbell == 1 && current_index == ring->dequeue) + goto finish; + + /* The corner case for TRBS_PER_SEGMENT equal 2). */ + if (TRBS_PER_SEGMENT == 2 && pep->type != USB_ENDPOINT_XFER_ISOC) { + handled = 1; + goto finish; + } + + if (ring->enqueue == ring->dequeue && + ring->free_trbs == 0) { + handled = 1; + } else if (ring->dequeue < current_index) { + if ((current_index == (TRBS_PER_SEGMENT - 1)) && + !ring->dequeue) + goto finish; + + handled = 1; + } else if (ring->dequeue > current_index) { + handled = 1; + } + +finish: + trace_cdns2_request_handled(preq, current_index, handled); + + return handled; +} + +static void cdns2_skip_isoc_td(struct cdns2_device *pdev, + struct cdns2_endpoint *pep, + struct cdns2_request *preq) +{ + struct cdns2_trb *trb; + int i; + + trb = pep->ring.trbs + pep->ring.dequeue; + + for (i = preq->finished_trb ; i < preq->num_of_trb; i++) { + preq->finished_trb++; + trace_cdns2_complete_trb(pep, trb); + cdns2_ep_inc_deq(&pep->ring); + trb = cdns2_next_trb(pep, trb); + } + + cdns2_gadget_giveback(pep, preq, 0); + cdns2_prepare_first_isoc_transfer(pdev, pep); + pep->skip = false; + cdns2_set_drdy(pdev, pep); +} + +static void cdns2_transfer_completed(struct cdns2_device *pdev, + struct cdns2_endpoint *pep) +{ + struct cdns2_request *preq = NULL; + bool request_handled = false; + struct cdns2_trb *trb; + + while (!list_empty(&pep->pending_list)) { + preq = cdns2_next_preq(&pep->pending_list); + trb = pep->ring.trbs + pep->ring.dequeue; + + /* + * The TRB was changed as link TRB, and the request + * was handled at ep_dequeue. + */ + while (TRB_FIELD_TO_TYPE(le32_to_cpu(trb->control)) == TRB_LINK && + le32_to_cpu(trb->length)) { + trace_cdns2_complete_trb(pep, trb); + cdns2_ep_inc_deq(&pep->ring); + trb = pep->ring.trbs + pep->ring.dequeue; + } + + /* + * Re-select endpoint. It could be changed by other CPU + * during handling usb_gadget_giveback_request. + */ + cdns2_select_ep(pdev, pep->endpoint.address); + + while (cdns2_trb_handled(pep, preq)) { + preq->finished_trb++; + + if (preq->finished_trb >= preq->num_of_trb) + request_handled = true; + + trb = pep->ring.trbs + pep->ring.dequeue; + trace_cdns2_complete_trb(pep, trb); + + if (pep->dir && pep->type == USB_ENDPOINT_XFER_ISOC) + /* + * For ISOC IN controller doens't update the + * trb->length. + */ + preq->request.actual = preq->request.length; + else + preq->request.actual += + TRB_LEN(le32_to_cpu(trb->length)); + + cdns2_ep_inc_deq(&pep->ring); + } + + if (request_handled) { + cdns2_gadget_giveback(pep, preq, 0); + request_handled = false; + } else { + goto prepare_next_td; + } + + if (pep->type != USB_ENDPOINT_XFER_ISOC && + TRBS_PER_SEGMENT == 2) + break; + } + +prepare_next_td: + if (pep->skip && preq) + cdns2_skip_isoc_td(pdev, pep, preq); + + if (!(pep->ep_state & EP_STALLED) && + !(pep->ep_state & EP_STALL_PENDING)) + cdns2_start_all_request(pdev, pep); +} + +static void cdns2_wakeup(struct cdns2_device *pdev) +{ + if (!pdev->may_wakeup) + return; + + /* Start driving resume signaling to indicate remote wakeup. */ + set_reg_bit_8(&pdev->usb_regs->usbcs, USBCS_SIGRSUME); +} + +static void cdns2_rearm_transfer(struct cdns2_endpoint *pep, u8 rearm) +{ + struct cdns2_device *pdev = pep->pdev; + + cdns2_wa1_restore_cycle_bit(pep); + + if (rearm) { + trace_cdns2_ring(pep); + + /* Cycle Bit must be updated before arming DMA. */ + dma_wmb(); + + writel(DMA_EP_CMD_DRDY, &pdev->adma_regs->ep_cmd); + + cdns2_wakeup(pdev); + + trace_cdns2_doorbell_epx(pep, + readl(&pdev->adma_regs->ep_traddr)); + } +} + +static void cdns2_handle_epx_interrupt(struct cdns2_endpoint *pep) +{ + struct cdns2_device *pdev = pep->pdev; + u8 isoerror = 0; + u32 ep_sts_reg; + u32 val; + + cdns2_select_ep(pdev, pep->endpoint.address); + + trace_cdns2_epx_irq(pdev, pep); + + ep_sts_reg = readl(&pdev->adma_regs->ep_sts); + writel(ep_sts_reg, &pdev->adma_regs->ep_sts); + + if (pep->type == USB_ENDPOINT_XFER_ISOC) { + u8 mult; + u8 cs; + + mult = USB_EP_MAXP_MULT(pep->endpoint.desc->wMaxPacketSize); + cs = pep->dir ? readb(&pdev->epx_regs->ep[pep->num - 1].txcs) : + readb(&pdev->epx_regs->ep[pep->num - 1].rxcs); + if (mult > 0) + isoerror = EPX_CS_ERR(cs); + } + + /* + * Sometimes ISO Error for mult=1 or mult=2 is not propagated on time + * from USB module to DMA module. To protect against this driver + * checks also the txcs/rxcs registers. + */ + if ((ep_sts_reg & DMA_EP_STS_ISOERR) || isoerror) { + clear_reg_bit_32(&pdev->adma_regs->ep_cfg, DMA_EP_CFG_ENABLE); + + /* Wait for DBUSY cleared. */ + readl_poll_timeout_atomic(&pdev->adma_regs->ep_sts, val, + !(val & DMA_EP_STS_DBUSY), 1, 125); + + writel(DMA_EP_CMD_DFLUSH, &pep->pdev->adma_regs->ep_cmd); + + /* Wait for DFLUSH cleared. */ + readl_poll_timeout_atomic(&pep->pdev->adma_regs->ep_cmd, val, + !(val & DMA_EP_CMD_DFLUSH), 1, 10); + + pep->skip = true; + } + + if (ep_sts_reg & DMA_EP_STS_TRBERR || pep->skip) { + if (pep->ep_state & EP_STALL_PENDING && + !(ep_sts_reg & DMA_EP_STS_DESCMIS)) + cdns2_ep_stall_flush(pep); + + /* + * For isochronous transfer driver completes request on + * IOC or on TRBERR. IOC appears only when device receive + * OUT data packet. If host disable stream or lost some packet + * then the only way to finish all queued transfer is to do it + * on TRBERR event. + */ + if (pep->type == USB_ENDPOINT_XFER_ISOC && !pep->wa1_set) { + if (!pep->dir) + clear_reg_bit_32(&pdev->adma_regs->ep_cfg, + DMA_EP_CFG_ENABLE); + + cdns2_transfer_completed(pdev, pep); + if (pep->ep_state & EP_DEFERRED_DRDY) { + pep->ep_state &= ~EP_DEFERRED_DRDY; + cdns2_set_drdy(pdev, pep); + } + + return; + } + + cdns2_transfer_completed(pdev, pep); + + if (!(pep->ep_state & EP_STALLED) && + !(pep->ep_state & EP_STALL_PENDING)) { + if (pep->ep_state & EP_DEFERRED_DRDY) { + pep->ep_state &= ~EP_DEFERRED_DRDY; + cdns2_start_all_request(pdev, pep); + } else { + cdns2_rearm_transfer(pep, pep->wa1_set); + } + } + + return; + } + + if ((ep_sts_reg & DMA_EP_STS_IOC) || (ep_sts_reg & DMA_EP_STS_ISP)) + cdns2_transfer_completed(pdev, pep); +} + +static void cdns2_disconnect_gadget(struct cdns2_device *pdev) +{ + if (pdev->gadget_driver && pdev->gadget_driver->disconnect) + pdev->gadget_driver->disconnect(&pdev->gadget); +} + +static irqreturn_t cdns2_usb_irq_handler(int irq, void *data) +{ + struct cdns2_device *pdev = data; + unsigned long reg_ep_ists; + u8 reg_usb_irq_m; + u8 reg_ext_irq_m; + u8 reg_usb_irq; + u8 reg_ext_irq; + + if (pdev->in_lpm) + return IRQ_NONE; + + reg_usb_irq_m = readb(&pdev->interrupt_regs->usbien); + reg_ext_irq_m = readb(&pdev->interrupt_regs->extien); + + /* Mask all sources of interrupt. */ + writeb(0, &pdev->interrupt_regs->usbien); + writeb(0, &pdev->interrupt_regs->extien); + writel(0, &pdev->adma_regs->ep_ien); + + /* Clear interrupt sources. */ + writel(0, &pdev->adma_regs->ep_sts); + writeb(0, &pdev->interrupt_regs->usbirq); + writeb(0, &pdev->interrupt_regs->extirq); + + reg_ep_ists = readl(&pdev->adma_regs->ep_ists); + reg_usb_irq = readb(&pdev->interrupt_regs->usbirq); + reg_ext_irq = readb(&pdev->interrupt_regs->extirq); + + if (reg_ep_ists || (reg_usb_irq & reg_usb_irq_m) || + (reg_ext_irq & reg_ext_irq_m)) + return IRQ_WAKE_THREAD; + + writeb(USB_IEN_INIT, &pdev->interrupt_regs->usbien); + writeb(EXTIRQ_WAKEUP, &pdev->interrupt_regs->extien); + writel(~0, &pdev->adma_regs->ep_ien); + + return IRQ_NONE; +} + +static irqreturn_t cdns2_thread_usb_irq_handler(struct cdns2_device *pdev) +{ + u8 usb_irq, ext_irq; + int speed; + int i; + + ext_irq = readb(&pdev->interrupt_regs->extirq) & EXTIRQ_WAKEUP; + writeb(ext_irq, &pdev->interrupt_regs->extirq); + + usb_irq = readb(&pdev->interrupt_regs->usbirq) & USB_IEN_INIT; + writeb(usb_irq, &pdev->interrupt_regs->usbirq); + + if (!ext_irq && !usb_irq) + return IRQ_NONE; + + trace_cdns2_usb_irq(usb_irq, ext_irq); + + if (ext_irq & EXTIRQ_WAKEUP) { + if (pdev->gadget_driver && pdev->gadget_driver->resume) { + spin_unlock(&pdev->lock); + pdev->gadget_driver->resume(&pdev->gadget); + spin_lock(&pdev->lock); + } + } + + if (usb_irq & USBIRQ_LPM) { + u8 reg = readb(&pdev->usb_regs->lpmctrl); + + /* LPM1 enter */ + if (!(reg & LPMCTRLLH_LPMNYET)) + writeb(0, &pdev->usb_regs->sleep_clkgate); + } + + if (usb_irq & USBIRQ_SUSPEND) { + if (pdev->gadget_driver && pdev->gadget_driver->suspend) { + spin_unlock(&pdev->lock); + pdev->gadget_driver->suspend(&pdev->gadget); + spin_lock(&pdev->lock); + } + } + + if (usb_irq & USBIRQ_URESET) { + if (pdev->gadget_driver) { + pdev->dev_address = 0; + + spin_unlock(&pdev->lock); + usb_gadget_udc_reset(&pdev->gadget, + pdev->gadget_driver); + spin_lock(&pdev->lock); + + /* + * The USBIRQ_URESET is reported at the beginning of + * reset signal. 100ms is enough time to finish reset + * process. For high-speed reset procedure is completed + * when controller detect HS mode. + */ + for (i = 0; i < 100; i++) { + mdelay(1); + speed = cdns2_get_speed(pdev); + if (speed == USB_SPEED_HIGH) + break; + } + + pdev->gadget.speed = speed; + cdns2_enable_l1(pdev, 0); + cdns2_ep0_config(pdev); + pdev->may_wakeup = 0; + } + } + + if (usb_irq & USBIRQ_SUDAV) { + pdev->ep0_stage = CDNS2_SETUP_STAGE; + cdns2_handle_setup_packet(pdev); + } + + return IRQ_HANDLED; +} + +/* Deferred USB interrupt handler. */ +static irqreturn_t cdns2_thread_irq_handler(int irq, void *data) +{ + struct cdns2_device *pdev = data; + unsigned long dma_ep_ists; + unsigned long flags; + unsigned int bit; + + local_bh_disable(); + spin_lock_irqsave(&pdev->lock, flags); + + cdns2_thread_usb_irq_handler(pdev); + + dma_ep_ists = readl(&pdev->adma_regs->ep_ists); + if (!dma_ep_ists) + goto unlock; + + trace_cdns2_dma_ep_ists(dma_ep_ists); + + /* Handle default endpoint OUT. */ + if (dma_ep_ists & DMA_EP_ISTS_EP_OUT0) + cdns2_handle_ep0_interrupt(pdev, USB_DIR_OUT); + + /* Handle default endpoint IN. */ + if (dma_ep_ists & DMA_EP_ISTS_EP_IN0) + cdns2_handle_ep0_interrupt(pdev, USB_DIR_IN); + + dma_ep_ists &= ~(DMA_EP_ISTS_EP_OUT0 | DMA_EP_ISTS_EP_IN0); + + for_each_set_bit(bit, &dma_ep_ists, sizeof(u32) * BITS_PER_BYTE) { + u8 ep_idx = bit > 16 ? (bit - 16) * 2 : (bit * 2) - 1; + + /* + * Endpoints in pdev->eps[] are held in order: + * ep0, ep1out, ep1in, ep2out, ep2in... ep15out, ep15in. + * but in dma_ep_ists in order: + * ep0 ep1out ep2out ... ep15out ep0in ep1in .. ep15in + */ + cdns2_handle_epx_interrupt(&pdev->eps[ep_idx]); + } + +unlock: + writel(~0, &pdev->adma_regs->ep_ien); + writeb(USB_IEN_INIT, &pdev->interrupt_regs->usbien); + writeb(EXTIRQ_WAKEUP, &pdev->interrupt_regs->extien); + + spin_unlock_irqrestore(&pdev->lock, flags); + local_bh_enable(); + + return IRQ_HANDLED; +} + +/* Calculates and assigns onchip memory for endpoints. */ +static void cdns2_eps_onchip_buffer_init(struct cdns2_device *pdev) +{ + struct cdns2_endpoint *pep; + int min_buf_tx = 0; + int min_buf_rx = 0; + u16 tx_offset = 0; + u16 rx_offset = 0; + int free; + int i; + + for (i = 0; i < CDNS2_ENDPOINTS_NUM; i++) { + pep = &pdev->eps[i]; + + if (!(pep->ep_state & EP_CLAIMED)) + continue; + + if (pep->dir) + min_buf_tx += pep->buffering; + else + min_buf_rx += pep->buffering; + } + + for (i = 0; i < CDNS2_ENDPOINTS_NUM; i++) { + pep = &pdev->eps[i]; + + if (!(pep->ep_state & EP_CLAIMED)) + continue; + + if (pep->dir) { + free = pdev->onchip_tx_buf - min_buf_tx; + + if (free + pep->buffering >= 4) + free = 4; + else + free = free + pep->buffering; + + min_buf_tx = min_buf_tx - pep->buffering + free; + + pep->buffering = free; + + writel(tx_offset, + &pdev->epx_regs->txstaddr[pep->num - 1]); + pdev->epx_regs->txstaddr[pep->num - 1] = tx_offset; + + dev_dbg(pdev->dev, "%s onchip address %04x, buffering: %d\n", + pep->name, tx_offset, pep->buffering); + + tx_offset += pep->buffering * 1024; + } else { + free = pdev->onchip_rx_buf - min_buf_rx; + + if (free + pep->buffering >= 4) + free = 4; + else + free = free + pep->buffering; + + min_buf_rx = min_buf_rx - pep->buffering + free; + + pep->buffering = free; + writel(rx_offset, + &pdev->epx_regs->rxstaddr[pep->num - 1]); + + dev_dbg(pdev->dev, "%s onchip address %04x, buffering: %d\n", + pep->name, rx_offset, pep->buffering); + + rx_offset += pep->buffering * 1024; + } + } +} + +/* Configure hardware endpoint. */ +static int cdns2_ep_config(struct cdns2_endpoint *pep, bool enable) +{ + bool is_iso_ep = (pep->type == USB_ENDPOINT_XFER_ISOC); + struct cdns2_device *pdev = pep->pdev; + u32 max_packet_size; + u8 dir = 0; + u8 ep_cfg; + u8 mult; + u32 val; + int ret; + + switch (pep->type) { + case USB_ENDPOINT_XFER_INT: + ep_cfg = EPX_CON_TYPE_INT; + break; + case USB_ENDPOINT_XFER_BULK: + ep_cfg = EPX_CON_TYPE_BULK; + break; + default: + mult = USB_EP_MAXP_MULT(pep->endpoint.desc->wMaxPacketSize); + ep_cfg = mult << EPX_CON_ISOD_SHIFT; + ep_cfg |= EPX_CON_TYPE_ISOC; + + if (pep->dir) { + set_reg_bit_8(&pdev->epx_regs->isoautoarm, BIT(pep->num)); + set_reg_bit_8(&pdev->epx_regs->isoautodump, BIT(pep->num)); + set_reg_bit_8(&pdev->epx_regs->isodctrl, BIT(pep->num)); + } + } + + switch (pdev->gadget.speed) { + case USB_SPEED_FULL: + max_packet_size = is_iso_ep ? 1023 : 64; + break; + case USB_SPEED_HIGH: + max_packet_size = is_iso_ep ? 1024 : 512; + break; + default: + /* All other speed are not supported. */ + return -EINVAL; + } + + ep_cfg |= (EPX_CON_VAL | (pep->buffering - 1)); + + if (pep->dir) { + dir = FIFOCTRL_IO_TX; + writew(max_packet_size, &pdev->epx_regs->txmaxpack[pep->num - 1]); + writeb(ep_cfg, &pdev->epx_regs->ep[pep->num - 1].txcon); + } else { + writew(max_packet_size, &pdev->epx_regs->rxmaxpack[pep->num - 1]); + writeb(ep_cfg, &pdev->epx_regs->ep[pep->num - 1].rxcon); + } + + writeb(pep->num | dir | FIFOCTRL_FIFOAUTO, + &pdev->usb_regs->fifoctrl); + writeb(pep->num | dir, &pdev->epx_regs->endprst); + writeb(pep->num | ENDPRST_FIFORST | ENDPRST_TOGRST | dir, + &pdev->epx_regs->endprst); + + if (max_packet_size == 1024) + pep->trb_burst_size = 128; + else if (max_packet_size >= 512) + pep->trb_burst_size = 64; + else + pep->trb_burst_size = 16; + + cdns2_select_ep(pdev, pep->num | pep->dir); + writel(DMA_EP_CMD_EPRST | DMA_EP_CMD_DFLUSH, &pdev->adma_regs->ep_cmd); + + ret = readl_poll_timeout_atomic(&pdev->adma_regs->ep_cmd, val, + !(val & (DMA_EP_CMD_DFLUSH | + DMA_EP_CMD_EPRST)), + 1, 1000); + + if (ret) + return ret; + + writel(DMA_EP_STS_TRBERR | DMA_EP_STS_ISOERR, &pdev->adma_regs->ep_sts_en); + + if (enable) + writel(DMA_EP_CFG_ENABLE, &pdev->adma_regs->ep_cfg); + + trace_cdns2_epx_hw_cfg(pdev, pep); + + dev_dbg(pdev->dev, "Configure %s: with MPS: %08x, ep con: %02x\n", + pep->name, max_packet_size, ep_cfg); + + return 0; +} + +struct usb_request *cdns2_gadget_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct cdns2_endpoint *pep = ep_to_cdns2_ep(ep); + struct cdns2_request *preq; + + preq = kzalloc(sizeof(*preq), gfp_flags); + if (!preq) + return NULL; + + preq->pep = pep; + + trace_cdns2_alloc_request(preq); + + return &preq->request; +} + +void cdns2_gadget_ep_free_request(struct usb_ep *ep, + struct usb_request *request) +{ + struct cdns2_request *preq = to_cdns2_request(request); + + trace_cdns2_free_request(preq); + kfree(preq); +} + +static int cdns2_gadget_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + u32 reg = DMA_EP_STS_EN_TRBERREN; + struct cdns2_endpoint *pep; + struct cdns2_device *pdev; + unsigned long flags; + int enable = 1; + int ret = 0; + + if (!ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT || + !desc->wMaxPacketSize) { + return -EINVAL; + } + + pep = ep_to_cdns2_ep(ep); + pdev = pep->pdev; + + if (dev_WARN_ONCE(pdev->dev, pep->ep_state & EP_ENABLED, + "%s is already enabled\n", pep->name)) + return 0; + + spin_lock_irqsave(&pdev->lock, flags); + + pep->type = usb_endpoint_type(desc); + pep->interval = desc->bInterval ? BIT(desc->bInterval - 1) : 0; + + if (pdev->gadget.speed == USB_SPEED_FULL) + if (pep->type == USB_ENDPOINT_XFER_INT) + pep->interval = desc->bInterval; + + if (pep->interval > ISO_MAX_INTERVAL && + pep->type == USB_ENDPOINT_XFER_ISOC) { + dev_err(pdev->dev, "ISO period is limited to %d (current: %d)\n", + ISO_MAX_INTERVAL, pep->interval); + + ret = -EINVAL; + goto exit; + } + + /* + * During ISO OUT traffic DMA reads Transfer Ring for the EP which has + * never got doorbell. + * This issue was detected only on simulation, but to avoid this issue + * driver add protection against it. To fix it driver enable ISO OUT + * endpoint before setting DRBL. This special treatment of ISO OUT + * endpoints are recommended by controller specification. + */ + if (pep->type == USB_ENDPOINT_XFER_ISOC && !pep->dir) + enable = 0; + + ret = cdns2_alloc_tr_segment(pep); + if (ret) + goto exit; + + ret = cdns2_ep_config(pep, enable); + if (ret) { + cdns2_free_tr_segment(pep); + ret = -EINVAL; + goto exit; + } + + trace_cdns2_gadget_ep_enable(pep); + + pep->ep_state &= ~(EP_STALLED | EP_STALL_PENDING); + pep->ep_state |= EP_ENABLED; + pep->wa1_set = 0; + pep->ring.enqueue = 0; + pep->ring.dequeue = 0; + reg = readl(&pdev->adma_regs->ep_sts); + pep->ring.pcs = !!DMA_EP_STS_CCS(reg); + pep->ring.ccs = !!DMA_EP_STS_CCS(reg); + + writel(pep->ring.dma, &pdev->adma_regs->ep_traddr); + + /* one TRB is reserved for link TRB used in DMULT mode*/ + pep->ring.free_trbs = TRBS_PER_SEGMENT - 1; + +exit: + spin_unlock_irqrestore(&pdev->lock, flags); + + return ret; +} + +static int cdns2_gadget_ep_disable(struct usb_ep *ep) +{ + struct cdns2_endpoint *pep; + struct cdns2_request *preq; + struct cdns2_device *pdev; + unsigned long flags; + int val; + + if (!ep) + return -EINVAL; + + pep = ep_to_cdns2_ep(ep); + pdev = pep->pdev; + + if (dev_WARN_ONCE(pdev->dev, !(pep->ep_state & EP_ENABLED), + "%s is already disabled\n", pep->name)) + return 0; + + spin_lock_irqsave(&pdev->lock, flags); + + trace_cdns2_gadget_ep_disable(pep); + + cdns2_select_ep(pdev, ep->desc->bEndpointAddress); + + clear_reg_bit_32(&pdev->adma_regs->ep_cfg, DMA_EP_CFG_ENABLE); + + /* + * Driver needs some time before resetting endpoint. + * It need waits for clearing DBUSY bit or for timeout expired. + * 10us is enough time for controller to stop transfer. + */ + readl_poll_timeout_atomic(&pdev->adma_regs->ep_sts, val, + !(val & DMA_EP_STS_DBUSY), 1, 10); + writel(DMA_EP_CMD_EPRST, &pdev->adma_regs->ep_cmd); + + readl_poll_timeout_atomic(&pdev->adma_regs->ep_cmd, val, + !(val & (DMA_EP_CMD_DFLUSH | DMA_EP_CMD_EPRST)), + 1, 1000); + + while (!list_empty(&pep->pending_list)) { + preq = cdns2_next_preq(&pep->pending_list); + cdns2_gadget_giveback(pep, preq, -ESHUTDOWN); + } + + while (!list_empty(&pep->deferred_list)) { + preq = cdns2_next_preq(&pep->deferred_list); + cdns2_gadget_giveback(pep, preq, -ESHUTDOWN); + } + + ep->desc = NULL; + pep->ep_state &= ~EP_ENABLED; + + spin_unlock_irqrestore(&pdev->lock, flags); + + return 0; +} + +static int cdns2_ep_enqueue(struct cdns2_endpoint *pep, + struct cdns2_request *preq, + gfp_t gfp_flags) +{ + struct cdns2_device *pdev = pep->pdev; + struct usb_request *request; + int ret; + + request = &preq->request; + request->actual = 0; + request->status = -EINPROGRESS; + + ret = usb_gadget_map_request_by_dev(pdev->dev, request, pep->dir); + if (ret) { + trace_cdns2_request_enqueue_error(preq); + return ret; + } + + list_add_tail(&preq->list, &pep->deferred_list); + trace_cdns2_request_enqueue(preq); + + if (!(pep->ep_state & EP_STALLED) && !(pep->ep_state & EP_STALL_PENDING)) + cdns2_start_all_request(pdev, pep); + + return 0; +} + +static int cdns2_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, + gfp_t gfp_flags) +{ + struct usb_request *zlp_request; + struct cdns2_request *preq; + struct cdns2_endpoint *pep; + struct cdns2_device *pdev; + unsigned long flags; + int ret; + + if (!request || !ep) + return -EINVAL; + + pep = ep_to_cdns2_ep(ep); + pdev = pep->pdev; + + if (!(pep->ep_state & EP_ENABLED)) { + dev_err(pdev->dev, "%s: can't queue to disabled endpoint\n", + pep->name); + return -EINVAL; + } + + spin_lock_irqsave(&pdev->lock, flags); + + preq = to_cdns2_request(request); + ret = cdns2_ep_enqueue(pep, preq, gfp_flags); + + if (ret == 0 && request->zero && request->length && + (request->length % ep->maxpacket == 0)) { + struct cdns2_request *preq; + + zlp_request = cdns2_gadget_ep_alloc_request(ep, GFP_ATOMIC); + zlp_request->buf = pdev->zlp_buf; + zlp_request->length = 0; + + preq = to_cdns2_request(zlp_request); + ret = cdns2_ep_enqueue(pep, preq, gfp_flags); + } + + spin_unlock_irqrestore(&pdev->lock, flags); + return ret; +} + +int cdns2_gadget_ep_dequeue(struct usb_ep *ep, + struct usb_request *request) +{ + struct cdns2_request *preq, *preq_temp, *cur_preq; + struct cdns2_endpoint *pep; + struct cdns2_trb *link_trb; + u8 req_on_hw_ring = 0; + unsigned long flags; + u32 buffer; + int val, i; + + if (!ep || !request || !ep->desc) + return -EINVAL; + + pep = ep_to_cdns2_ep(ep); + if (!pep->endpoint.desc) { + dev_err(pep->pdev->dev, "%s: can't dequeue to disabled endpoint\n", + pep->name); + return -ESHUTDOWN; + } + + /* Requests has been dequeued during disabling endpoint. */ + if (!(pep->ep_state & EP_ENABLED)) + return 0; + + spin_lock_irqsave(&pep->pdev->lock, flags); + + cur_preq = to_cdns2_request(request); + trace_cdns2_request_dequeue(cur_preq); + + list_for_each_entry_safe(preq, preq_temp, &pep->pending_list, list) { + if (cur_preq == preq) { + req_on_hw_ring = 1; + goto found; + } + } + + list_for_each_entry_safe(preq, preq_temp, &pep->deferred_list, list) { + if (cur_preq == preq) + goto found; + } + + goto not_found; + +found: + link_trb = preq->trb; + + /* Update ring only if removed request is on pending_req_list list. */ + if (req_on_hw_ring && link_trb) { + /* Stop DMA */ + writel(DMA_EP_CMD_DFLUSH, &pep->pdev->adma_regs->ep_cmd); + + /* Wait for DFLUSH cleared. */ + readl_poll_timeout_atomic(&pep->pdev->adma_regs->ep_cmd, val, + !(val & DMA_EP_CMD_DFLUSH), 1, 1000); + + buffer = cpu_to_le32(TRB_BUFFER(pep->ring.dma + + ((preq->end_trb + 1) * TRB_SIZE))); + + for (i = 0; i < preq->num_of_trb; i++) { + link_trb->buffer = buffer; + link_trb->control = cpu_to_le32((le32_to_cpu(link_trb->control) + & TRB_CYCLE) | TRB_CHAIN | + TRB_TYPE(TRB_LINK)); + + trace_cdns2_queue_trb(pep, link_trb); + link_trb = cdns2_next_trb(pep, link_trb); + } + + if (pep->wa1_trb == preq->trb) + cdns2_wa1_restore_cycle_bit(pep); + } + + cdns2_gadget_giveback(pep, cur_preq, -ECONNRESET); + + preq = cdns2_next_preq(&pep->pending_list); + if (preq) + cdns2_rearm_transfer(pep, 1); + +not_found: + spin_unlock_irqrestore(&pep->pdev->lock, flags); + return 0; +} + +int cdns2_halt_endpoint(struct cdns2_device *pdev, + struct cdns2_endpoint *pep, + int value) +{ + u8 __iomem *conf; + int dir = 0; + + if (!(pep->ep_state & EP_ENABLED)) + return -EPERM; + + if (pep->dir) { + dir = ENDPRST_IO_TX; + conf = &pdev->epx_regs->ep[pep->num - 1].txcon; + } else { + conf = &pdev->epx_regs->ep[pep->num - 1].rxcon; + } + + if (!value) { + struct cdns2_trb *trb = NULL; + struct cdns2_request *preq; + struct cdns2_trb trb_tmp; + + preq = cdns2_next_preq(&pep->pending_list); + if (preq) { + trb = preq->trb; + if (trb) { + trb_tmp = *trb; + trb->control = trb->control ^ cpu_to_le32(TRB_CYCLE); + } + } + + trace_cdns2_ep_halt(pep, 0, 0); + + /* Resets Sequence Number */ + writeb(dir | pep->num, &pdev->epx_regs->endprst); + writeb(dir | ENDPRST_TOGRST | pep->num, + &pdev->epx_regs->endprst); + + clear_reg_bit_8(conf, EPX_CON_STALL); + + pep->ep_state &= ~(EP_STALLED | EP_STALL_PENDING); + + if (preq) { + if (trb) + *trb = trb_tmp; + + cdns2_rearm_transfer(pep, 1); + } + + cdns2_start_all_request(pdev, pep); + } else { + trace_cdns2_ep_halt(pep, 1, 0); + set_reg_bit_8(conf, EPX_CON_STALL); + writeb(dir | pep->num, &pdev->epx_regs->endprst); + writeb(dir | ENDPRST_FIFORST | pep->num, + &pdev->epx_regs->endprst); + pep->ep_state |= EP_STALLED; + } + + return 0; +} + +/* Sets/clears stall on selected endpoint. */ +static int cdns2_gadget_ep_set_halt(struct usb_ep *ep, int value) +{ + struct cdns2_endpoint *pep = ep_to_cdns2_ep(ep); + struct cdns2_device *pdev = pep->pdev; + struct cdns2_request *preq; + unsigned long flags = 0; + int ret; + + spin_lock_irqsave(&pdev->lock, flags); + + preq = cdns2_next_preq(&pep->pending_list); + if (value && preq) { + trace_cdns2_ep_busy_try_halt_again(pep); + ret = -EAGAIN; + goto done; + } + + if (!value) + pep->ep_state &= ~EP_WEDGE; + + ret = cdns2_halt_endpoint(pdev, pep, value); + +done: + spin_unlock_irqrestore(&pdev->lock, flags); + return ret; +} + +static int cdns2_gadget_ep_set_wedge(struct usb_ep *ep) +{ + struct cdns2_endpoint *pep = ep_to_cdns2_ep(ep); + + cdns2_gadget_ep_set_halt(ep, 1); + pep->ep_state |= EP_WEDGE; + + return 0; +} + +static struct +cdns2_endpoint *cdns2_find_available_ep(struct cdns2_device *pdev, + struct usb_endpoint_descriptor *desc) +{ + struct cdns2_endpoint *pep; + struct usb_ep *ep; + int ep_correct; + + list_for_each_entry(ep, &pdev->gadget.ep_list, ep_list) { + unsigned long num; + int ret; + /* ep name pattern likes epXin or epXout. */ + char c[2] = {ep->name[2], '\0'}; + + ret = kstrtoul(c, 10, &num); + if (ret) + return ERR_PTR(ret); + pep = ep_to_cdns2_ep(ep); + + if (pep->num != num) + continue; + + ep_correct = (pep->endpoint.caps.dir_in && + usb_endpoint_dir_in(desc)) || + (pep->endpoint.caps.dir_out && + usb_endpoint_dir_out(desc)); + + if (ep_correct && !(pep->ep_state & EP_CLAIMED)) + return pep; + } + + return ERR_PTR(-ENOENT); +} + +/* + * Function used to recognize which endpoints will be used to optimize + * on-chip memory usage. + */ +static struct +usb_ep *cdns2_gadget_match_ep(struct usb_gadget *gadget, + struct usb_endpoint_descriptor *desc, + struct usb_ss_ep_comp_descriptor *comp_desc) +{ + struct cdns2_device *pdev = gadget_to_cdns2_device(gadget); + struct cdns2_endpoint *pep; + unsigned long flags; + + pep = cdns2_find_available_ep(pdev, desc); + if (IS_ERR(pep)) { + dev_err(pdev->dev, "no available ep\n"); + return NULL; + } + + spin_lock_irqsave(&pdev->lock, flags); + + if (usb_endpoint_type(desc) == USB_ENDPOINT_XFER_ISOC) + pep->buffering = 4; + else + pep->buffering = 1; + + pep->ep_state |= EP_CLAIMED; + spin_unlock_irqrestore(&pdev->lock, flags); + + return &pep->endpoint; +} + +static const struct usb_ep_ops cdns2_gadget_ep_ops = { + .enable = cdns2_gadget_ep_enable, + .disable = cdns2_gadget_ep_disable, + .alloc_request = cdns2_gadget_ep_alloc_request, + .free_request = cdns2_gadget_ep_free_request, + .queue = cdns2_gadget_ep_queue, + .dequeue = cdns2_gadget_ep_dequeue, + .set_halt = cdns2_gadget_ep_set_halt, + .set_wedge = cdns2_gadget_ep_set_wedge, +}; + +static int cdns2_gadget_get_frame(struct usb_gadget *gadget) +{ + struct cdns2_device *pdev = gadget_to_cdns2_device(gadget); + + return readw(&pdev->usb_regs->frmnr); +} + +static int cdns2_gadget_wakeup(struct usb_gadget *gadget) +{ + struct cdns2_device *pdev = gadget_to_cdns2_device(gadget); + unsigned long flags; + + spin_lock_irqsave(&pdev->lock, flags); + cdns2_wakeup(pdev); + spin_unlock_irqrestore(&pdev->lock, flags); + + return 0; +} + +static int cdns2_gadget_set_selfpowered(struct usb_gadget *gadget, + int is_selfpowered) +{ + struct cdns2_device *pdev = gadget_to_cdns2_device(gadget); + unsigned long flags; + + spin_lock_irqsave(&pdev->lock, flags); + pdev->is_selfpowered = !!is_selfpowered; + spin_unlock_irqrestore(&pdev->lock, flags); + return 0; +} + +/* Disable interrupts and begin the controller halting process. */ +static void cdns2_quiesce(struct cdns2_device *pdev) +{ + set_reg_bit_8(&pdev->usb_regs->usbcs, USBCS_DISCON); + + /* Disable interrupt. */ + writeb(0, &pdev->interrupt_regs->extien), + writeb(0, &pdev->interrupt_regs->usbien), + writew(0, &pdev->adma_regs->ep_ien); + + /* Clear interrupt line. */ + writeb(0x0, &pdev->interrupt_regs->usbirq); +} + +static void cdns2_gadget_config(struct cdns2_device *pdev) +{ + cdns2_ep0_config(pdev); + + /* Enable DMA interrupts for all endpoints. */ + writel(~0x0, &pdev->adma_regs->ep_ien); + cdns2_enable_l1(pdev, 0); + writeb(USB_IEN_INIT, &pdev->interrupt_regs->usbien); + writeb(EXTIRQ_WAKEUP, &pdev->interrupt_regs->extien); + writel(DMA_CONF_DMULT, &pdev->adma_regs->conf); +} + +static int cdns2_gadget_pullup(struct usb_gadget *gadget, int is_on) +{ + struct cdns2_device *pdev = gadget_to_cdns2_device(gadget); + unsigned long flags; + + trace_cdns2_pullup(is_on); + + /* + * Disable events handling while controller is being + * enabled/disabled. + */ + disable_irq(pdev->irq); + spin_lock_irqsave(&pdev->lock, flags); + + if (is_on) { + cdns2_gadget_config(pdev); + clear_reg_bit_8(&pdev->usb_regs->usbcs, USBCS_DISCON); + } else { + cdns2_quiesce(pdev); + } + + spin_unlock_irqrestore(&pdev->lock, flags); + enable_irq(pdev->irq); + + return 0; +} + +static int cdns2_gadget_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct cdns2_device *pdev = gadget_to_cdns2_device(gadget); + enum usb_device_speed max_speed = driver->max_speed; + unsigned long flags; + + spin_lock_irqsave(&pdev->lock, flags); + pdev->gadget_driver = driver; + + /* Limit speed if necessary. */ + max_speed = min(driver->max_speed, gadget->max_speed); + + switch (max_speed) { + case USB_SPEED_FULL: + writeb(SPEEDCTRL_HSDISABLE, &pdev->usb_regs->speedctrl); + break; + case USB_SPEED_HIGH: + writeb(0, &pdev->usb_regs->speedctrl); + break; + default: + dev_err(pdev->dev, "invalid maximum_speed parameter %d\n", + max_speed); + fallthrough; + case USB_SPEED_UNKNOWN: + /* Default to highspeed. */ + max_speed = USB_SPEED_HIGH; + break; + } + + /* Reset all USB endpoints. */ + writeb(ENDPRST_IO_TX, &pdev->usb_regs->endprst); + writeb(ENDPRST_FIFORST | ENDPRST_TOGRST | ENDPRST_IO_TX, + &pdev->usb_regs->endprst); + writeb(ENDPRST_FIFORST | ENDPRST_TOGRST, &pdev->usb_regs->endprst); + + cdns2_eps_onchip_buffer_init(pdev); + + cdns2_gadget_config(pdev); + spin_unlock_irqrestore(&pdev->lock, flags); + + return 0; +} + +static int cdns2_gadget_udc_stop(struct usb_gadget *gadget) +{ + struct cdns2_device *pdev = gadget_to_cdns2_device(gadget); + struct cdns2_endpoint *pep; + u32 bEndpointAddress; + struct usb_ep *ep; + int val; + + pdev->gadget_driver = NULL; + pdev->gadget.speed = USB_SPEED_UNKNOWN; + + list_for_each_entry(ep, &pdev->gadget.ep_list, ep_list) { + pep = ep_to_cdns2_ep(ep); + bEndpointAddress = pep->num | pep->dir; + cdns2_select_ep(pdev, bEndpointAddress); + writel(DMA_EP_CMD_EPRST, &pdev->adma_regs->ep_cmd); + readl_poll_timeout_atomic(&pdev->adma_regs->ep_cmd, val, + !(val & DMA_EP_CMD_EPRST), 1, 100); + } + + cdns2_quiesce(pdev); + + writeb(ENDPRST_IO_TX, &pdev->usb_regs->endprst); + writeb(ENDPRST_FIFORST | ENDPRST_TOGRST | ENDPRST_IO_TX, + &pdev->epx_regs->endprst); + writeb(ENDPRST_FIFORST | ENDPRST_TOGRST, &pdev->epx_regs->endprst); + + return 0; +} + +static const struct usb_gadget_ops cdns2_gadget_ops = { + .get_frame = cdns2_gadget_get_frame, + .wakeup = cdns2_gadget_wakeup, + .set_selfpowered = cdns2_gadget_set_selfpowered, + .pullup = cdns2_gadget_pullup, + .udc_start = cdns2_gadget_udc_start, + .udc_stop = cdns2_gadget_udc_stop, + .match_ep = cdns2_gadget_match_ep, +}; + +static void cdns2_free_all_eps(struct cdns2_device *pdev) +{ + int i; + + for (i = 0; i < CDNS2_ENDPOINTS_NUM; i++) + cdns2_free_tr_segment(&pdev->eps[i]); +} + +/* Initializes software endpoints of gadget. */ +static int cdns2_init_eps(struct cdns2_device *pdev) +{ + struct cdns2_endpoint *pep; + int i; + + for (i = 0; i < CDNS2_ENDPOINTS_NUM; i++) { + bool direction = !(i & 1); /* Start from OUT endpoint. */ + u8 epnum = ((i + 1) >> 1); + + /* + * Endpoints are being held in pdev->eps[] in form: + * ep0, ep1out, ep1in ... ep15out, ep15in. + */ + if (!CDNS2_IF_EP_EXIST(pdev, epnum, direction)) + continue; + + pep = &pdev->eps[i]; + pep->pdev = pdev; + pep->num = epnum; + /* 0 for OUT, 1 for IN. */ + pep->dir = direction ? USB_DIR_IN : USB_DIR_OUT; + pep->idx = i; + + /* Ep0in and ep0out are represented by pdev->eps[0]. */ + if (!epnum) { + int ret; + + snprintf(pep->name, sizeof(pep->name), "ep%d%s", + epnum, "BiDir"); + + cdns2_init_ep0(pdev, pep); + + ret = cdns2_alloc_tr_segment(pep); + if (ret) { + dev_err(pdev->dev, "Failed to init ep0\n"); + return ret; + } + } else { + snprintf(pep->name, sizeof(pep->name), "ep%d%s", + epnum, !!direction ? "in" : "out"); + pep->endpoint.name = pep->name; + + usb_ep_set_maxpacket_limit(&pep->endpoint, 1024); + pep->endpoint.ops = &cdns2_gadget_ep_ops; + list_add_tail(&pep->endpoint.ep_list, &pdev->gadget.ep_list); + + pep->endpoint.caps.dir_in = direction; + pep->endpoint.caps.dir_out = !direction; + + pep->endpoint.caps.type_iso = 1; + pep->endpoint.caps.type_bulk = 1; + pep->endpoint.caps.type_int = 1; + } + + pep->endpoint.name = pep->name; + pep->ep_state = 0; + + dev_dbg(pdev->dev, "Init %s, SupType: CTRL: %s, INT: %s, " + "BULK: %s, ISOC %s, SupDir IN: %s, OUT: %s\n", + pep->name, + (pep->endpoint.caps.type_control) ? "yes" : "no", + (pep->endpoint.caps.type_int) ? "yes" : "no", + (pep->endpoint.caps.type_bulk) ? "yes" : "no", + (pep->endpoint.caps.type_iso) ? "yes" : "no", + (pep->endpoint.caps.dir_in) ? "yes" : "no", + (pep->endpoint.caps.dir_out) ? "yes" : "no"); + + INIT_LIST_HEAD(&pep->pending_list); + INIT_LIST_HEAD(&pep->deferred_list); + } + + return 0; +} + +static int cdns2_gadget_start(struct cdns2_device *pdev) +{ + u32 max_speed; + void *buf; + int val; + int ret; + + pdev->usb_regs = pdev->regs; + pdev->ep0_regs = pdev->regs; + pdev->epx_regs = pdev->regs; + pdev->interrupt_regs = pdev->regs; + pdev->adma_regs = pdev->regs + CDNS2_ADMA_REGS_OFFSET; + + /* Reset controller. */ + set_reg_bit_8(&pdev->usb_regs->cpuctrl, CPUCTRL_SW_RST); + + ret = readl_poll_timeout_atomic(&pdev->usb_regs->cpuctrl, val, + !(val & CPUCTRL_SW_RST), 1, 10000); + if (ret) { + dev_err(pdev->dev, "Error: reset controller timeout\n"); + return -EINVAL; + } + + usb_initialize_gadget(pdev->dev, &pdev->gadget, NULL); + + device_property_read_u16(pdev->dev, "cdns,on-chip-tx-buff-size", + &pdev->onchip_tx_buf); + device_property_read_u16(pdev->dev, "cdns,on-chip-rx-buff-size", + &pdev->onchip_rx_buf); + device_property_read_u32(pdev->dev, "cdns,avail-endpoints", + &pdev->eps_supported); + + /* + * Driver assumes that each USBHS controller has at least + * one IN and one OUT non control endpoint. + */ + if (!pdev->onchip_tx_buf && !pdev->onchip_rx_buf) { + ret = -EINVAL; + dev_err(pdev->dev, "Invalid on-chip memory configuration\n"); + goto put_gadget; + } + + if (!(pdev->eps_supported & ~0x00010001)) { + ret = -EINVAL; + dev_err(pdev->dev, "No hardware endpoints available\n"); + goto put_gadget; + } + + max_speed = usb_get_maximum_speed(pdev->dev); + + switch (max_speed) { + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + break; + default: + dev_err(pdev->dev, "invalid maximum_speed parameter %d\n", + max_speed); + fallthrough; + case USB_SPEED_UNKNOWN: + max_speed = USB_SPEED_HIGH; + break; + } + + pdev->gadget.max_speed = max_speed; + pdev->gadget.speed = USB_SPEED_UNKNOWN; + pdev->gadget.ops = &cdns2_gadget_ops; + pdev->gadget.name = "usbhs-gadget"; + pdev->gadget.quirk_avoids_skb_reserve = 1; + pdev->gadget.irq = pdev->irq; + + spin_lock_init(&pdev->lock); + INIT_WORK(&pdev->pending_status_wq, cdns2_pending_setup_status_handler); + + /* Initialize endpoint container. */ + INIT_LIST_HEAD(&pdev->gadget.ep_list); + pdev->eps_dma_pool = dma_pool_create("cdns2_eps_dma_pool", pdev->dev, + TR_SEG_SIZE, 8, 0); + if (!pdev->eps_dma_pool) { + dev_err(pdev->dev, "Failed to create TRB dma pool\n"); + ret = -ENOMEM; + goto put_gadget; + } + + ret = cdns2_init_eps(pdev); + if (ret) { + dev_err(pdev->dev, "Failed to create endpoints\n"); + goto destroy_dma_pool; + } + + pdev->gadget.sg_supported = 1; + + pdev->zlp_buf = kzalloc(CDNS2_EP_ZLP_BUF_SIZE, GFP_KERNEL); + if (!pdev->zlp_buf) { + ret = -ENOMEM; + goto destroy_dma_pool; + } + + /* Allocate memory for setup packet buffer. */ + buf = dma_alloc_coherent(pdev->dev, 8, &pdev->ep0_preq.request.dma, + GFP_DMA); + pdev->ep0_preq.request.buf = buf; + + if (!pdev->ep0_preq.request.buf) { + ret = -ENOMEM; + goto free_zlp_buf; + } + + /* Add USB gadget device. */ + ret = usb_add_gadget(&pdev->gadget); + if (ret < 0) { + dev_err(pdev->dev, "Failed to add gadget\n"); + goto free_ep0_buf; + } + + return 0; + +free_ep0_buf: + dma_free_coherent(pdev->dev, 8, pdev->ep0_preq.request.buf, + pdev->ep0_preq.request.dma); +free_zlp_buf: + kfree(pdev->zlp_buf); +destroy_dma_pool: + dma_pool_destroy(pdev->eps_dma_pool); +put_gadget: + usb_put_gadget(&pdev->gadget); + + return ret; +} + +int cdns2_gadget_suspend(struct cdns2_device *pdev) +{ + unsigned long flags; + + cdns2_disconnect_gadget(pdev); + + spin_lock_irqsave(&pdev->lock, flags); + pdev->gadget.speed = USB_SPEED_UNKNOWN; + + trace_cdns2_device_state("notattached"); + usb_gadget_set_state(&pdev->gadget, USB_STATE_NOTATTACHED); + cdns2_enable_l1(pdev, 0); + + /* Disable interrupt for device. */ + writeb(0, &pdev->interrupt_regs->usbien); + writel(0, &pdev->adma_regs->ep_ien); + spin_unlock_irqrestore(&pdev->lock, flags); + + return 0; +} + +int cdns2_gadget_resume(struct cdns2_device *pdev, bool hibernated) +{ + unsigned long flags; + + spin_lock_irqsave(&pdev->lock, flags); + + if (!pdev->gadget_driver) { + spin_unlock_irqrestore(&pdev->lock, flags); + return 0; + } + + cdns2_gadget_config(pdev); + + if (hibernated) + clear_reg_bit_8(&pdev->usb_regs->usbcs, USBCS_DISCON); + + spin_unlock_irqrestore(&pdev->lock, flags); + + return 0; +} + +void cdns2_gadget_remove(struct cdns2_device *pdev) +{ + pm_runtime_mark_last_busy(pdev->dev); + pm_runtime_put_autosuspend(pdev->dev); + + usb_del_gadget(&pdev->gadget); + cdns2_free_all_eps(pdev); + + dma_pool_destroy(pdev->eps_dma_pool); + kfree(pdev->zlp_buf); + usb_put_gadget(&pdev->gadget); +} + +int cdns2_gadget_init(struct cdns2_device *pdev) +{ + int ret; + + /* Ensure 32-bit DMA Mask. */ + ret = dma_set_mask_and_coherent(pdev->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(pdev->dev, "Failed to set dma mask: %d\n", ret); + return ret; + } + + pm_runtime_get_sync(pdev->dev); + + cdsn2_isoc_burst_opt(pdev); + + ret = cdns2_gadget_start(pdev); + if (ret) { + pm_runtime_put_sync(pdev->dev); + return ret; + } + + /* + * Because interrupt line can be shared with other components in + * driver it can't use IRQF_ONESHOT flag here. + */ + ret = devm_request_threaded_irq(pdev->dev, pdev->irq, + cdns2_usb_irq_handler, + cdns2_thread_irq_handler, + IRQF_SHARED, + dev_name(pdev->dev), + pdev); + if (ret) + goto err0; + + return 0; + +err0: + cdns2_gadget_remove(pdev); + + return ret; +} diff --git a/drivers/usb/gadget/udc/cdns2/cdns2-gadget.h b/drivers/usb/gadget/udc/cdns2/cdns2-gadget.h new file mode 100644 index 000000000000..71e2f62d653a --- /dev/null +++ b/drivers/usb/gadget/udc/cdns2/cdns2-gadget.h @@ -0,0 +1,707 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * USBHS-DEV device controller driver header file + * + * Copyright (C) 2023 Cadence. + * + * Author: Pawel Laszczak <pawell@cadence.com> + */ + +#ifndef __LINUX_CDNS2_GADGET +#define __LINUX_CDNS2_GADGET + +#include <linux/usb/gadget.h> +#include <linux/dma-direction.h> + +/* + * USBHS register interface. + * This corresponds to the USBHS Device Controller Interface. + */ + +/** + * struct cdns2_ep0_regs - endpoint 0 related registers. + * @rxbc: receive (OUT) 0 endpoint byte count register. + * @txbc: transmit (IN) 0 endpoint byte count register. + * @cs: 0 endpoint control and status register. + * @reserved1: reserved. + * @fifo: 0 endpoint fifo register. + * @reserved2: reserved. + * @setupdat: SETUP data register. + * @reserved4: reserved. + * @maxpack: 0 endpoint max packet size. + */ +struct cdns2_ep0_regs { + __u8 rxbc; + __u8 txbc; + __u8 cs; + __u8 reserved1[4]; + __u8 fifo; + __le32 reserved2[94]; + __u8 setupdat[8]; + __u8 reserved4[88]; + __u8 maxpack; +} __packed __aligned(4); + +/* EP0CS - bitmasks. */ +/* Endpoint 0 stall bit for status stage. */ +#define EP0CS_STALL BIT(0) +/* HSNAK bit. */ +#define EP0CS_HSNAK BIT(1) +/* IN 0 endpoint busy bit. */ +#define EP0CS_TXBSY_MSK BIT(2) +/* OUT 0 endpoint busy bit. */ +#define EP0CS_RXBSY_MSK BIT(3) +/* Send STALL in the data stage phase. */ +#define EP0CS_DSTALL BIT(4) +/* SETUP buffer content was changed. */ +#define EP0CS_CHGSET BIT(7) + +/* EP0FIFO - bitmasks. */ +/* Direction. */ +#define EP0_FIFO_IO_TX BIT(4) +/* FIFO auto bit. */ +#define EP0_FIFO_AUTO BIT(5) +/* FIFO commit bit. */ +#define EP0_FIFO_COMMIT BIT(6) +/* FIFO access bit. */ +#define EP0_FIFO_ACCES BIT(7) + +/** + * struct cdns2_epx_base - base endpoint registers. + * @rxbc: OUT endpoint byte count register. + * @rxcon: OUT endpoint control register. + * @rxcs: OUT endpoint control and status register. + * @txbc: IN endpoint byte count register. + * @txcon: IN endpoint control register. + * @txcs: IN endpoint control and status register. + */ +struct cdns2_epx_base { + __le16 rxbc; + __u8 rxcon; + __u8 rxcs; + __le16 txbc; + __u8 txcon; + __u8 txcs; +} __packed __aligned(4); + +/* rxcon/txcon - endpoint control register bitmasks. */ +/* Endpoint buffering: 0 - single buffering ... 3 - quad buffering. */ +#define EPX_CON_BUF GENMASK(1, 0) +/* Endpoint type. */ +#define EPX_CON_TYPE GENMASK(3, 2) +/* Endpoint type: isochronous. */ +#define EPX_CON_TYPE_ISOC 0x4 +/* Endpoint type: bulk. */ +#define EPX_CON_TYPE_BULK 0x8 +/* Endpoint type: interrupt. */ +#define EPX_CON_TYPE_INT 0xC +/* Number of packets per microframe. */ +#define EPX_CON_ISOD GENMASK(5, 4) +#define EPX_CON_ISOD_SHIFT 0x4 +/* Endpoint stall bit. */ +#define EPX_CON_STALL BIT(6) +/* Endpoint enable bit.*/ +#define EPX_CON_VAL BIT(7) + +/* rxcs/txcs - endpoint control and status bitmasks. */ +/* Data sequence error for the ISO endpoint. */ +#define EPX_CS_ERR(p) ((p) & BIT(0)) + +/** + * struct cdns2_epx_regs - endpoint 1..15 related registers. + * @reserved: reserved. + * @ep: none control endpoints array. + * @reserved2: reserved. + * @endprst: endpoint reset register. + * @reserved3: reserved. + * @isoautoarm: ISO auto-arm register. + * @reserved4: reserved. + * @isodctrl: ISO control register. + * @reserved5: reserved. + * @isoautodump: ISO auto dump enable register. + * @reserved6: reserved. + * @rxmaxpack: receive (OUT) Max packet size register. + * @reserved7: reserved. + * @rxstaddr: receive (OUT) start address endpoint buffer register. + * @reserved8: reserved. + * @txstaddr: transmit (IN) start address endpoint buffer register. + * @reserved9: reserved. + * @txmaxpack: transmit (IN) Max packet size register. + */ +struct cdns2_epx_regs { + __le32 reserved[2]; + struct cdns2_epx_base ep[15]; + __u8 reserved2[290]; + __u8 endprst; + __u8 reserved3[41]; + __le16 isoautoarm; + __u8 reserved4[10]; + __le16 isodctrl; + __le16 reserved5; + __le16 isoautodump; + __le32 reserved6; + __le16 rxmaxpack[15]; + __le32 reserved7[65]; + __le32 rxstaddr[15]; + __u8 reserved8[4]; + __le32 txstaddr[15]; + __u8 reserved9[98]; + __le16 txmaxpack[15]; +} __packed __aligned(4); + +/* ENDPRST - bitmasks. */ +/* Endpoint number. */ +#define ENDPRST_EP GENMASK(3, 0) +/* IN direction bit. */ +#define ENDPRST_IO_TX BIT(4) +/* Toggle reset bit. */ +#define ENDPRST_TOGRST BIT(5) +/* FIFO reset bit. */ +#define ENDPRST_FIFORST BIT(6) +/* Toggle status and reset bit. */ +#define ENDPRST_TOGSETQ BIT(7) + +/** + * struct cdns2_interrupt_regs - USB interrupt related registers. + * @reserved: reserved. + * @usbirq: USB interrupt request register. + * @extirq: external interrupt request register. + * @rxpngirq: external interrupt request register. + * @reserved1: reserved. + * @usbien: USB interrupt enable register. + * @extien: external interrupt enable register. + * @reserved2: reserved. + * @usbivect: USB interrupt vector register. + */ +struct cdns2_interrupt_regs { + __u8 reserved[396]; + __u8 usbirq; + __u8 extirq; + __le16 rxpngirq; + __le16 reserved1[4]; + __u8 usbien; + __u8 extien; + __le16 reserved2[3]; + __u8 usbivect; +} __packed __aligned(4); + +/* EXTIRQ and EXTIEN - bitmasks. */ +/* VBUS fault fall interrupt. */ +#define EXTIRQ_VBUSFAULT_FALL BIT(0) +/* VBUS fault fall interrupt. */ +#define EXTIRQ_VBUSFAULT_RISE BIT(1) +/* Wake up interrupt bit. */ +#define EXTIRQ_WAKEUP BIT(7) + +/* USBIEN and USBIRQ - bitmasks. */ +/* SETUP data valid interrupt bit.*/ +#define USBIRQ_SUDAV BIT(0) +/* Start-of-frame interrupt bit. */ +#define USBIRQ_SOF BIT(1) +/* SETUP token interrupt bit. */ +#define USBIRQ_SUTOK BIT(2) +/* USB suspend interrupt bit. */ +#define USBIRQ_SUSPEND BIT(3) +/* USB reset interrupt bit. */ +#define USBIRQ_URESET BIT(4) +/* USB high-speed mode interrupt bit. */ +#define USBIRQ_HSPEED BIT(5) +/* Link Power Management interrupt bit. */ +#define USBIRQ_LPM BIT(7) + +#define USB_IEN_INIT (USBIRQ_SUDAV | USBIRQ_SUSPEND | USBIRQ_URESET \ + | USBIRQ_HSPEED | USBIRQ_LPM) +/** + * struct cdns2_usb_regs - USB controller registers. + * @reserved: reserved. + * @lpmctrl: LPM control register. + * @lpmclock: LPM clock register. + * @reserved2: reserved. + * @endprst: endpoint reset register. + * @usbcs: USB control and status register. + * @frmnr: USB frame counter register. + * @fnaddr: function Address register. + * @clkgate: clock gate register. + * @fifoctrl: FIFO control register. + * @speedctrl: speed Control register. + * @sleep_clkgate: sleep Clock Gate register. + * @reserved3: reserved. + * @cpuctrl: microprocessor control register. + */ +struct cdns2_usb_regs { + __u8 reserved[4]; + __u16 lpmctrl; + __u8 lpmclock; + __u8 reserved2[411]; + __u8 endprst; + __u8 usbcs; + __le16 frmnr; + __u8 fnaddr; + __u8 clkgate; + __u8 fifoctrl; + __u8 speedctrl; + __u8 sleep_clkgate; + __u8 reserved3[533]; + __u8 cpuctrl; +} __packed __aligned(4); + +/* LPMCTRL - bitmasks. */ +/* BESL (Best Effort Service Latency). */ +#define LPMCTRLLL_HIRD GENMASK(7, 4) +/* Last received Remote Wakeup field from LPM Extended Token packet. */ +#define LPMCTRLLH_BREMOTEWAKEUP BIT(8) +/* Reflects value of the lpmnyet bit located in the usbcs[1] register. */ +#define LPMCTRLLH_LPMNYET BIT(16) + +/* LPMCLOCK - bitmasks. */ +/* + * If bit is 1 the controller automatically turns off clock + * (utmisleepm goes to low), else the microprocessor should use + * sleep clock gate register to turn off clock. + */ +#define LPMCLOCK_SLEEP_ENTRY BIT(7) + +/* USBCS - bitmasks. */ +/* Send NYET handshake for the LPM transaction. */ +#define USBCS_LPMNYET BIT(2) +/* Remote wake-up bit. */ +#define USBCS_SIGRSUME BIT(5) +/* Software disconnect bit. */ +#define USBCS_DISCON BIT(6) +/* Indicates that a wakeup pin resumed the controller. */ +#define USBCS_WAKESRC BIT(7) + +/* FIFOCTRL - bitmasks. */ +/* Endpoint number. */ +#define FIFOCTRL_EP GENMASK(3, 0) +/* Direction bit. */ +#define FIFOCTRL_IO_TX BIT(4) +/* FIFO auto bit. */ +#define FIFOCTRL_FIFOAUTO BIT(5) +/* FIFO commit bit. */ +#define FIFOCTRL_FIFOCMIT BIT(6) +/* FIFO access bit. */ +#define FIFOCTRL_FIFOACC BIT(7) + +/* SPEEDCTRL - bitmasks. */ +/* Device works in Full Speed. */ +#define SPEEDCTRL_FS BIT(1) +/* Device works in High Speed. */ +#define SPEEDCTRL_HS BIT(2) +/* Force FS mode. */ +#define SPEEDCTRL_HSDISABLE BIT(7) + +/* CPUCTRL- bitmasks. */ +/* Controller reset bit. */ +#define CPUCTRL_SW_RST BIT(1) + +/** + * struct cdns2_adma_regs - ADMA controller registers. + * @conf: DMA global configuration register. + * @sts: DMA global Status register. + * @reserved1: reserved. + * @ep_sel: DMA endpoint select register. + * @ep_traddr: DMA endpoint transfer ring address register. + * @ep_cfg: DMA endpoint configuration register. + * @ep_cmd: DMA endpoint command register. + * @ep_sts: DMA endpoint status register. + * @reserved2: reserved. + * @ep_sts_en: DMA endpoint status enable register. + * @drbl: DMA doorbell register. + * @ep_ien: DMA endpoint interrupt enable register. + * @ep_ists: DMA endpoint interrupt status register. + * @axim_ctrl: AXI Master Control register. + * @axim_id: AXI Master ID register. + * @reserved3: reserved. + * @axim_cap: AXI Master Wrapper Extended Capability. + * @reserved4: reserved. + * @axim_ctrl0: AXI Master Wrapper Extended Capability Control Register 0. + * @axim_ctrl1: AXI Master Wrapper Extended Capability Control Register 1. + */ +struct cdns2_adma_regs { + __le32 conf; + __le32 sts; + __le32 reserved1[5]; + __le32 ep_sel; + __le32 ep_traddr; + __le32 ep_cfg; + __le32 ep_cmd; + __le32 ep_sts; + __le32 reserved2; + __le32 ep_sts_en; + __le32 drbl; + __le32 ep_ien; + __le32 ep_ists; + __le32 axim_ctrl; + __le32 axim_id; + __le32 reserved3; + __le32 axim_cap; + __le32 reserved4; + __le32 axim_ctrl0; + __le32 axim_ctrl1; +}; + +#define CDNS2_ADMA_REGS_OFFSET 0x400 + +/* DMA_CONF - bitmasks. */ +/* Reset USB device configuration. */ +#define DMA_CONF_CFGRST BIT(0) +/* Singular DMA transfer mode.*/ +#define DMA_CONF_DSING BIT(8) +/* Multiple DMA transfers mode.*/ +#define DMA_CONF_DMULT BIT(9) + +/* DMA_EP_CFG - bitmasks. */ +/* Endpoint enable. */ +#define DMA_EP_CFG_ENABLE BIT(0) + +/* DMA_EP_CMD - bitmasks. */ +/* Endpoint reset. */ +#define DMA_EP_CMD_EPRST BIT(0) +/* Transfer descriptor ready. */ +#define DMA_EP_CMD_DRDY BIT(6) +/* Data flush. */ +#define DMA_EP_CMD_DFLUSH BIT(7) + +/* DMA_EP_STS - bitmasks. */ +/* Interrupt On Complete. */ +#define DMA_EP_STS_IOC BIT(2) +/* Interrupt on Short Packet. */ +#define DMA_EP_STS_ISP BIT(3) +/* Transfer descriptor missing. */ +#define DMA_EP_STS_DESCMIS BIT(4) +/* TRB error. */ +#define DMA_EP_STS_TRBERR BIT(7) +/* DMA busy bit. */ +#define DMA_EP_STS_DBUSY BIT(9) +/* Current Cycle Status. */ +#define DMA_EP_STS_CCS(p) ((p) & BIT(11)) +/* OUT size mismatch. */ +#define DMA_EP_STS_OUTSMM BIT(14) +/* ISO transmission error. */ +#define DMA_EP_STS_ISOERR BIT(15) + +/* DMA_EP_STS_EN - bitmasks. */ +/* OUT transfer missing descriptor enable. */ +#define DMA_EP_STS_EN_DESCMISEN BIT(4) +/* TRB enable. */ +#define DMA_EP_STS_EN_TRBERREN BIT(7) +/* OUT size mismatch enable. */ +#define DMA_EP_STS_EN_OUTSMMEN BIT(14) +/* ISO transmission error enable. */ +#define DMA_EP_STS_EN_ISOERREN BIT(15) + +/* DMA_EP_IEN - bitmasks. */ +#define DMA_EP_IEN(index) (1 << (index)) +#define DMA_EP_IEN_EP_OUT0 BIT(0) +#define DMA_EP_IEN_EP_IN0 BIT(16) + +/* DMA_EP_ISTS - bitmasks. */ +#define DMA_EP_ISTS(index) (1 << (index)) +#define DMA_EP_ISTS_EP_OUT0 BIT(0) +#define DMA_EP_ISTS_EP_IN0 BIT(16) + +#define gadget_to_cdns2_device(g) (container_of(g, struct cdns2_device, gadget)) +#define ep_to_cdns2_ep(ep) (container_of(ep, struct cdns2_endpoint, endpoint)) + +/*-------------------------------------------------------------------------*/ +#define TRBS_PER_SEGMENT 600 +#define ISO_MAX_INTERVAL 8 +#define MAX_TRB_LENGTH BIT(16) +#define MAX_ISO_SIZE 3076 +/* + * To improve performance the TRB buffer pointers can't cross + * 4KB boundaries. + */ +#define TRB_MAX_ISO_BUFF_SHIFT 12 +#define TRB_MAX_ISO_BUFF_SIZE BIT(TRB_MAX_ISO_BUFF_SHIFT) +/* How much data is left before the 4KB boundary? */ +#define TRB_BUFF_LEN_UP_TO_BOUNDARY(addr) (TRB_MAX_ISO_BUFF_SIZE - \ + ((addr) & (TRB_MAX_ISO_BUFF_SIZE - 1))) + +#if TRBS_PER_SEGMENT < 2 +#error "Incorrect TRBS_PER_SEGMENT. Minimal Transfer Ring size is 2." +#endif + +/** + * struct cdns2_trb - represent Transfer Descriptor block. + * @buffer: pointer to buffer data. + * @length: length of data. + * @control: control flags. + * + * This structure describes transfer block handled by DMA module. + */ +struct cdns2_trb { + __le32 buffer; + __le32 length; + __le32 control; +}; + +#define TRB_SIZE (sizeof(struct cdns2_trb)) +/* + * These two extra TRBs are reserved for isochronous transfer + * to inject 0 length packet and extra LINK TRB to synchronize the ISO transfer. + */ +#define TRB_ISO_RESERVED 2 +#define TR_SEG_SIZE (TRB_SIZE * (TRBS_PER_SEGMENT + TRB_ISO_RESERVED)) + +/* TRB bit mask. */ +#define TRB_TYPE_BITMASK GENMASK(15, 10) +#define TRB_TYPE(p) ((p) << 10) +#define TRB_FIELD_TO_TYPE(p) (((p) & TRB_TYPE_BITMASK) >> 10) + +/* TRB type IDs. */ +/* Used for Bulk, Interrupt, ISOC, and control data stage. */ +#define TRB_NORMAL 1 +/* TRB for linking ring segments. */ +#define TRB_LINK 6 + +/* Cycle bit - indicates TRB ownership by driver or hw. */ +#define TRB_CYCLE BIT(0) +/* + * When set to '1', the device will toggle its interpretation of the Cycle bit. + */ +#define TRB_TOGGLE BIT(1) +/* Interrupt on short packet. */ +#define TRB_ISP BIT(2) +/* Chain bit associate this TRB with next one TRB. */ +#define TRB_CHAIN BIT(4) +/* Interrupt on completion. */ +#define TRB_IOC BIT(5) + +/* Transfer_len bitmasks. */ +#define TRB_LEN(p) ((p) & GENMASK(16, 0)) +#define TRB_BURST(p) (((p) << 24) & GENMASK(31, 24)) +#define TRB_FIELD_TO_BURST(p) (((p) & GENMASK(31, 24)) >> 24) + +/* Data buffer pointer bitmasks. */ +#define TRB_BUFFER(p) ((p) & GENMASK(31, 0)) + +/*-------------------------------------------------------------------------*/ +/* Driver numeric constants. */ + +/* Maximum address that can be assigned to device. */ +#define USB_DEVICE_MAX_ADDRESS 127 + +/* One control and 15 IN and 15 OUT endpoints. */ +#define CDNS2_ENDPOINTS_NUM 31 + +#define CDNS2_EP_ZLP_BUF_SIZE 512 + +/*-------------------------------------------------------------------------*/ +/* Used structures. */ + +struct cdns2_device; + +/** + * struct cdns2_ring - transfer ring representation. + * @trbs: pointer to transfer ring. + * @dma: dma address of transfer ring. + * @free_trbs: number of free TRBs in transfer ring. + * @pcs: producer cycle state. + * @ccs: consumer cycle state. + * @enqueue: enqueue index in transfer ring. + * @dequeue: dequeue index in transfer ring. + */ +struct cdns2_ring { + struct cdns2_trb *trbs; + dma_addr_t dma; + int free_trbs; + u8 pcs; + u8 ccs; + int enqueue; + int dequeue; +}; + +/** + * struct cdns2_endpoint - extended device side representation of USB endpoint. + * @endpoint: usb endpoint. + * @pending_list: list of requests queuing on transfer ring. + * @deferred_list: list of requests waiting for queuing on transfer ring. + * @pdev: device associated with endpoint. + * @name: a human readable name e.g. ep1out. + * @ring: transfer ring associated with endpoint. + * @ep_state: state of endpoint. + * @idx: index of endpoint in pdev->eps table. + * @dir: endpoint direction. + * @num: endpoint number (1 - 15). + * @type: set to bmAttributes & USB_ENDPOINT_XFERTYPE_MASK. + * @interval: interval between packets used for ISOC and Interrupt endpoint. + * @buffering: on-chip buffers assigned to endpoint. + * @trb_burst_size: number of burst used in TRB. + * @skip: Sometimes the controller cannot process isochronous endpoint ring + * quickly enough and it will miss some isoc tds on the ring and + * generate ISO transmition error. + * Driver sets skip flag when receive a ISO transmition error and + * process the missed TDs on the endpoint ring. + * @wa1_set: use WA1. + * @wa1_trb: TRB assigned to WA1. + * @wa1_trb_index: TRB index for WA1. + * @wa1_cycle_bit: correct cycle bit for WA1. + */ +struct cdns2_endpoint { + struct usb_ep endpoint; + struct list_head pending_list; + struct list_head deferred_list; + + struct cdns2_device *pdev; + char name[20]; + + struct cdns2_ring ring; + +#define EP_ENABLED BIT(0) +#define EP_STALLED BIT(1) +#define EP_STALL_PENDING BIT(2) +#define EP_WEDGE BIT(3) +#define EP_CLAIMED BIT(4) +#define EP_RING_FULL BIT(5) +#define EP_DEFERRED_DRDY BIT(6) + + u32 ep_state; + + u8 idx; + u8 dir; + u8 num; + u8 type; + int interval; + u8 buffering; + u8 trb_burst_size; + bool skip; + + unsigned int wa1_set:1; + struct cdns2_trb *wa1_trb; + unsigned int wa1_trb_index; + unsigned int wa1_cycle_bit:1; +}; + +/** + * struct cdns2_request - extended device side representation of usb_request + * object. + * @request: generic usb_request object describing single I/O request. + * @pep: extended representation of usb_ep object. + * @trb: the first TRB association with this request. + * @start_trb: number of the first TRB in transfer ring. + * @end_trb: number of the last TRB in transfer ring. + * @list: used for queuing request in lists. + * @finished_trb: number of trb has already finished per request. + * @num_of_trb: how many trbs are associated with request. + */ +struct cdns2_request { + struct usb_request request; + struct cdns2_endpoint *pep; + struct cdns2_trb *trb; + int start_trb; + int end_trb; + struct list_head list; + int finished_trb; + int num_of_trb; +}; + +#define to_cdns2_request(r) (container_of(r, struct cdns2_request, request)) + +/* Stages used during enumeration process.*/ +#define CDNS2_SETUP_STAGE 0x0 +#define CDNS2_DATA_STAGE 0x1 +#define CDNS2_STATUS_STAGE 0x2 + +/** + * struct cdns2_device - represent USB device. + * @dev: pointer to device structure associated whit this controller. + * @gadget: device side representation of the peripheral controller. + * @gadget_driver: pointer to the gadget driver. + * @lock: for synchronizing. + * @irq: interrupt line number. + * @regs: base address for registers + * @usb_regs: base address for common USB registers. + * @ep0_regs: base address for endpoint 0 related registers. + * @epx_regs: base address for all none control endpoint registers. + * @interrupt_regs: base address for interrupt handling related registers. + * @adma_regs: base address for ADMA registers. + * @eps_dma_pool: endpoint Transfer Ring pool. + * @setup: used while processing usb control requests. + * @ep0_preq: private request used while handling EP0. + * @ep0_stage: ep0 stage during enumeration process. + * @zlp_buf: zlp buffer. + * @dev_address: device address assigned by host. + * @eps: array of objects describing endpoints. + * @selected_ep: actually selected endpoint. It's used only to improve + * performance by limiting access to dma_ep_sel register. + * @is_selfpowered: device is self powered. + * @may_wakeup: allows device to remote wakeup the host. + * @status_completion_no_call: indicate that driver is waiting for status + * stage completion. It's used in deferred SET_CONFIGURATION request. + * @in_lpm: indicate the controller is in low power mode. + * @pending_status_wq: workqueue handling status stage for deferred requests. + * @pending_status_request: request for which status stage was deferred. + * @eps_supported: endpoints supported by controller in form: + * bit: 0 - ep0, 1 - epOut1, 2 - epIn1, 3 - epOut2 ... + * @burst_opt: array with the best burst size value for different TRB size. + * @onchip_tx_buf: size of transmit on-chip buffer in KB. + * @onchip_rx_buf: size of receive on-chip buffer in KB. + */ +struct cdns2_device { + struct device *dev; + struct usb_gadget gadget; + struct usb_gadget_driver *gadget_driver; + + /* generic spin-lock for drivers */ + spinlock_t lock; + int irq; + void __iomem *regs; + struct cdns2_usb_regs __iomem *usb_regs; + struct cdns2_ep0_regs __iomem *ep0_regs; + struct cdns2_epx_regs __iomem *epx_regs; + struct cdns2_interrupt_regs __iomem *interrupt_regs; + struct cdns2_adma_regs __iomem *adma_regs; + struct dma_pool *eps_dma_pool; + struct usb_ctrlrequest setup; + struct cdns2_request ep0_preq; + u8 ep0_stage; + void *zlp_buf; + u8 dev_address; + struct cdns2_endpoint eps[CDNS2_ENDPOINTS_NUM]; + u32 selected_ep; + bool is_selfpowered; + bool may_wakeup; + bool status_completion_no_call; + bool in_lpm; + struct work_struct pending_status_wq; + struct usb_request *pending_status_request; + u32 eps_supported; + u8 burst_opt[MAX_ISO_SIZE + 1]; + + /*in KB */ + u16 onchip_tx_buf; + u16 onchip_rx_buf; +}; + +#define CDNS2_IF_EP_EXIST(pdev, ep_num, dir) \ + ((pdev)->eps_supported & \ + (BIT(ep_num) << ((dir) ? 0 : 16))) + +dma_addr_t cdns2_trb_virt_to_dma(struct cdns2_endpoint *pep, + struct cdns2_trb *trb); +void cdns2_pending_setup_status_handler(struct work_struct *work); +void cdns2_select_ep(struct cdns2_device *pdev, u32 ep); +struct cdns2_request *cdns2_next_preq(struct list_head *list); +struct usb_request *cdns2_gadget_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags); +void cdns2_gadget_ep_free_request(struct usb_ep *ep, + struct usb_request *request); +int cdns2_gadget_ep_dequeue(struct usb_ep *ep, struct usb_request *request); +void cdns2_gadget_giveback(struct cdns2_endpoint *pep, + struct cdns2_request *priv_req, + int status); +void cdns2_init_ep0(struct cdns2_device *pdev, struct cdns2_endpoint *pep); +void cdns2_ep0_config(struct cdns2_device *pdev); +void cdns2_handle_ep0_interrupt(struct cdns2_device *pdev, int dir); +void cdns2_handle_setup_packet(struct cdns2_device *pdev); +int cdns2_gadget_resume(struct cdns2_device *pdev, bool hibernated); +int cdns2_gadget_suspend(struct cdns2_device *pdev); +void cdns2_gadget_remove(struct cdns2_device *pdev); +int cdns2_gadget_init(struct cdns2_device *pdev); +void set_reg_bit_8(void __iomem *ptr, u8 mask); +int cdns2_halt_endpoint(struct cdns2_device *pdev, struct cdns2_endpoint *pep, + int value); + +#endif /* __LINUX_CDNS2_GADGET */ diff --git a/drivers/usb/gadget/udc/cdns2/cdns2-pci.c b/drivers/usb/gadget/udc/cdns2/cdns2-pci.c new file mode 100644 index 000000000000..1691541c9413 --- /dev/null +++ b/drivers/usb/gadget/udc/cdns2/cdns2-pci.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence USBHS-DEV controller - PCI Glue driver. + * + * Copyright (C) 2023 Cadence. + * + * Author: Pawel Laszczak <pawell@cadence.com> + * + */ + +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/pci.h> + +#include "cdns2-gadget.h" + +#define PCI_DRIVER_NAME "cdns-pci-usbhs" +#define CDNS_VENDOR_ID 0x17cd +#define CDNS_DEVICE_ID 0x0120 +#define PCI_BAR_DEV 0 +#define PCI_DEV_FN_DEVICE 0 + +static int cdns2_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + resource_size_t rsrc_start, rsrc_len; + struct device *dev = &pdev->dev; + struct cdns2_device *priv_dev; + struct resource *res; + int ret; + + /* For GADGET PCI (devfn) function number is 0. */ + if (!id || pdev->devfn != PCI_DEV_FN_DEVICE || + pdev->class != PCI_CLASS_SERIAL_USB_DEVICE) + return -EINVAL; + + ret = pcim_enable_device(pdev); + if (ret) { + dev_err(&pdev->dev, "Enabling PCI device has failed %d\n", ret); + return ret; + } + + pci_set_master(pdev); + + priv_dev = devm_kzalloc(&pdev->dev, sizeof(*priv_dev), GFP_KERNEL); + if (!priv_dev) + return -ENOMEM; + + dev_dbg(dev, "Initialize resources\n"); + rsrc_start = pci_resource_start(pdev, PCI_BAR_DEV); + rsrc_len = pci_resource_len(pdev, PCI_BAR_DEV); + + res = devm_request_mem_region(dev, rsrc_start, rsrc_len, "dev"); + if (!res) { + dev_dbg(dev, "controller already in use\n"); + return -EBUSY; + } + + priv_dev->regs = devm_ioremap(dev, rsrc_start, rsrc_len); + if (!priv_dev->regs) { + dev_dbg(dev, "error mapping memory\n"); + return -EFAULT; + } + + priv_dev->irq = pdev->irq; + dev_dbg(dev, "USBSS-DEV physical base addr: %pa\n", + &rsrc_start); + + priv_dev->dev = dev; + + priv_dev->eps_supported = 0x000f000f; + priv_dev->onchip_tx_buf = 16; + priv_dev->onchip_rx_buf = 16; + + ret = cdns2_gadget_init(priv_dev); + if (ret) + return ret; + + pci_set_drvdata(pdev, priv_dev); + + device_wakeup_enable(&pdev->dev); + if (pci_dev_run_wake(pdev)) + pm_runtime_put_noidle(&pdev->dev); + + return 0; +} + +static void cdns2_pci_remove(struct pci_dev *pdev) +{ + struct cdns2_device *priv_dev = pci_get_drvdata(pdev); + + if (pci_dev_run_wake(pdev)) + pm_runtime_get_noresume(&pdev->dev); + + cdns2_gadget_remove(priv_dev); +} + +static int cdns2_pci_suspend(struct device *dev) +{ + struct cdns2_device *priv_dev = dev_get_drvdata(dev); + + return cdns2_gadget_suspend(priv_dev); +} + +static int cdns2_pci_resume(struct device *dev) +{ + struct cdns2_device *priv_dev = dev_get_drvdata(dev); + + return cdns2_gadget_resume(priv_dev, 1); +} + +static const struct dev_pm_ops cdns2_pci_pm_ops = { + SYSTEM_SLEEP_PM_OPS(cdns2_pci_suspend, cdns2_pci_resume) +}; + +static const struct pci_device_id cdns2_pci_ids[] = { + { PCI_VENDOR_ID_CDNS, CDNS_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, + PCI_CLASS_SERIAL_USB_DEVICE, PCI_ANY_ID }, + { 0, } +}; + +static struct pci_driver cdns2_pci_driver = { + .name = "cdns2-pci", + .id_table = &cdns2_pci_ids[0], + .probe = cdns2_pci_probe, + .remove = cdns2_pci_remove, + .driver = { + .pm = pm_ptr(&cdns2_pci_pm_ops), + } +}; + +module_pci_driver(cdns2_pci_driver); +MODULE_DEVICE_TABLE(pci, cdns2_pci_ids); + +MODULE_ALIAS("pci:cdns2"); +MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Cadence CDNS2 PCI driver"); diff --git a/drivers/usb/gadget/udc/cdns2/cdns2-trace.c b/drivers/usb/gadget/udc/cdns2/cdns2-trace.c new file mode 100644 index 000000000000..de6b8cc3d071 --- /dev/null +++ b/drivers/usb/gadget/udc/cdns2/cdns2-trace.c @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USBHS device controller driver Trace Support + * + * Copyright (C) 2023 Cadence. + * + * Author: Pawel Laszczak <pawell@cadence.com> + */ + +#define CREATE_TRACE_POINTS +#include "cdns2-trace.h" diff --git a/drivers/usb/gadget/udc/cdns2/cdns2-trace.h b/drivers/usb/gadget/udc/cdns2/cdns2-trace.h new file mode 100644 index 000000000000..61f241634ea5 --- /dev/null +++ b/drivers/usb/gadget/udc/cdns2/cdns2-trace.h @@ -0,0 +1,605 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * USBHS-DEV device controller driver. + * Trace support header file. + * + * Copyright (C) 2023 Cadence. + * + * Author: Pawel Laszczak <pawell@cadence.com> + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM cdns2-dev + +/* + * The TRACE_SYSTEM_VAR defaults to TRACE_SYSTEM, but must be a + * legitimate C variable. It is not exported to user space. + */ +#undef TRACE_SYSTEM_VAR +#define TRACE_SYSTEM_VAR cdns2_dev + +#if !defined(__LINUX_CDNS2_TRACE) || defined(TRACE_HEADER_MULTI_READ) +#define __LINUX_CDNS2_TRACE + +#include <linux/types.h> +#include <linux/tracepoint.h> +#include <asm/byteorder.h> +#include <linux/usb/ch9.h> +#include "cdns2-gadget.h" +#include "cdns2-debug.h" + +#define CDNS2_MSG_MAX 500 + +DECLARE_EVENT_CLASS(cdns2_log_enable_disable, + TP_PROTO(int set), + TP_ARGS(set), + TP_STRUCT__entry( + __field(int, set) + ), + TP_fast_assign( + __entry->set = set; + ), + TP_printk("%s", __entry->set ? "enabled" : "disabled") +); + +DEFINE_EVENT(cdns2_log_enable_disable, cdns2_pullup, + TP_PROTO(int set), + TP_ARGS(set) +); + +DEFINE_EVENT(cdns2_log_enable_disable, cdns2_lpm, + TP_PROTO(int set), + TP_ARGS(set) +); + +DEFINE_EVENT(cdns2_log_enable_disable, cdns2_may_wakeup, + TP_PROTO(int set), + TP_ARGS(set) +); + +DECLARE_EVENT_CLASS(cdns2_log_simple, + TP_PROTO(char *msg), + TP_ARGS(msg), + TP_STRUCT__entry( + __string(text, msg) + ), + TP_fast_assign( + __assign_str(text, msg); + ), + TP_printk("%s", __get_str(text)) +); + +DEFINE_EVENT(cdns2_log_simple, cdns2_no_room_on_ring, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdns2_log_simple, cdns2_ep0_status_stage, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdns2_log_simple, cdns2_ep0_set_config, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdns2_log_simple, cdns2_ep0_setup, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdns2_log_simple, cdns2_device_state, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +TRACE_EVENT(cdns2_ep_halt, + TP_PROTO(struct cdns2_endpoint *ep_priv, u8 halt, u8 flush), + TP_ARGS(ep_priv, halt, flush), + TP_STRUCT__entry( + __string(name, ep_priv->name) + __field(u8, halt) + __field(u8, flush) + ), + TP_fast_assign( + __assign_str(name, ep_priv->name); + __entry->halt = halt; + __entry->flush = flush; + ), + TP_printk("Halt %s for %s: %s", __entry->flush ? " and flush" : "", + __get_str(name), __entry->halt ? "set" : "cleared") +); + +TRACE_EVENT(cdns2_wa1, + TP_PROTO(struct cdns2_endpoint *ep_priv, char *msg), + TP_ARGS(ep_priv, msg), + TP_STRUCT__entry( + __string(ep_name, ep_priv->name) + __string(msg, msg) + ), + TP_fast_assign( + __assign_str(ep_name, ep_priv->name); + __assign_str(msg, msg); + ), + TP_printk("WA1: %s %s", __get_str(ep_name), __get_str(msg)) +); + +DECLARE_EVENT_CLASS(cdns2_log_doorbell, + TP_PROTO(struct cdns2_endpoint *pep, u32 ep_trbaddr), + TP_ARGS(pep, ep_trbaddr), + TP_STRUCT__entry( + __string(name, pep->num ? pep->name : + (pep->dir ? "ep0in" : "ep0out")) + __field(u32, ep_trbaddr) + ), + TP_fast_assign( + __assign_str(name, pep->name); + __entry->ep_trbaddr = ep_trbaddr; + ), + TP_printk("%s, ep_trbaddr %08x", __get_str(name), + __entry->ep_trbaddr) +); + +DEFINE_EVENT(cdns2_log_doorbell, cdns2_doorbell_ep0, + TP_PROTO(struct cdns2_endpoint *pep, u32 ep_trbaddr), + TP_ARGS(pep, ep_trbaddr) +); + +DEFINE_EVENT(cdns2_log_doorbell, cdns2_doorbell_epx, + TP_PROTO(struct cdns2_endpoint *pep, u32 ep_trbaddr), + TP_ARGS(pep, ep_trbaddr) +); + +DECLARE_EVENT_CLASS(cdns2_log_usb_irq, + TP_PROTO(u32 usb_irq, u32 ext_irq), + TP_ARGS(usb_irq, ext_irq), + TP_STRUCT__entry( + __field(u32, usb_irq) + __field(u32, ext_irq) + ), + TP_fast_assign( + __entry->usb_irq = usb_irq; + __entry->ext_irq = ext_irq; + ), + TP_printk("%s", cdns2_decode_usb_irq(__get_buf(CDNS2_MSG_MAX), + CDNS2_MSG_MAX, + __entry->usb_irq, + __entry->ext_irq)) +); + +DEFINE_EVENT(cdns2_log_usb_irq, cdns2_usb_irq, + TP_PROTO(u32 usb_irq, u32 ext_irq), + TP_ARGS(usb_irq, ext_irq) +); + +TRACE_EVENT(cdns2_dma_ep_ists, + TP_PROTO(u32 dma_ep_ists), + TP_ARGS(dma_ep_ists), + TP_STRUCT__entry( + __field(u32, dma_ep_ists) + ), + TP_fast_assign( + __entry->dma_ep_ists = dma_ep_ists; + ), + TP_printk("OUT: 0x%04x, IN: 0x%04x", (u16)__entry->dma_ep_ists, + __entry->dma_ep_ists >> 16) +); + +DECLARE_EVENT_CLASS(cdns2_log_epx_irq, + TP_PROTO(struct cdns2_device *pdev, struct cdns2_endpoint *pep), + TP_ARGS(pdev, pep), + TP_STRUCT__entry( + __string(ep_name, pep->name) + __field(u32, ep_sts) + __field(u32, ep_ists) + __field(u32, ep_traddr) + ), + TP_fast_assign( + __assign_str(ep_name, pep->name); + __entry->ep_sts = readl(&pdev->adma_regs->ep_sts); + __entry->ep_ists = readl(&pdev->adma_regs->ep_ists); + __entry->ep_traddr = readl(&pdev->adma_regs->ep_traddr); + ), + TP_printk("%s, ep_traddr: %08x", + cdns2_decode_epx_irq(__get_buf(CDNS2_MSG_MAX), CDNS2_MSG_MAX, + __get_str(ep_name), + __entry->ep_ists, __entry->ep_sts), + __entry->ep_traddr) +); + +DEFINE_EVENT(cdns2_log_epx_irq, cdns2_epx_irq, + TP_PROTO(struct cdns2_device *pdev, struct cdns2_endpoint *pep), + TP_ARGS(pdev, pep) +); + +DECLARE_EVENT_CLASS(cdns2_log_ep0_irq, + TP_PROTO(struct cdns2_device *pdev), + TP_ARGS(pdev), + TP_STRUCT__entry( + __field(int, ep_dir) + __field(u32, ep_ists) + __field(u32, ep_sts) + ), + TP_fast_assign( + __entry->ep_dir = pdev->selected_ep; + __entry->ep_ists = readl(&pdev->adma_regs->ep_ists); + __entry->ep_sts = readl(&pdev->adma_regs->ep_sts); + ), + TP_printk("%s", cdns2_decode_ep0_irq(__get_buf(CDNS2_MSG_MAX), + CDNS2_MSG_MAX, + __entry->ep_ists, __entry->ep_sts, + __entry->ep_dir)) +); + +DEFINE_EVENT(cdns2_log_ep0_irq, cdns2_ep0_irq, + TP_PROTO(struct cdns2_device *pdev), + TP_ARGS(pdev) +); + +DECLARE_EVENT_CLASS(cdns2_log_ctrl, + TP_PROTO(struct usb_ctrlrequest *ctrl), + TP_ARGS(ctrl), + TP_STRUCT__entry( + __field(u8, bRequestType) + __field(u8, bRequest) + __field(u16, wValue) + __field(u16, wIndex) + __field(u16, wLength) + ), + TP_fast_assign( + __entry->bRequestType = ctrl->bRequestType; + __entry->bRequest = ctrl->bRequest; + __entry->wValue = le16_to_cpu(ctrl->wValue); + __entry->wIndex = le16_to_cpu(ctrl->wIndex); + __entry->wLength = le16_to_cpu(ctrl->wLength); + ), + TP_printk("%s", usb_decode_ctrl(__get_buf(CDNS2_MSG_MAX), CDNS2_MSG_MAX, + __entry->bRequestType, + __entry->bRequest, __entry->wValue, + __entry->wIndex, __entry->wLength) + ) +); + +DEFINE_EVENT(cdns2_log_ctrl, cdns2_ctrl_req, + TP_PROTO(struct usb_ctrlrequest *ctrl), + TP_ARGS(ctrl) +); + +DECLARE_EVENT_CLASS(cdns2_log_request, + TP_PROTO(struct cdns2_request *preq), + TP_ARGS(preq), + TP_STRUCT__entry( + __string(name, preq->pep->name) + __field(struct usb_request *, request) + __field(struct cdns2_request *, preq) + __field(void *, buf) + __field(unsigned int, actual) + __field(unsigned int, length) + __field(int, status) + __field(dma_addr_t, dma) + __field(int, zero) + __field(int, short_not_ok) + __field(int, no_interrupt) + __field(struct scatterlist*, sg) + __field(unsigned int, num_sgs) + __field(unsigned int, num_mapped_sgs) + __field(int, start_trb) + __field(int, end_trb) + ), + TP_fast_assign( + __assign_str(name, preq->pep->name); + __entry->request = &preq->request; + __entry->preq = preq; + __entry->buf = preq->request.buf; + __entry->actual = preq->request.actual; + __entry->length = preq->request.length; + __entry->status = preq->request.status; + __entry->dma = preq->request.dma; + __entry->zero = preq->request.zero; + __entry->short_not_ok = preq->request.short_not_ok; + __entry->no_interrupt = preq->request.no_interrupt; + __entry->sg = preq->request.sg; + __entry->num_sgs = preq->request.num_sgs; + __entry->num_mapped_sgs = preq->request.num_mapped_sgs; + __entry->start_trb = preq->start_trb; + __entry->end_trb = preq->end_trb; + ), + TP_printk("%s: req: %p, preq: %p, req buf: %p, length: %u/%u, status: %d," + "buf dma: (%pad), %s%s%s, sg: %p, num_sgs: %d, num_m_sgs: %d," + "trb: [start: %d, end: %d]", + __get_str(name), __entry->request, __entry->preq, + __entry->buf, __entry->actual, __entry->length, + __entry->status, &__entry->dma, + __entry->zero ? "Z" : "z", + __entry->short_not_ok ? "S" : "s", + __entry->no_interrupt ? "I" : "i", + __entry->sg, __entry->num_sgs, __entry->num_mapped_sgs, + __entry->start_trb, + __entry->end_trb + ) +); + +DEFINE_EVENT(cdns2_log_request, cdns2_request_enqueue, + TP_PROTO(struct cdns2_request *preq), + TP_ARGS(preq) +); + +DEFINE_EVENT(cdns2_log_request, cdns2_request_enqueue_error, + TP_PROTO(struct cdns2_request *preq), + TP_ARGS(preq) +); + +DEFINE_EVENT(cdns2_log_request, cdns2_alloc_request, + TP_PROTO(struct cdns2_request *preq), + TP_ARGS(preq) +); + +DEFINE_EVENT(cdns2_log_request, cdns2_free_request, + TP_PROTO(struct cdns2_request *preq), + TP_ARGS(preq) +); + +DEFINE_EVENT(cdns2_log_request, cdns2_ep_queue, + TP_PROTO(struct cdns2_request *preq), + TP_ARGS(preq) +); + +DEFINE_EVENT(cdns2_log_request, cdns2_request_dequeue, + TP_PROTO(struct cdns2_request *preq), + TP_ARGS(preq) +); + +DEFINE_EVENT(cdns2_log_request, cdns2_request_giveback, + TP_PROTO(struct cdns2_request *preq), + TP_ARGS(preq) +); + +TRACE_EVENT(cdns2_ep0_enqueue, + TP_PROTO(struct cdns2_device *dev_priv, struct usb_request *request), + TP_ARGS(dev_priv, request), + TP_STRUCT__entry( + __field(int, dir) + __field(int, length) + ), + TP_fast_assign( + __entry->dir = dev_priv->eps[0].dir; + __entry->length = request->length; + ), + TP_printk("Queue to ep0%s length: %u", __entry->dir ? "in" : "out", + __entry->length) +); + +DECLARE_EVENT_CLASS(cdns2_log_map_request, + TP_PROTO(struct cdns2_request *priv_req), + TP_ARGS(priv_req), + TP_STRUCT__entry( + __string(name, priv_req->pep->name) + __field(struct usb_request *, req) + __field(void *, buf) + __field(dma_addr_t, dma) + ), + TP_fast_assign( + __assign_str(name, priv_req->pep->name); + __entry->req = &priv_req->request; + __entry->buf = priv_req->request.buf; + __entry->dma = priv_req->request.dma; + ), + TP_printk("%s: req: %p, req buf %p, dma %p", + __get_str(name), __entry->req, __entry->buf, &__entry->dma + ) +); + +DEFINE_EVENT(cdns2_log_map_request, cdns2_map_request, + TP_PROTO(struct cdns2_request *req), + TP_ARGS(req) +); +DEFINE_EVENT(cdns2_log_map_request, cdns2_mapped_request, + TP_PROTO(struct cdns2_request *req), + TP_ARGS(req) +); + +DECLARE_EVENT_CLASS(cdns2_log_trb, + TP_PROTO(struct cdns2_endpoint *pep, struct cdns2_trb *trb), + TP_ARGS(pep, trb), + TP_STRUCT__entry( + __string(name, pep->name) + __field(struct cdns2_trb *, trb) + __field(u32, buffer) + __field(u32, length) + __field(u32, control) + __field(u32, type) + ), + TP_fast_assign( + __assign_str(name, pep->name); + __entry->trb = trb; + __entry->buffer = le32_to_cpu(trb->buffer); + __entry->length = le32_to_cpu(trb->length); + __entry->control = le32_to_cpu(trb->control); + __entry->type = usb_endpoint_type(pep->endpoint.desc); + ), + TP_printk("%s: trb V: %p, dma buf: P: 0x%08x, %s", + __get_str(name), __entry->trb, __entry->buffer, + cdns2_decode_trb(__get_buf(CDNS2_MSG_MAX), CDNS2_MSG_MAX, + __entry->control, __entry->length, + __entry->buffer)) +); + +DEFINE_EVENT(cdns2_log_trb, cdns2_queue_trb, + TP_PROTO(struct cdns2_endpoint *pep, struct cdns2_trb *trb), + TP_ARGS(pep, trb) +); + +DEFINE_EVENT(cdns2_log_trb, cdns2_complete_trb, + TP_PROTO(struct cdns2_endpoint *pep, struct cdns2_trb *trb), + TP_ARGS(pep, trb) +); + +DECLARE_EVENT_CLASS(cdns2_log_ring, + TP_PROTO(struct cdns2_endpoint *pep), + TP_ARGS(pep), + TP_STRUCT__entry( + __dynamic_array(u8, tr_seg, TR_SEG_SIZE) + __dynamic_array(u8, pep, sizeof(struct cdns2_endpoint)) + __dynamic_array(char, buffer, + (TRBS_PER_SEGMENT * 65) + CDNS2_MSG_MAX) + ), + TP_fast_assign( + memcpy(__get_dynamic_array(pep), pep, + sizeof(struct cdns2_endpoint)); + memcpy(__get_dynamic_array(tr_seg), pep->ring.trbs, + TR_SEG_SIZE); + ), + + TP_printk("%s", + cdns2_raw_ring((struct cdns2_endpoint *)__get_str(pep), + (struct cdns2_trb *)__get_str(tr_seg), + __get_str(buffer), + (TRBS_PER_SEGMENT * 65) + CDNS2_MSG_MAX)) +); + +DEFINE_EVENT(cdns2_log_ring, cdns2_ring, + TP_PROTO(struct cdns2_endpoint *pep), + TP_ARGS(pep) +); + +DECLARE_EVENT_CLASS(cdns2_log_ep, + TP_PROTO(struct cdns2_endpoint *pep), + TP_ARGS(pep), + TP_STRUCT__entry( + __string(name, pep->name) + __field(unsigned int, maxpacket) + __field(unsigned int, maxpacket_limit) + __field(unsigned int, flags) + __field(unsigned int, dir) + __field(u8, enqueue) + __field(u8, dequeue) + ), + TP_fast_assign( + __assign_str(name, pep->name); + __entry->maxpacket = pep->endpoint.maxpacket; + __entry->maxpacket_limit = pep->endpoint.maxpacket_limit; + __entry->flags = pep->ep_state; + __entry->dir = pep->dir; + __entry->enqueue = pep->ring.enqueue; + __entry->dequeue = pep->ring.dequeue; + ), + TP_printk("%s: mps: %d/%d, enq idx: %d, deq idx: %d, " + "flags: %s%s%s%s, dir: %s", + __get_str(name), __entry->maxpacket, + __entry->maxpacket_limit, __entry->enqueue, + __entry->dequeue, + __entry->flags & EP_ENABLED ? "EN | " : "", + __entry->flags & EP_STALLED ? "STALLED | " : "", + __entry->flags & EP_WEDGE ? "WEDGE | " : "", + __entry->flags & EP_RING_FULL ? "RING FULL |" : "", + __entry->dir ? "IN" : "OUT" + ) +); + +DEFINE_EVENT(cdns2_log_ep, cdns2_gadget_ep_enable, + TP_PROTO(struct cdns2_endpoint *pep), + TP_ARGS(pep) +); + +DEFINE_EVENT(cdns2_log_ep, cdns2_gadget_ep_disable, + TP_PROTO(struct cdns2_endpoint *pep), + TP_ARGS(pep) +); + +DEFINE_EVENT(cdns2_log_ep, cdns2_iso_out_ep_disable, + TP_PROTO(struct cdns2_endpoint *pep), + TP_ARGS(pep) +); + +DEFINE_EVENT(cdns2_log_ep, cdns2_ep_busy_try_halt_again, + TP_PROTO(struct cdns2_endpoint *pep), + TP_ARGS(pep) +); + +DECLARE_EVENT_CLASS(cdns2_log_request_handled, + TP_PROTO(struct cdns2_request *priv_req, int current_index, + int handled), + TP_ARGS(priv_req, current_index, handled), + TP_STRUCT__entry( + __field(struct cdns2_request *, priv_req) + __field(unsigned int, dma_position) + __field(unsigned int, handled) + __field(unsigned int, dequeue_idx) + __field(unsigned int, enqueue_idx) + __field(unsigned int, start_trb) + __field(unsigned int, end_trb) + ), + TP_fast_assign( + __entry->priv_req = priv_req; + __entry->dma_position = current_index; + __entry->handled = handled; + __entry->dequeue_idx = priv_req->pep->ring.dequeue; + __entry->enqueue_idx = priv_req->pep->ring.enqueue; + __entry->start_trb = priv_req->start_trb; + __entry->end_trb = priv_req->end_trb; + ), + TP_printk("Req: %p %s, DMA pos: %d, ep deq: %d, ep enq: %d," + " start trb: %d, end trb: %d", + __entry->priv_req, + __entry->handled ? "handled" : "not handled", + __entry->dma_position, __entry->dequeue_idx, + __entry->enqueue_idx, __entry->start_trb, + __entry->end_trb + ) +); + +DEFINE_EVENT(cdns2_log_request_handled, cdns2_request_handled, + TP_PROTO(struct cdns2_request *priv_req, int current_index, + int handled), + TP_ARGS(priv_req, current_index, handled) +); + +DECLARE_EVENT_CLASS(cdns2_log_epx_reg_config, + TP_PROTO(struct cdns2_device *pdev, struct cdns2_endpoint *pep), + TP_ARGS(pdev, pep), + TP_STRUCT__entry( + __string(ep_name, pep->name) + __field(u8, burst_size) + __field(__le16, maxpack_reg) + __field(__u8, con_reg) + __field(u32, ep_sel_reg) + __field(u32, ep_sts_en_reg) + __field(u32, ep_cfg_reg) + ), + TP_fast_assign( + __assign_str(ep_name, pep->name); + __entry->burst_size = pep->trb_burst_size; + __entry->maxpack_reg = pep->dir ? readw(&pdev->epx_regs->txmaxpack[pep->num - 1]) : + readw(&pdev->epx_regs->rxmaxpack[pep->num - 1]); + __entry->con_reg = pep->dir ? readb(&pdev->epx_regs->ep[pep->num - 1].txcon) : + readb(&pdev->epx_regs->ep[pep->num - 1].rxcon); + __entry->ep_sel_reg = readl(&pdev->adma_regs->ep_sel); + __entry->ep_sts_en_reg = readl(&pdev->adma_regs->ep_sts_en); + __entry->ep_cfg_reg = readl(&pdev->adma_regs->ep_cfg); + ), + + TP_printk("%s, maxpack: %d, con: %02x, dma_ep_sel: %08x, dma_ep_sts_en: %08x" + " dma_ep_cfg %08x", + __get_str(ep_name), __entry->maxpack_reg, __entry->con_reg, + __entry->ep_sel_reg, __entry->ep_sts_en_reg, + __entry->ep_cfg_reg + ) +); + +DEFINE_EVENT(cdns2_log_epx_reg_config, cdns2_epx_hw_cfg, + TP_PROTO(struct cdns2_device *pdev, struct cdns2_endpoint *pep), + TP_ARGS(pdev, pep) +); + +#endif /* __LINUX_CDNS2_TRACE */ + +/* This part must be outside header guard. */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE cdns2-trace + +#include <trace/define_trace.h> diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index 83fd1de14784..59188ea065e0 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -61,7 +61,7 @@ struct usb_udc { struct mutex connect_lock; }; -static struct class *udc_class; +static const struct class udc_class; static LIST_HEAD(udc_list); /* Protects udc_list, udc->driver, driver->is_bound, and related calls */ @@ -1378,7 +1378,7 @@ int usb_add_gadget(struct usb_gadget *gadget) device_initialize(&udc->dev); udc->dev.release = usb_udc_release; - udc->dev.class = udc_class; + udc->dev.class = &udc_class; udc->dev.groups = usb_udc_attr_groups; udc->dev.parent = gadget->dev.parent; ret = dev_set_name(&udc->dev, "%s", @@ -1856,6 +1856,11 @@ static int usb_udc_uevent(const struct device *dev, struct kobj_uevent_env *env) return 0; } +static const struct class udc_class = { + .name = "udc", + .dev_uevent = usb_udc_uevent, +}; + static const struct bus_type gadget_bus_type = { .name = "gadget", .probe = gadget_bind_driver, @@ -1867,18 +1872,13 @@ static int __init usb_udc_init(void) { int rc; - udc_class = class_create("udc"); - if (IS_ERR(udc_class)) { - pr_err("failed to create udc class --> %ld\n", - PTR_ERR(udc_class)); - return PTR_ERR(udc_class); - } - - udc_class->dev_uevent = usb_udc_uevent; + rc = class_register(&udc_class); + if (rc) + return rc; rc = bus_register(&gadget_bus_type); if (rc) - class_destroy(udc_class); + class_unregister(&udc_class); return rc; } subsys_initcall(usb_udc_init); @@ -1886,7 +1886,7 @@ subsys_initcall(usb_udc_init); static void __exit usb_udc_exit(void) { bus_unregister(&gadget_bus_type); - class_destroy(udc_class); + class_unregister(&udc_class); } module_exit(usb_udc_exit); diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index 899ac9f9c279..0953e1b5c030 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -1108,13 +1108,12 @@ err_udc: return rc; } -static int dummy_udc_remove(struct platform_device *pdev) +static void dummy_udc_remove(struct platform_device *pdev) { struct dummy *dum = platform_get_drvdata(pdev); device_remove_file(&dum->gadget.dev, &dev_attr_function); usb_del_gadget_udc(&dum->gadget); - return 0; } static void dummy_udc_pm(struct dummy *dum, struct dummy_hcd *dum_hcd, @@ -1150,7 +1149,7 @@ static int dummy_udc_resume(struct platform_device *pdev) static struct platform_driver dummy_udc_driver = { .probe = dummy_udc_probe, - .remove = dummy_udc_remove, + .remove_new = dummy_udc_remove, .suspend = dummy_udc_suspend, .resume = dummy_udc_resume, .driver = { @@ -2701,7 +2700,7 @@ put_usb2_hcd: return retval; } -static int dummy_hcd_remove(struct platform_device *pdev) +static void dummy_hcd_remove(struct platform_device *pdev) { struct dummy *dum; @@ -2717,8 +2716,6 @@ static int dummy_hcd_remove(struct platform_device *pdev) dum->hs_hcd = NULL; dum->ss_hcd = NULL; - - return 0; } static int dummy_hcd_suspend(struct platform_device *pdev, pm_message_t state) @@ -2753,7 +2750,7 @@ static int dummy_hcd_resume(struct platform_device *pdev) static struct platform_driver dummy_hcd_driver = { .probe = dummy_hcd_probe, - .remove = dummy_hcd_remove, + .remove_new = dummy_hcd_remove, .suspend = dummy_hcd_suspend, .resume = dummy_hcd_resume, .driver = { diff --git a/drivers/usb/gadget/udc/fsl_qe_udc.c b/drivers/usb/gadget/udc/fsl_qe_udc.c index 3b1cc8fa30c8..9c5dc1c1a68e 100644 --- a/drivers/usb/gadget/udc/fsl_qe_udc.c +++ b/drivers/usb/gadget/udc/fsl_qe_udc.c @@ -2628,7 +2628,7 @@ static int qe_udc_resume(struct platform_device *dev) } #endif -static int qe_udc_remove(struct platform_device *ofdev) +static void qe_udc_remove(struct platform_device *ofdev) { struct qe_udc *udc = platform_get_drvdata(ofdev); struct qe_ep *ep; @@ -2679,8 +2679,6 @@ static int qe_udc_remove(struct platform_device *ofdev) /* wait for release() of gadget.dev to free udc */ wait_for_completion(&done); - - return 0; } /*-------------------------------------------------------------------------*/ @@ -2708,7 +2706,7 @@ static struct platform_driver udc_driver = { .of_match_table = qe_udc_match, }, .probe = qe_udc_probe, - .remove = qe_udc_remove, + .remove_new = qe_udc_remove, #ifdef CONFIG_PM .suspend = qe_udc_suspend, .resume = qe_udc_resume, diff --git a/drivers/usb/gadget/udc/fusb300_udc.c b/drivers/usb/gadget/udc/fusb300_udc.c index 08ba9c8c1e67..bd03d475f927 100644 --- a/drivers/usb/gadget/udc/fusb300_udc.c +++ b/drivers/usb/gadget/udc/fusb300_udc.c @@ -1338,7 +1338,7 @@ static const struct usb_gadget_ops fusb300_gadget_ops = { .udc_stop = fusb300_udc_stop, }; -static int fusb300_remove(struct platform_device *pdev) +static void fusb300_remove(struct platform_device *pdev) { struct fusb300 *fusb300 = platform_get_drvdata(pdev); int i; @@ -1352,8 +1352,6 @@ static int fusb300_remove(struct platform_device *pdev) for (i = 0; i < FUSB300_MAX_NUM_EP; i++) kfree(fusb300->ep[i]); kfree(fusb300); - - return 0; } static int fusb300_probe(struct platform_device *pdev) @@ -1508,7 +1506,7 @@ clean_up: } static struct platform_driver fusb300_driver = { - .remove = fusb300_remove, + .remove_new = fusb300_remove, .driver = { .name = udc_name, }, diff --git a/drivers/usb/gadget/udc/m66592-udc.c b/drivers/usb/gadget/udc/m66592-udc.c index 06e21cee431b..e05f45a4b56b 100644 --- a/drivers/usb/gadget/udc/m66592-udc.c +++ b/drivers/usb/gadget/udc/m66592-udc.c @@ -1512,7 +1512,7 @@ static const struct usb_gadget_ops m66592_gadget_ops = { .pullup = m66592_pullup, }; -static int m66592_remove(struct platform_device *pdev) +static void m66592_remove(struct platform_device *pdev) { struct m66592 *m66592 = platform_get_drvdata(pdev); @@ -1527,7 +1527,6 @@ static int m66592_remove(struct platform_device *pdev) clk_put(m66592->clk); } kfree(m66592); - return 0; } static void nop_completion(struct usb_ep *ep, struct usb_request *r) @@ -1688,7 +1687,7 @@ clean_up: /*-------------------------------------------------------------------------*/ static struct platform_driver m66592_driver = { - .remove = m66592_remove, + .remove_new = m66592_remove, .driver = { .name = udc_name, }, diff --git a/drivers/usb/gadget/udc/mv_u3d_core.c b/drivers/usb/gadget/udc/mv_u3d_core.c index 411b6179782c..3473048a85f5 100644 --- a/drivers/usb/gadget/udc/mv_u3d_core.c +++ b/drivers/usb/gadget/udc/mv_u3d_core.c @@ -1746,7 +1746,7 @@ static irqreturn_t mv_u3d_irq(int irq, void *dev) return IRQ_HANDLED; } -static int mv_u3d_remove(struct platform_device *dev) +static void mv_u3d_remove(struct platform_device *dev) { struct mv_u3d *u3d = platform_get_drvdata(dev); @@ -1775,8 +1775,6 @@ static int mv_u3d_remove(struct platform_device *dev) clk_put(u3d->clk); kfree(u3d); - - return 0; } static int mv_u3d_probe(struct platform_device *dev) @@ -2049,7 +2047,7 @@ static void mv_u3d_shutdown(struct platform_device *dev) static struct platform_driver mv_u3d_driver = { .probe = mv_u3d_probe, - .remove = mv_u3d_remove, + .remove_new = mv_u3d_remove, .shutdown = mv_u3d_shutdown, .driver = { .name = "mv-u3d", diff --git a/drivers/usb/gadget/udc/mv_udc_core.c b/drivers/usb/gadget/udc/mv_udc_core.c index 08474c08d874..79db74e2040b 100644 --- a/drivers/usb/gadget/udc/mv_udc_core.c +++ b/drivers/usb/gadget/udc/mv_udc_core.c @@ -2077,7 +2077,7 @@ static void gadget_release(struct device *_dev) complete(udc->done); } -static int mv_udc_remove(struct platform_device *pdev) +static void mv_udc_remove(struct platform_device *pdev) { struct mv_udc *udc; @@ -2099,8 +2099,6 @@ static int mv_udc_remove(struct platform_device *pdev) /* free dev, wait for the release() finished */ wait_for_completion(udc->done); - - return 0; } static int mv_udc_probe(struct platform_device *pdev) @@ -2411,7 +2409,7 @@ static void mv_udc_shutdown(struct platform_device *pdev) static struct platform_driver udc_driver = { .probe = mv_udc_probe, - .remove = mv_udc_remove, + .remove_new = mv_udc_remove, .shutdown = mv_udc_shutdown, .driver = { .name = "mv-udc", diff --git a/drivers/usb/gadget/udc/net2272.c b/drivers/usb/gadget/udc/net2272.c index 538c1b9a2883..12e76bb62c20 100644 --- a/drivers/usb/gadget/udc/net2272.c +++ b/drivers/usb/gadget/udc/net2272.c @@ -2670,7 +2670,7 @@ net2272_plat_probe(struct platform_device *pdev) return ret; } -static int +static void net2272_plat_remove(struct platform_device *pdev) { struct net2272 *dev = platform_get_drvdata(pdev); @@ -2681,13 +2681,11 @@ net2272_plat_remove(struct platform_device *pdev) resource_size(&pdev->resource[0])); usb_put_gadget(&dev->gadget); - - return 0; } static struct platform_driver net2272_plat_driver = { .probe = net2272_plat_probe, - .remove = net2272_plat_remove, + .remove_new = net2272_plat_remove, .driver = { .name = driver_name, }, diff --git a/drivers/usb/gadget/udc/omap_udc.c b/drivers/usb/gadget/udc/omap_udc.c index 2d87c7cd5f7e..10c5d7f726a1 100644 --- a/drivers/usb/gadget/udc/omap_udc.c +++ b/drivers/usb/gadget/udc/omap_udc.c @@ -2927,7 +2927,7 @@ cleanup0: return status; } -static int omap_udc_remove(struct platform_device *pdev) +static void omap_udc_remove(struct platform_device *pdev) { DECLARE_COMPLETION_ONSTACK(done); @@ -2939,8 +2939,6 @@ static int omap_udc_remove(struct platform_device *pdev) release_mem_region(pdev->resource[0].start, resource_size(&pdev->resource[0])); - - return 0; } /* suspend/resume/wakeup from sysfs (echo > power/state) or when the @@ -2985,7 +2983,7 @@ static int omap_udc_resume(struct platform_device *dev) static struct platform_driver udc_driver = { .probe = omap_udc_probe, - .remove = omap_udc_remove, + .remove_new = omap_udc_remove, .suspend = omap_udc_suspend, .resume = omap_udc_resume, .driver = { diff --git a/drivers/usb/gadget/udc/pxa27x_udc.c b/drivers/usb/gadget/udc/pxa27x_udc.c index fdf9cd4506b0..c4e1d957f913 100644 --- a/drivers/usb/gadget/udc/pxa27x_udc.c +++ b/drivers/usb/gadget/udc/pxa27x_udc.c @@ -2445,7 +2445,7 @@ err: * pxa_udc_remove - removes the udc device driver * @_dev: platform device */ -static int pxa_udc_remove(struct platform_device *_dev) +static void pxa_udc_remove(struct platform_device *_dev) { struct pxa_udc *udc = platform_get_drvdata(_dev); @@ -2460,8 +2460,6 @@ static int pxa_udc_remove(struct platform_device *_dev) udc->transceiver = NULL; the_controller = NULL; clk_unprepare(udc->clk); - - return 0; } static void pxa_udc_shutdown(struct platform_device *_dev) @@ -2541,7 +2539,7 @@ static struct platform_driver udc_driver = { .of_match_table = of_match_ptr(udc_pxa_dt_ids), }, .probe = pxa_udc_probe, - .remove = pxa_udc_remove, + .remove_new = pxa_udc_remove, .shutdown = pxa_udc_shutdown, #ifdef CONFIG_PM .suspend = pxa_udc_suspend, diff --git a/drivers/usb/gadget/udc/r8a66597-udc.c b/drivers/usb/gadget/udc/r8a66597-udc.c index 38e4d6b505a0..51b665f15c8e 100644 --- a/drivers/usb/gadget/udc/r8a66597-udc.c +++ b/drivers/usb/gadget/udc/r8a66597-udc.c @@ -1805,7 +1805,7 @@ static const struct usb_gadget_ops r8a66597_gadget_ops = { .set_selfpowered = r8a66597_set_selfpowered, }; -static int r8a66597_remove(struct platform_device *pdev) +static void r8a66597_remove(struct platform_device *pdev) { struct r8a66597 *r8a66597 = platform_get_drvdata(pdev); @@ -1816,8 +1816,6 @@ static int r8a66597_remove(struct platform_device *pdev) if (r8a66597->pdata->on_chip) { clk_disable_unprepare(r8a66597->clk); } - - return 0; } static void nop_completion(struct usb_ep *ep, struct usb_request *r) @@ -1966,7 +1964,7 @@ clean_up2: /*-------------------------------------------------------------------------*/ static struct platform_driver r8a66597_driver = { - .remove = r8a66597_remove, + .remove_new = r8a66597_remove, .driver = { .name = udc_name, }, diff --git a/drivers/usb/gadget/udc/renesas_usb3.c b/drivers/usb/gadget/udc/renesas_usb3.c index eb008e873361..59bb25de2015 100644 --- a/drivers/usb/gadget/udc/renesas_usb3.c +++ b/drivers/usb/gadget/udc/renesas_usb3.c @@ -2653,7 +2653,7 @@ static void renesas_usb3_debugfs_init(struct renesas_usb3 *usb3, } /*------- platform_driver ------------------------------------------------*/ -static int renesas_usb3_remove(struct platform_device *pdev) +static void renesas_usb3_remove(struct platform_device *pdev) { struct renesas_usb3 *usb3 = platform_get_drvdata(pdev); @@ -2669,8 +2669,6 @@ static int renesas_usb3_remove(struct platform_device *pdev) __renesas_usb3_ep_free_request(usb3->ep0_req); pm_runtime_disable(&pdev->dev); - - return 0; } static int renesas_usb3_init_ep(struct renesas_usb3 *usb3, struct device *dev, @@ -3015,7 +3013,7 @@ static SIMPLE_DEV_PM_OPS(renesas_usb3_pm_ops, renesas_usb3_suspend, static struct platform_driver renesas_usb3_driver = { .probe = renesas_usb3_probe, - .remove = renesas_usb3_remove, + .remove_new = renesas_usb3_remove, .driver = { .name = udc_name, .pm = &renesas_usb3_pm_ops, diff --git a/drivers/usb/gadget/udc/renesas_usbf.c b/drivers/usb/gadget/udc/renesas_usbf.c index 84ac9fe4ce7f..6cd0af83e91e 100644 --- a/drivers/usb/gadget/udc/renesas_usbf.c +++ b/drivers/usb/gadget/udc/renesas_usbf.c @@ -3361,15 +3361,13 @@ static int usbf_probe(struct platform_device *pdev) return 0; } -static int usbf_remove(struct platform_device *pdev) +static void usbf_remove(struct platform_device *pdev) { struct usbf_udc *udc = platform_get_drvdata(pdev); usb_del_gadget_udc(&udc->gadget); pm_runtime_put(&pdev->dev); - - return 0; } static const struct of_device_id usbf_match[] = { @@ -3385,7 +3383,7 @@ static struct platform_driver udc_driver = { .of_match_table = usbf_match, }, .probe = usbf_probe, - .remove = usbf_remove, + .remove_new = usbf_remove, }; module_platform_driver(udc_driver); diff --git a/drivers/usb/gadget/udc/rzv2m_usb3drd.c b/drivers/usb/gadget/udc/rzv2m_usb3drd.c index 589c7252e014..36f4ff00d22f 100644 --- a/drivers/usb/gadget/udc/rzv2m_usb3drd.c +++ b/drivers/usb/gadget/udc/rzv2m_usb3drd.c @@ -58,7 +58,7 @@ void rzv2m_usb3drd_reset(struct device *dev, bool host) } EXPORT_SYMBOL_GPL(rzv2m_usb3drd_reset); -static int rzv2m_usb3drd_remove(struct platform_device *pdev) +static void rzv2m_usb3drd_remove(struct platform_device *pdev) { struct rzv2m_usb3drd *usb3 = platform_get_drvdata(pdev); @@ -66,8 +66,6 @@ static int rzv2m_usb3drd_remove(struct platform_device *pdev) pm_runtime_put(usb3->dev); pm_runtime_disable(&pdev->dev); reset_control_assert(usb3->drd_rstc); - - return 0; } static int rzv2m_usb3drd_probe(struct platform_device *pdev) @@ -129,7 +127,7 @@ static struct platform_driver rzv2m_usb3drd_driver = { .of_match_table = rzv2m_usb3drd_of_match, }, .probe = rzv2m_usb3drd_probe, - .remove = rzv2m_usb3drd_remove, + .remove_new = rzv2m_usb3drd_remove, }; module_platform_driver(rzv2m_usb3drd_driver); diff --git a/drivers/usb/gadget/udc/snps_udc_plat.c b/drivers/usb/gadget/udc/snps_udc_plat.c index 0d3e705655b9..0ed685db149d 100644 --- a/drivers/usb/gadget/udc/snps_udc_plat.c +++ b/drivers/usb/gadget/udc/snps_udc_plat.c @@ -225,7 +225,7 @@ exit_phy: return ret; } -static int udc_plat_remove(struct platform_device *pdev) +static void udc_plat_remove(struct platform_device *pdev) { struct udc *dev; @@ -234,7 +234,7 @@ static int udc_plat_remove(struct platform_device *pdev) usb_del_gadget_udc(&dev->gadget); /* gadget driver must not be registered */ if (WARN_ON(dev->driver)) - return 0; + return; /* dma pool cleanup */ free_dma_pools(dev); @@ -248,8 +248,6 @@ static int udc_plat_remove(struct platform_device *pdev) extcon_unregister_notifier(dev->edev, EXTCON_USB, &dev->nb); dev_info(&pdev->dev, "Synopsys UDC platform driver removed\n"); - - return 0; } #ifdef CONFIG_PM_SLEEP @@ -315,7 +313,7 @@ MODULE_DEVICE_TABLE(of, of_udc_match); static struct platform_driver udc_plat_driver = { .probe = udc_plat_probe, - .remove = udc_plat_remove, + .remove_new = udc_plat_remove, .driver = { .name = "snps-udc-plat", .of_match_table = of_match_ptr(of_udc_match), diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c index 34e9c1df54c7..83eaa65ddde3 100644 --- a/drivers/usb/gadget/udc/tegra-xudc.c +++ b/drivers/usb/gadget/udc/tegra-xudc.c @@ -3906,7 +3906,7 @@ put_padctl: return err; } -static int tegra_xudc_remove(struct platform_device *pdev) +static void tegra_xudc_remove(struct platform_device *pdev) { struct tegra_xudc *xudc = platform_get_drvdata(pdev); unsigned int i; @@ -3936,8 +3936,6 @@ static int tegra_xudc_remove(struct platform_device *pdev) pm_runtime_put(xudc->dev); tegra_xusb_padctl_put(xudc->padctl); - - return 0; } static int __maybe_unused tegra_xudc_powergate(struct tegra_xudc *xudc) @@ -4063,7 +4061,7 @@ static const struct dev_pm_ops tegra_xudc_pm_ops = { static struct platform_driver tegra_xudc_driver = { .probe = tegra_xudc_probe, - .remove = tegra_xudc_remove, + .remove_new = tegra_xudc_remove, .driver = { .name = "tegra-xudc", .pm = &tegra_xudc_pm_ops, diff --git a/drivers/usb/gadget/udc/udc-xilinx.c b/drivers/usb/gadget/udc/udc-xilinx.c index 4827e3cd3834..a4a7b90a97e7 100644 --- a/drivers/usb/gadget/udc/udc-xilinx.c +++ b/drivers/usb/gadget/udc/udc-xilinx.c @@ -192,7 +192,7 @@ struct xusb_udc { bool dma_enabled; struct clk *clk; - unsigned int (*read_fn)(void __iomem *); + unsigned int (*read_fn)(void __iomem *reg); void (*write_fn)(void __iomem *, u32, u32); }; @@ -2178,14 +2178,12 @@ fail: * * Return: 0 always */ -static int xudc_remove(struct platform_device *pdev) +static void xudc_remove(struct platform_device *pdev) { struct xusb_udc *udc = platform_get_drvdata(pdev); usb_del_gadget_udc(&udc->gadget); clk_disable_unprepare(udc->clk); - - return 0; } #ifdef CONFIG_PM_SLEEP @@ -2257,7 +2255,7 @@ static struct platform_driver xudc_driver = { .pm = &xudc_pm_ops, }, .probe = xudc_probe, - .remove = xudc_remove, + .remove_new = xudc_remove, }; module_platform_driver(xudc_driver); diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index c170672f847e..4448d0ab06f0 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -376,7 +376,7 @@ config USB_ISP116X_HCD config USB_ISP1362_HCD tristate "ISP1362 HCD support" - depends on HAS_IOMEM + depends on HAS_IOPORT depends on COMPILE_TEST # nothing uses this help Supports the Philips ISP1362 chip as a host controller @@ -578,7 +578,7 @@ endif # USB_OHCI_HCD config USB_UHCI_HCD tristate "UHCI HCD (most Intel and VIA) support" - depends on USB_PCI || USB_UHCI_SUPPORT_NON_PCI_HC + depends on (USB_PCI && HAS_IOPORT) || USB_UHCI_SUPPORT_NON_PCI_HC help The Universal Host Controller Interface is a standard by Intel for accessing the USB hardware in the PC (which is also called the USB diff --git a/drivers/usb/host/ehci-atmel.c b/drivers/usb/host/ehci-atmel.c index 8b775e7bab06..61808c51e702 100644 --- a/drivers/usb/host/ehci-atmel.c +++ b/drivers/usb/host/ehci-atmel.c @@ -173,7 +173,7 @@ fail_create_hcd: return retval; } -static int ehci_atmel_drv_remove(struct platform_device *pdev) +static void ehci_atmel_drv_remove(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); @@ -181,8 +181,6 @@ static int ehci_atmel_drv_remove(struct platform_device *pdev) usb_put_hcd(hcd); atmel_stop_ehci(pdev); - - return 0; } static int __maybe_unused ehci_atmel_drv_suspend(struct device *dev) @@ -223,7 +221,7 @@ static SIMPLE_DEV_PM_OPS(ehci_atmel_pm_ops, ehci_atmel_drv_suspend, static struct platform_driver ehci_atmel_driver = { .probe = ehci_atmel_drv_probe, - .remove = ehci_atmel_drv_remove, + .remove_new = ehci_atmel_drv_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "atmel-ehci", diff --git a/drivers/usb/host/ehci-brcm.c b/drivers/usb/host/ehci-brcm.c index 6a0f64c9e5e8..0362a082abb4 100644 --- a/drivers/usb/host/ehci-brcm.c +++ b/drivers/usb/host/ehci-brcm.c @@ -188,7 +188,7 @@ err_hcd: return err; } -static int ehci_brcm_remove(struct platform_device *dev) +static void ehci_brcm_remove(struct platform_device *dev) { struct usb_hcd *hcd = platform_get_drvdata(dev); struct brcm_priv *priv = hcd_to_ehci_priv(hcd); @@ -196,7 +196,6 @@ static int ehci_brcm_remove(struct platform_device *dev) usb_remove_hcd(hcd); clk_disable_unprepare(priv->clk); usb_put_hcd(hcd); - return 0; } static int __maybe_unused ehci_brcm_suspend(struct device *dev) @@ -250,7 +249,7 @@ static const struct of_device_id brcm_ehci_of_match[] = { static struct platform_driver ehci_brcm_driver = { .probe = ehci_brcm_probe, - .remove = ehci_brcm_remove, + .remove_new = ehci_brcm_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "ehci-brcm", diff --git a/drivers/usb/host/ehci-exynos.c b/drivers/usb/host/ehci-exynos.c index 47c9f06c3d84..20f8c0ec6810 100644 --- a/drivers/usb/host/ehci-exynos.c +++ b/drivers/usb/host/ehci-exynos.c @@ -230,7 +230,7 @@ fail_clk: return err; } -static int exynos_ehci_remove(struct platform_device *pdev) +static void exynos_ehci_remove(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); struct exynos_ehci_hcd *exynos_ehci = to_exynos_ehci(hcd); @@ -244,8 +244,6 @@ static int exynos_ehci_remove(struct platform_device *pdev) clk_disable_unprepare(exynos_ehci->clk); usb_put_hcd(hcd); - - return 0; } #ifdef CONFIG_PM @@ -311,7 +309,7 @@ MODULE_DEVICE_TABLE(of, exynos_ehci_match); static struct platform_driver exynos_ehci_driver = { .probe = exynos_ehci_probe, - .remove = exynos_ehci_remove, + .remove_new = exynos_ehci_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "exynos-ehci", diff --git a/drivers/usb/host/ehci-fsl.c b/drivers/usb/host/ehci-fsl.c index d74fa5ba845b..81d60a695510 100644 --- a/drivers/usb/host/ehci-fsl.c +++ b/drivers/usb/host/ehci-fsl.c @@ -684,7 +684,7 @@ static const struct ehci_driver_overrides ehci_fsl_overrides __initconst = { * * Reverses the effect of usb_hcd_fsl_probe(). */ -static int fsl_ehci_drv_remove(struct platform_device *pdev) +static void fsl_ehci_drv_remove(struct platform_device *pdev) { struct fsl_usb2_platform_data *pdata = dev_get_platdata(&pdev->dev); struct usb_hcd *hcd = platform_get_drvdata(pdev); @@ -703,13 +703,11 @@ static int fsl_ehci_drv_remove(struct platform_device *pdev) if (pdata->exit) pdata->exit(pdev); usb_put_hcd(hcd); - - return 0; } static struct platform_driver ehci_fsl_driver = { .probe = fsl_ehci_drv_probe, - .remove = fsl_ehci_drv_remove, + .remove_new = fsl_ehci_drv_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { .name = DRV_NAME, diff --git a/drivers/usb/host/ehci-grlib.c b/drivers/usb/host/ehci-grlib.c index 0717f2ccf49d..14150e4d3382 100644 --- a/drivers/usb/host/ehci-grlib.c +++ b/drivers/usb/host/ehci-grlib.c @@ -140,7 +140,7 @@ err_irq: } -static int ehci_hcd_grlib_remove(struct platform_device *op) +static void ehci_hcd_grlib_remove(struct platform_device *op) { struct usb_hcd *hcd = platform_get_drvdata(op); @@ -151,8 +151,6 @@ static int ehci_hcd_grlib_remove(struct platform_device *op) irq_dispose_mapping(hcd->irq); usb_put_hcd(hcd); - - return 0; } @@ -170,7 +168,7 @@ MODULE_DEVICE_TABLE(of, ehci_hcd_grlib_of_match); static struct platform_driver ehci_grlib_driver = { .probe = ehci_hcd_grlib_probe, - .remove = ehci_hcd_grlib_remove, + .remove_new = ehci_hcd_grlib_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "grlib-ehci", diff --git a/drivers/usb/host/ehci-mv.c b/drivers/usb/host/ehci-mv.c index fa46d217dd10..9320cf0e5bc7 100644 --- a/drivers/usb/host/ehci-mv.c +++ b/drivers/usb/host/ehci-mv.c @@ -235,7 +235,7 @@ err_put_hcd: return retval; } -static int mv_ehci_remove(struct platform_device *pdev) +static void mv_ehci_remove(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); struct ehci_hcd_mv *ehci_mv = hcd_to_ehci_hcd_mv(hcd); @@ -254,8 +254,6 @@ static int mv_ehci_remove(struct platform_device *pdev) } usb_put_hcd(hcd); - - return 0; } static const struct platform_device_id ehci_id_table[] = { @@ -282,7 +280,7 @@ static const struct of_device_id ehci_mv_dt_ids[] = { static struct platform_driver ehci_mv_driver = { .probe = mv_ehci_probe, - .remove = mv_ehci_remove, + .remove_new = mv_ehci_remove, .shutdown = mv_ehci_shutdown, .driver = { .name = "mv-ehci", diff --git a/drivers/usb/host/ehci-npcm7xx.c b/drivers/usb/host/ehci-npcm7xx.c index 63af1a827fcb..ad79ce63afcf 100644 --- a/drivers/usb/host/ehci-npcm7xx.c +++ b/drivers/usb/host/ehci-npcm7xx.c @@ -106,15 +106,13 @@ fail: return retval; } -static int npcm7xx_ehci_hcd_drv_remove(struct platform_device *pdev) +static void npcm7xx_ehci_hcd_drv_remove(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); usb_remove_hcd(hcd); usb_put_hcd(hcd); - - return 0; } static const struct of_device_id npcm7xx_ehci_id_table[] = { @@ -125,7 +123,7 @@ MODULE_DEVICE_TABLE(of, npcm7xx_ehci_id_table); static struct platform_driver npcm7xx_ehci_hcd_driver = { .probe = npcm7xx_ehci_hcd_drv_probe, - .remove = npcm7xx_ehci_hcd_drv_remove, + .remove_new = npcm7xx_ehci_hcd_drv_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "npcm7xx-ehci", diff --git a/drivers/usb/host/ehci-omap.c b/drivers/usb/host/ehci-omap.c index 7dd984722a7f..cb6509a735ac 100644 --- a/drivers/usb/host/ehci-omap.c +++ b/drivers/usb/host/ehci-omap.c @@ -237,7 +237,7 @@ err_phy: * the HCD's stop() method. It is always called from a thread * context, normally "rmmod", "apmd", or something similar. */ -static int ehci_hcd_omap_remove(struct platform_device *pdev) +static void ehci_hcd_omap_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct usb_hcd *hcd = dev_get_drvdata(dev); @@ -254,8 +254,6 @@ static int ehci_hcd_omap_remove(struct platform_device *pdev) usb_put_hcd(hcd); pm_runtime_put_sync(dev); pm_runtime_disable(dev); - - return 0; } static const struct of_device_id omap_ehci_dt_ids[] = { @@ -267,7 +265,7 @@ MODULE_DEVICE_TABLE(of, omap_ehci_dt_ids); static struct platform_driver ehci_hcd_omap_driver = { .probe = ehci_hcd_omap_probe, - .remove = ehci_hcd_omap_remove, + .remove_new = ehci_hcd_omap_remove, .shutdown = usb_hcd_platform_shutdown, /*.suspend = ehci_hcd_omap_suspend, */ /*.resume = ehci_hcd_omap_resume, */ diff --git a/drivers/usb/host/ehci-orion.c b/drivers/usb/host/ehci-orion.c index a3454a3ea4e0..2cfb27dc943a 100644 --- a/drivers/usb/host/ehci-orion.c +++ b/drivers/usb/host/ehci-orion.c @@ -321,7 +321,7 @@ err: return err; } -static int ehci_orion_drv_remove(struct platform_device *pdev) +static void ehci_orion_drv_remove(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); struct orion_ehci_hcd *priv = hcd_to_orion_priv(hcd); @@ -332,8 +332,6 @@ static int ehci_orion_drv_remove(struct platform_device *pdev) clk_disable_unprepare(priv->clk); usb_put_hcd(hcd); - - return 0; } static const struct of_device_id ehci_orion_dt_ids[] = { @@ -345,7 +343,7 @@ MODULE_DEVICE_TABLE(of, ehci_orion_dt_ids); static struct platform_driver ehci_orion_driver = { .probe = ehci_orion_drv_probe, - .remove = ehci_orion_drv_remove, + .remove_new = ehci_orion_drv_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "orion-ehci", diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 4b148fe5e43b..889dc4426271 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -354,10 +354,11 @@ done: * Also they depend on separate root hub suspend/resume. */ -static int ehci_pci_resume(struct usb_hcd *hcd, bool hibernated) +static int ehci_pci_resume(struct usb_hcd *hcd, pm_message_t msg) { struct ehci_hcd *ehci = hcd_to_ehci(hcd); struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + bool hibernated = (msg.event == PM_EVENT_RESTORE); if (ehci_resume(hcd, hibernated) != 0) (void) ehci_pci_reinit(ehci, pdev); diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c index fe497c876d76..83bf56c9424f 100644 --- a/drivers/usb/host/ehci-platform.c +++ b/drivers/usb/host/ehci-platform.c @@ -400,7 +400,7 @@ err_put_clks: return err; } -static int ehci_platform_remove(struct platform_device *dev) +static void ehci_platform_remove(struct platform_device *dev) { struct usb_hcd *hcd = platform_get_drvdata(dev); struct usb_ehci_pdata *pdata = dev_get_platdata(&dev->dev); @@ -424,8 +424,6 @@ static int ehci_platform_remove(struct platform_device *dev) if (pdata == &ehci_platform_defaults) dev->dev.platform_data = NULL; - - return 0; } static int __maybe_unused ehci_platform_suspend(struct device *dev) @@ -511,7 +509,7 @@ static SIMPLE_DEV_PM_OPS(ehci_platform_pm_ops, ehci_platform_suspend, static struct platform_driver ehci_platform_driver = { .id_table = ehci_platform_table, .probe = ehci_platform_probe, - .remove = ehci_platform_remove, + .remove_new = ehci_platform_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "ehci-platform", diff --git a/drivers/usb/host/ehci-ppc-of.c b/drivers/usb/host/ehci-ppc-of.c index b3aa464e9d2c..7fd83e806ae4 100644 --- a/drivers/usb/host/ehci-ppc-of.c +++ b/drivers/usb/host/ehci-ppc-of.c @@ -184,7 +184,7 @@ err_irq: } -static int ehci_hcd_ppc_of_remove(struct platform_device *op) +static void ehci_hcd_ppc_of_remove(struct platform_device *op) { struct usb_hcd *hcd = platform_get_drvdata(op); struct ehci_hcd *ehci = hcd_to_ehci(hcd); @@ -216,8 +216,6 @@ static int ehci_hcd_ppc_of_remove(struct platform_device *op) } } usb_put_hcd(hcd); - - return 0; } @@ -232,7 +230,7 @@ MODULE_DEVICE_TABLE(of, ehci_hcd_ppc_of_match); static struct platform_driver ehci_hcd_ppc_of_driver = { .probe = ehci_hcd_ppc_of_probe, - .remove = ehci_hcd_ppc_of_remove, + .remove_new = ehci_hcd_ppc_of_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "ppc-of-ehci", diff --git a/drivers/usb/host/ehci-sh.c b/drivers/usb/host/ehci-sh.c index c25c51d26f26..0520e762801d 100644 --- a/drivers/usb/host/ehci-sh.c +++ b/drivers/usb/host/ehci-sh.c @@ -147,7 +147,7 @@ fail_create_hcd: return ret; } -static int ehci_hcd_sh_remove(struct platform_device *pdev) +static void ehci_hcd_sh_remove(struct platform_device *pdev) { struct ehci_sh_priv *priv = platform_get_drvdata(pdev); struct usb_hcd *hcd = priv->hcd; @@ -157,8 +157,6 @@ static int ehci_hcd_sh_remove(struct platform_device *pdev) clk_disable(priv->fclk); clk_disable(priv->iclk); - - return 0; } static void ehci_hcd_sh_shutdown(struct platform_device *pdev) @@ -172,7 +170,7 @@ static void ehci_hcd_sh_shutdown(struct platform_device *pdev) static struct platform_driver ehci_hcd_sh_driver = { .probe = ehci_hcd_sh_probe, - .remove = ehci_hcd_sh_remove, + .remove_new = ehci_hcd_sh_remove, .shutdown = ehci_hcd_sh_shutdown, .driver = { .name = "sh_ehci", diff --git a/drivers/usb/host/ehci-spear.c b/drivers/usb/host/ehci-spear.c index c4ddd1022f60..1407703649be 100644 --- a/drivers/usb/host/ehci-spear.c +++ b/drivers/usb/host/ehci-spear.c @@ -124,7 +124,7 @@ fail: return retval ; } -static int spear_ehci_hcd_drv_remove(struct platform_device *pdev) +static void spear_ehci_hcd_drv_remove(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); struct spear_ehci *sehci = to_spear_ehci(hcd); @@ -134,8 +134,6 @@ static int spear_ehci_hcd_drv_remove(struct platform_device *pdev) if (sehci->clk) clk_disable_unprepare(sehci->clk); usb_put_hcd(hcd); - - return 0; } static const struct of_device_id spear_ehci_id_table[] = { @@ -146,7 +144,7 @@ MODULE_DEVICE_TABLE(of, spear_ehci_id_table); static struct platform_driver spear_ehci_hcd_driver = { .probe = spear_ehci_hcd_drv_probe, - .remove = spear_ehci_hcd_drv_remove, + .remove_new = spear_ehci_hcd_drv_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "spear-ehci", diff --git a/drivers/usb/host/ehci-st.c b/drivers/usb/host/ehci-st.c index f731dc98c533..ee0976b815b4 100644 --- a/drivers/usb/host/ehci-st.c +++ b/drivers/usb/host/ehci-st.c @@ -252,7 +252,7 @@ err_put_hcd: return err; } -static int st_ehci_platform_remove(struct platform_device *dev) +static void st_ehci_platform_remove(struct platform_device *dev) { struct usb_hcd *hcd = platform_get_drvdata(dev); struct usb_ehci_pdata *pdata = dev_get_platdata(&dev->dev); @@ -271,8 +271,6 @@ static int st_ehci_platform_remove(struct platform_device *dev) if (pdata == &ehci_platform_defaults) dev->dev.platform_data = NULL; - - return 0; } #ifdef CONFIG_PM_SLEEP @@ -328,7 +326,7 @@ MODULE_DEVICE_TABLE(of, st_ehci_ids); static struct platform_driver ehci_platform_driver = { .probe = st_ehci_platform_probe, - .remove = st_ehci_platform_remove, + .remove_new = st_ehci_platform_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "st-ehci", diff --git a/drivers/usb/host/ehci-xilinx-of.c b/drivers/usb/host/ehci-xilinx-of.c index 3d7893747835..a2112c28f631 100644 --- a/drivers/usb/host/ehci-xilinx-of.c +++ b/drivers/usb/host/ehci-xilinx-of.c @@ -201,7 +201,7 @@ err_irq: * * Return: Always return 0 */ -static int ehci_hcd_xilinx_of_remove(struct platform_device *op) +static void ehci_hcd_xilinx_of_remove(struct platform_device *op) { struct usb_hcd *hcd = platform_get_drvdata(op); @@ -210,8 +210,6 @@ static int ehci_hcd_xilinx_of_remove(struct platform_device *op) usb_remove_hcd(hcd); usb_put_hcd(hcd); - - return 0; } static const struct of_device_id ehci_hcd_xilinx_of_match[] = { @@ -222,7 +220,7 @@ MODULE_DEVICE_TABLE(of, ehci_hcd_xilinx_of_match); static struct platform_driver ehci_hcd_xilinx_of_driver = { .probe = ehci_hcd_xilinx_of_probe, - .remove = ehci_hcd_xilinx_of_remove, + .remove_new = ehci_hcd_xilinx_of_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "xilinx-of-ehci", diff --git a/drivers/usb/host/fhci-hcd.c b/drivers/usb/host/fhci-hcd.c index 92794ffc25c8..66a045e01dad 100644 --- a/drivers/usb/host/fhci-hcd.c +++ b/drivers/usb/host/fhci-hcd.c @@ -757,7 +757,7 @@ err_regs: return ret; } -static int fhci_remove(struct device *dev) +static void fhci_remove(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); struct fhci_hcd *fhci = hcd_to_fhci(hcd); @@ -771,12 +771,11 @@ static int fhci_remove(struct device *dev) qe_pin_free(fhci->pins[j]); fhci_dfs_destroy(fhci); usb_put_hcd(hcd); - return 0; } -static int of_fhci_remove(struct platform_device *ofdev) +static void of_fhci_remove(struct platform_device *ofdev) { - return fhci_remove(&ofdev->dev); + fhci_remove(&ofdev->dev); } static const struct of_device_id of_fhci_match[] = { @@ -791,7 +790,7 @@ static struct platform_driver of_fhci_driver = { .of_match_table = of_fhci_match, }, .probe = of_fhci_probe, - .remove = of_fhci_remove, + .remove_new = of_fhci_remove, }; module_platform_driver(of_fhci_driver); diff --git a/drivers/usb/host/fsl-mph-dr-of.c b/drivers/usb/host/fsl-mph-dr-of.c index 9db909d12354..a9877f2569f4 100644 --- a/drivers/usb/host/fsl-mph-dr-of.c +++ b/drivers/usb/host/fsl-mph-dr-of.c @@ -265,10 +265,9 @@ static int __unregister_subdev(struct device *dev, void *d) return 0; } -static int fsl_usb2_mph_dr_of_remove(struct platform_device *ofdev) +static void fsl_usb2_mph_dr_of_remove(struct platform_device *ofdev) { device_for_each_child(&ofdev->dev, NULL, __unregister_subdev); - return 0; } #ifdef CONFIG_PPC_MPC512x @@ -362,7 +361,7 @@ static struct platform_driver fsl_usb2_mph_dr_driver = { .of_match_table = fsl_usb2_mph_dr_of_match, }, .probe = fsl_usb2_mph_dr_of_probe, - .remove = fsl_usb2_mph_dr_of_remove, + .remove_new = fsl_usb2_mph_dr_of_remove, }; module_platform_driver(fsl_usb2_mph_dr_driver); diff --git a/drivers/usb/host/isp116x-hcd.c b/drivers/usb/host/isp116x-hcd.c index 49ae01487af4..a82d8926e922 100644 --- a/drivers/usb/host/isp116x-hcd.c +++ b/drivers/usb/host/isp116x-hcd.c @@ -1526,14 +1526,14 @@ static const struct hc_driver isp116x_hc_driver = { /*----------------------------------------------------------------*/ -static int isp116x_remove(struct platform_device *pdev) +static void isp116x_remove(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); struct isp116x *isp116x; struct resource *res; if (!hcd) - return 0; + return; isp116x = hcd_to_isp116x(hcd); remove_debug_file(isp116x); usb_remove_hcd(hcd); @@ -1548,7 +1548,6 @@ static int isp116x_remove(struct platform_device *pdev) release_mem_region(res->start, 2); usb_put_hcd(hcd); - return 0; } static int isp116x_probe(struct platform_device *pdev) @@ -1685,7 +1684,7 @@ MODULE_ALIAS("platform:isp116x-hcd"); static struct platform_driver isp116x_driver = { .probe = isp116x_probe, - .remove = isp116x_remove, + .remove_new = isp116x_remove, .suspend = isp116x_suspend, .resume = isp116x_resume, .driver = { diff --git a/drivers/usb/host/isp1362-hcd.c b/drivers/usb/host/isp1362-hcd.c index b0da143ef4be..606f0a64f3b7 100644 --- a/drivers/usb/host/isp1362-hcd.c +++ b/drivers/usb/host/isp1362-hcd.c @@ -2606,7 +2606,7 @@ static const struct hc_driver isp1362_hc_driver = { /*-------------------------------------------------------------------------*/ -static int isp1362_remove(struct platform_device *pdev) +static void isp1362_remove(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); struct isp1362_hcd *isp1362_hcd = hcd_to_isp1362_hcd(hcd); @@ -2617,8 +2617,6 @@ static int isp1362_remove(struct platform_device *pdev) DBG(0, "%s: put_hcd\n", __func__); usb_put_hcd(hcd); DBG(0, "%s: Done\n", __func__); - - return 0; } static int isp1362_probe(struct platform_device *pdev) @@ -2760,7 +2758,7 @@ static int isp1362_resume(struct platform_device *pdev) static struct platform_driver isp1362_driver = { .probe = isp1362_probe, - .remove = isp1362_remove, + .remove_new = isp1362_remove, .suspend = isp1362_suspend, .resume = isp1362_resume, diff --git a/drivers/usb/host/octeon-hcd.c b/drivers/usb/host/octeon-hcd.c index a1cd81d4a114..19d5777f5db2 100644 --- a/drivers/usb/host/octeon-hcd.c +++ b/drivers/usb/host/octeon-hcd.c @@ -3680,7 +3680,7 @@ static int octeon_usb_probe(struct platform_device *pdev) return 0; } -static int octeon_usb_remove(struct platform_device *pdev) +static void octeon_usb_remove(struct platform_device *pdev) { int status; struct device *dev = &pdev->dev; @@ -3696,8 +3696,6 @@ static int octeon_usb_remove(struct platform_device *pdev) dev_dbg(dev, "USB shutdown failed with %d\n", status); usb_put_hcd(hcd); - - return 0; } static const struct of_device_id octeon_usb_match[] = { @@ -3714,7 +3712,7 @@ static struct platform_driver octeon_usb_driver = { .of_match_table = octeon_usb_match, }, .probe = octeon_usb_probe, - .remove = octeon_usb_remove, + .remove_new = octeon_usb_remove, }; static int __init octeon_usb_driver_init(void) diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c index 533537ef3c21..b9ce8d80f20b 100644 --- a/drivers/usb/host/ohci-at91.c +++ b/drivers/usb/host/ohci-at91.c @@ -599,7 +599,7 @@ static int ohci_hcd_at91_drv_probe(struct platform_device *pdev) return usb_hcd_at91_probe(&ohci_at91_hc_driver, pdev); } -static int ohci_hcd_at91_drv_remove(struct platform_device *pdev) +static void ohci_hcd_at91_drv_remove(struct platform_device *pdev) { struct at91_usbh_data *pdata = dev_get_platdata(&pdev->dev); int i; @@ -611,7 +611,6 @@ static int ohci_hcd_at91_drv_remove(struct platform_device *pdev) device_init_wakeup(&pdev->dev, 0); usb_hcd_at91_remove(platform_get_drvdata(pdev), pdev); - return 0; } static int __maybe_unused @@ -683,7 +682,7 @@ static SIMPLE_DEV_PM_OPS(ohci_hcd_at91_pm_ops, ohci_hcd_at91_drv_suspend, static struct platform_driver ohci_hcd_at91_driver = { .probe = ohci_hcd_at91_drv_probe, - .remove = ohci_hcd_at91_drv_remove, + .remove_new = ohci_hcd_at91_drv_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "at91_ohci", diff --git a/drivers/usb/host/ohci-da8xx.c b/drivers/usb/host/ohci-da8xx.c index d4818e8d652b..e4191a868944 100644 --- a/drivers/usb/host/ohci-da8xx.c +++ b/drivers/usb/host/ohci-da8xx.c @@ -469,14 +469,12 @@ err: return error; } -static int ohci_da8xx_remove(struct platform_device *pdev) +static void ohci_da8xx_remove(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); usb_remove_hcd(hcd); usb_put_hcd(hcd); - - return 0; } #ifdef CONFIG_PM @@ -533,7 +531,7 @@ static const struct ohci_driver_overrides da8xx_overrides __initconst = { */ static struct platform_driver ohci_hcd_da8xx_driver = { .probe = ohci_da8xx_probe, - .remove = ohci_da8xx_remove, + .remove_new = ohci_da8xx_remove, .shutdown = usb_hcd_platform_shutdown, #ifdef CONFIG_PM .suspend = ohci_da8xx_suspend, diff --git a/drivers/usb/host/ohci-exynos.c b/drivers/usb/host/ohci-exynos.c index 8af17c1ee5cc..ab31c459b32d 100644 --- a/drivers/usb/host/ohci-exynos.c +++ b/drivers/usb/host/ohci-exynos.c @@ -198,7 +198,7 @@ fail_clk: return err; } -static int exynos_ohci_remove(struct platform_device *pdev) +static void exynos_ohci_remove(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); struct exynos_ohci_hcd *exynos_ohci = to_exynos_ohci(hcd); @@ -212,8 +212,6 @@ static int exynos_ohci_remove(struct platform_device *pdev) clk_disable_unprepare(exynos_ohci->clk); usb_put_hcd(hcd); - - return 0; } static void exynos_ohci_shutdown(struct platform_device *pdev) @@ -285,7 +283,7 @@ MODULE_DEVICE_TABLE(of, exynos_ohci_match); static struct platform_driver exynos_ohci_driver = { .probe = exynos_ohci_probe, - .remove = exynos_ohci_remove, + .remove_new = exynos_ohci_remove, .shutdown = exynos_ohci_shutdown, .driver = { .name = "exynos-ohci", diff --git a/drivers/usb/host/ohci-nxp.c b/drivers/usb/host/ohci-nxp.c index 5b32e683e367..c04b2af5c766 100644 --- a/drivers/usb/host/ohci-nxp.c +++ b/drivers/usb/host/ohci-nxp.c @@ -237,7 +237,7 @@ fail_disable: return ret; } -static int ohci_hcd_nxp_remove(struct platform_device *pdev) +static void ohci_hcd_nxp_remove(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); @@ -246,8 +246,6 @@ static int ohci_hcd_nxp_remove(struct platform_device *pdev) usb_put_hcd(hcd); clk_disable_unprepare(usb_host_clk); isp1301_i2c_client = NULL; - - return 0; } /* work with hotplug and coldplug */ @@ -267,7 +265,7 @@ static struct platform_driver ohci_hcd_nxp_driver = { .of_match_table = of_match_ptr(ohci_hcd_nxp_match), }, .probe = ohci_hcd_nxp_probe, - .remove = ohci_hcd_nxp_remove, + .remove_new = ohci_hcd_nxp_remove, }; static int __init ohci_nxp_init(void) diff --git a/drivers/usb/host/ohci-omap.c b/drivers/usb/host/ohci-omap.c index c82121602511..21a6f6c55e07 100644 --- a/drivers/usb/host/ohci-omap.c +++ b/drivers/usb/host/ohci-omap.c @@ -321,7 +321,7 @@ err_put_hcd: * the HCD's stop() method. It is always called from a thread * context, normally "rmmod", "apmd", or something similar. */ -static int ohci_hcd_omap_remove(struct platform_device *pdev) +static void ohci_hcd_omap_remove(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); struct ohci_omap_priv *priv = hcd_to_ohci_omap_priv(hcd); @@ -340,7 +340,6 @@ static int ohci_hcd_omap_remove(struct platform_device *pdev) clk_unprepare(priv->usb_host_ck); clk_put(priv->usb_host_ck); usb_put_hcd(hcd); - return 0; } /*-------------------------------------------------------------------------*/ @@ -391,7 +390,7 @@ static int ohci_omap_resume(struct platform_device *dev) */ static struct platform_driver ohci_hcd_omap_driver = { .probe = ohci_hcd_omap_probe, - .remove = ohci_hcd_omap_remove, + .remove_new = ohci_hcd_omap_remove, .shutdown = usb_hcd_platform_shutdown, #ifdef CONFIG_PM .suspend = ohci_omap_suspend, diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index d7b4f40f9ff4..900ea0d368e0 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -301,6 +301,12 @@ static struct pci_driver ohci_pci_driver = { #endif }; +#ifdef CONFIG_PM +static int ohci_pci_resume(struct usb_hcd *hcd, pm_message_t msg) +{ + return ohci_resume(hcd, msg.event == PM_EVENT_RESTORE); +} +#endif static int __init ohci_pci_init(void) { if (usb_disabled()) @@ -311,7 +317,7 @@ static int __init ohci_pci_init(void) #ifdef CONFIG_PM /* Entries for the PCI suspend/resume callbacks are special */ ohci_pci_hc_driver.pci_suspend = ohci_suspend; - ohci_pci_hc_driver.pci_resume = ohci_resume; + ohci_pci_hc_driver.pci_resume = ohci_pci_resume; #endif return pci_register_driver(&ohci_pci_driver); diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c index a84305091c43..45a2c981319e 100644 --- a/drivers/usb/host/ohci-platform.c +++ b/drivers/usb/host/ohci-platform.c @@ -33,7 +33,7 @@ #include "ohci.h" #define DRIVER_DESC "OHCI generic platform driver" -#define OHCI_MAX_CLKS 3 +#define OHCI_MAX_CLKS 4 #define hcd_to_ohci_priv(h) ((struct ohci_platform_priv *)hcd_to_ohci(h)->priv) struct ohci_platform_priv { @@ -239,7 +239,7 @@ err_put_clks: return err; } -static int ohci_platform_remove(struct platform_device *dev) +static void ohci_platform_remove(struct platform_device *dev) { struct usb_hcd *hcd = platform_get_drvdata(dev); struct usb_ohci_pdata *pdata = dev_get_platdata(&dev->dev); @@ -264,8 +264,6 @@ static int ohci_platform_remove(struct platform_device *dev) if (pdata == &ohci_platform_defaults) dev->dev.platform_data = NULL; - - return 0; } #ifdef CONFIG_PM_SLEEP @@ -347,7 +345,7 @@ static const struct dev_pm_ops ohci_platform_pm_ops = { static struct platform_driver ohci_platform_driver = { .id_table = ohci_platform_table, .probe = ohci_platform_probe, - .remove = ohci_platform_remove, + .remove_new = ohci_platform_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "ohci-platform", diff --git a/drivers/usb/host/ohci-ppc-of.c b/drivers/usb/host/ohci-ppc-of.c index f2f6c832ec98..35a7ad7e2569 100644 --- a/drivers/usb/host/ohci-ppc-of.c +++ b/drivers/usb/host/ohci-ppc-of.c @@ -176,7 +176,7 @@ err_rmr: return rv; } -static int ohci_hcd_ppc_of_remove(struct platform_device *op) +static void ohci_hcd_ppc_of_remove(struct platform_device *op) { struct usb_hcd *hcd = platform_get_drvdata(op); @@ -187,8 +187,6 @@ static int ohci_hcd_ppc_of_remove(struct platform_device *op) irq_dispose_mapping(hcd->irq); usb_put_hcd(hcd); - - return 0; } static const struct of_device_id ohci_hcd_ppc_of_match[] = { @@ -224,7 +222,7 @@ MODULE_DEVICE_TABLE(of, ohci_hcd_ppc_of_match); static struct platform_driver ohci_hcd_ppc_of_driver = { .probe = ohci_hcd_ppc_of_probe, - .remove = ohci_hcd_ppc_of_remove, + .remove_new = ohci_hcd_ppc_of_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "ppc-of-ohci", diff --git a/drivers/usb/host/ohci-pxa27x.c b/drivers/usb/host/ohci-pxa27x.c index dcac2938789a..7410442f720f 100644 --- a/drivers/usb/host/ohci-pxa27x.c +++ b/drivers/usb/host/ohci-pxa27x.c @@ -501,7 +501,7 @@ static int ohci_hcd_pxa27x_probe(struct platform_device *pdev) * the HCD's stop() method. It is always called from a thread * context, normally "rmmod", "apmd", or something similar. */ -static int ohci_hcd_pxa27x_remove(struct platform_device *pdev) +static void ohci_hcd_pxa27x_remove(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); struct pxa27x_ohci *pxa_ohci = to_pxa27x_ohci(hcd); @@ -514,7 +514,6 @@ static int ohci_hcd_pxa27x_remove(struct platform_device *pdev) pxa27x_ohci_set_vbus_power(pxa_ohci, i, false); usb_put_hcd(hcd); - return 0; } /*-------------------------------------------------------------------------*/ @@ -572,7 +571,7 @@ static const struct dev_pm_ops ohci_hcd_pxa27x_pm_ops = { static struct platform_driver ohci_hcd_pxa27x_driver = { .probe = ohci_hcd_pxa27x_probe, - .remove = ohci_hcd_pxa27x_remove, + .remove_new = ohci_hcd_pxa27x_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "pxa27x-ohci", diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c index 85a0a9ae0095..c5c9b4cbcb9a 100644 --- a/drivers/usb/host/ohci-s3c2410.c +++ b/drivers/usb/host/ohci-s3c2410.c @@ -329,7 +329,7 @@ static void s3c2410_hcd_oc(struct s3c2410_hcd_info *info, int port_oc) * the HCD's stop() method. It is always called from a thread * context, normally "rmmod", "apmd", or something similar. */ -static int +static void ohci_hcd_s3c2410_remove(struct platform_device *dev) { struct usb_hcd *hcd = platform_get_drvdata(dev); @@ -337,7 +337,6 @@ ohci_hcd_s3c2410_remove(struct platform_device *dev) usb_remove_hcd(hcd); s3c2410_stop_hc(dev); usb_put_hcd(hcd); - return 0; } /* @@ -458,7 +457,7 @@ MODULE_DEVICE_TABLE(of, ohci_hcd_s3c2410_dt_ids); static struct platform_driver ohci_hcd_s3c2410_driver = { .probe = ohci_hcd_s3c2410_probe, - .remove = ohci_hcd_s3c2410_remove, + .remove_new = ohci_hcd_s3c2410_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "s3c2410-ohci", diff --git a/drivers/usb/host/ohci-sm501.c b/drivers/usb/host/ohci-sm501.c index f5de586454e3..0468eeb4fcfd 100644 --- a/drivers/usb/host/ohci-sm501.c +++ b/drivers/usb/host/ohci-sm501.c @@ -185,7 +185,7 @@ err0: return retval; } -static int ohci_hcd_sm501_drv_remove(struct platform_device *pdev) +static void ohci_hcd_sm501_drv_remove(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); struct resource *mem; @@ -202,8 +202,6 @@ static int ohci_hcd_sm501_drv_remove(struct platform_device *pdev) sm501_modify_reg(pdev->dev.parent, SM501_IRQ_MASK, 0, 1 << 6); sm501_unit_power(pdev->dev.parent, SM501_GATE_USB_HOST, 0); - - return 0; } /*-------------------------------------------------------------------------*/ @@ -255,7 +253,7 @@ static int ohci_sm501_resume(struct platform_device *pdev) */ static struct platform_driver ohci_hcd_sm501_driver = { .probe = ohci_hcd_sm501_drv_probe, - .remove = ohci_hcd_sm501_drv_remove, + .remove_new = ohci_hcd_sm501_drv_remove, .shutdown = usb_hcd_platform_shutdown, .suspend = ohci_sm501_suspend, .resume = ohci_sm501_resume, diff --git a/drivers/usb/host/ohci-spear.c b/drivers/usb/host/ohci-spear.c index 196951a27f3f..f4b2656407dd 100644 --- a/drivers/usb/host/ohci-spear.c +++ b/drivers/usb/host/ohci-spear.c @@ -98,7 +98,7 @@ fail: return retval; } -static int spear_ohci_hcd_drv_remove(struct platform_device *pdev) +static void spear_ohci_hcd_drv_remove(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); struct spear_ohci *sohci_p = to_spear_ohci(hcd); @@ -108,7 +108,6 @@ static int spear_ohci_hcd_drv_remove(struct platform_device *pdev) clk_disable_unprepare(sohci_p->clk); usb_put_hcd(hcd); - return 0; } #if defined(CONFIG_PM) @@ -159,7 +158,7 @@ MODULE_DEVICE_TABLE(of, spear_ohci_id_table); /* Driver definition to register with the platform bus */ static struct platform_driver spear_ohci_hcd_driver = { .probe = spear_ohci_hcd_drv_probe, - .remove = spear_ohci_hcd_drv_remove, + .remove_new = spear_ohci_hcd_drv_remove, #ifdef CONFIG_PM .suspend = spear_ohci_hcd_drv_suspend, .resume = spear_ohci_hcd_drv_resume, diff --git a/drivers/usb/host/ohci-st.c b/drivers/usb/host/ohci-st.c index 82eef3c62e11..884e447a8098 100644 --- a/drivers/usb/host/ohci-st.c +++ b/drivers/usb/host/ohci-st.c @@ -233,7 +233,7 @@ err_put_hcd: return err; } -static int st_ohci_platform_remove(struct platform_device *dev) +static void st_ohci_platform_remove(struct platform_device *dev) { struct usb_hcd *hcd = platform_get_drvdata(dev); struct usb_ohci_pdata *pdata = dev_get_platdata(&dev->dev); @@ -253,8 +253,6 @@ static int st_ohci_platform_remove(struct platform_device *dev) if (pdata == &ohci_platform_defaults) dev->dev.platform_data = NULL; - - return 0; } #ifdef CONFIG_PM_SLEEP @@ -306,7 +304,7 @@ MODULE_DEVICE_TABLE(of, st_ohci_platform_ids); static struct platform_driver ohci_platform_driver = { .probe = st_ohci_platform_probe, - .remove = st_ohci_platform_remove, + .remove_new = st_ohci_platform_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "st-ohci", diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c index f998d3f1a78a..50c1ccabb0f5 100644 --- a/drivers/usb/host/oxu210hp-hcd.c +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -4278,14 +4278,12 @@ static void oxu_remove(struct platform_device *pdev, struct usb_hcd *hcd) usb_put_hcd(hcd); } -static int oxu_drv_remove(struct platform_device *pdev) +static void oxu_drv_remove(struct platform_device *pdev) { struct oxu_info *info = platform_get_drvdata(pdev); oxu_remove(pdev, info->hcd[0]); oxu_remove(pdev, info->hcd[1]); - - return 0; } static void oxu_drv_shutdown(struct platform_device *pdev) @@ -4317,7 +4315,7 @@ static int oxu_drv_resume(struct device *dev) static struct platform_driver oxu_driver = { .probe = oxu_drv_probe, - .remove = oxu_drv_remove, + .remove_new = oxu_drv_remove, .shutdown = oxu_drv_shutdown, .suspend = oxu_drv_suspend, .resume = oxu_drv_resume, diff --git a/drivers/usb/host/r8a66597-hcd.c b/drivers/usb/host/r8a66597-hcd.c index abb88dd40d4e..9f4bf8c5f8a5 100644 --- a/drivers/usb/host/r8a66597-hcd.c +++ b/drivers/usb/host/r8a66597-hcd.c @@ -2379,7 +2379,7 @@ static const struct dev_pm_ops r8a66597_dev_pm_ops = { #define R8A66597_DEV_PM_OPS NULL #endif -static int r8a66597_remove(struct platform_device *pdev) +static void r8a66597_remove(struct platform_device *pdev) { struct r8a66597 *r8a66597 = platform_get_drvdata(pdev); struct usb_hcd *hcd = r8a66597_to_hcd(r8a66597); @@ -2390,7 +2390,6 @@ static int r8a66597_remove(struct platform_device *pdev) if (r8a66597->pdata->on_chip) clk_put(r8a66597->clk); usb_put_hcd(hcd); - return 0; } static int r8a66597_probe(struct platform_device *pdev) @@ -2511,7 +2510,7 @@ clean_up: static struct platform_driver r8a66597_driver = { .probe = r8a66597_probe, - .remove = r8a66597_remove, + .remove_new = r8a66597_remove, .driver = { .name = hcd_name, .pm = R8A66597_DEV_PM_OPS, diff --git a/drivers/usb/host/sl811-hcd.c b/drivers/usb/host/sl811-hcd.c index b8b90eec9107..0956495bba57 100644 --- a/drivers/usb/host/sl811-hcd.c +++ b/drivers/usb/host/sl811-hcd.c @@ -1579,7 +1579,7 @@ static const struct hc_driver sl811h_hc_driver = { /*-------------------------------------------------------------------------*/ -static int +static void sl811h_remove(struct platform_device *dev) { struct usb_hcd *hcd = platform_get_drvdata(dev); @@ -1599,7 +1599,6 @@ sl811h_remove(struct platform_device *dev) iounmap(sl811->addr_reg); usb_put_hcd(hcd); - return 0; } static int @@ -1783,7 +1782,7 @@ sl811h_resume(struct platform_device *dev) /* this driver is exported so sl811_cs can depend on it */ struct platform_driver sl811h_driver = { .probe = sl811h_probe, - .remove = sl811h_remove, + .remove_new = sl811h_remove, .suspend = sl811h_suspend, .resume = sl811h_resume, diff --git a/drivers/usb/host/uhci-grlib.c b/drivers/usb/host/uhci-grlib.c index 907d5f01edfd..ac3fc5970315 100644 --- a/drivers/usb/host/uhci-grlib.c +++ b/drivers/usb/host/uhci-grlib.c @@ -147,7 +147,7 @@ err_usb: return rv; } -static int uhci_hcd_grlib_remove(struct platform_device *op) +static void uhci_hcd_grlib_remove(struct platform_device *op) { struct usb_hcd *hcd = platform_get_drvdata(op); @@ -157,8 +157,6 @@ static int uhci_hcd_grlib_remove(struct platform_device *op) irq_dispose_mapping(hcd->irq); usb_put_hcd(hcd); - - return 0; } /* Make sure the controller is quiescent and that we're not using it @@ -185,7 +183,7 @@ MODULE_DEVICE_TABLE(of, uhci_hcd_grlib_of_match); static struct platform_driver uhci_grlib_driver = { .probe = uhci_hcd_grlib_probe, - .remove = uhci_hcd_grlib_remove, + .remove_new = uhci_hcd_grlib_remove, .shutdown = uhci_hcd_grlib_shutdown, .driver = { .name = "grlib-uhci", diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c index 7cdc2fa7c28f..fd2408b553cf 100644 --- a/drivers/usb/host/uhci-hcd.c +++ b/drivers/usb/host/uhci-hcd.c @@ -841,7 +841,7 @@ static int uhci_count_ports(struct usb_hcd *hcd) static const char hcd_name[] = "uhci_hcd"; -#ifdef CONFIG_USB_PCI +#if defined(CONFIG_USB_PCI) && defined(CONFIG_HAS_IOPORT) #include "uhci-pci.c" #define PCI_DRIVER uhci_pci_driver #endif diff --git a/drivers/usb/host/uhci-hcd.h b/drivers/usb/host/uhci-hcd.h index 0688c3e5bfe2..13ee2a6144b2 100644 --- a/drivers/usb/host/uhci-hcd.h +++ b/drivers/usb/host/uhci-hcd.h @@ -505,6 +505,14 @@ static inline bool uhci_is_aspeed(const struct uhci_hcd *uhci) * we use memory mapped registers. */ +#ifdef CONFIG_HAS_IOPORT +#define UHCI_IN(x) x +#define UHCI_OUT(x) x +#else +#define UHCI_IN(x) 0 +#define UHCI_OUT(x) do { } while (0) +#endif + #ifndef CONFIG_USB_UHCI_SUPPORT_NON_PCI_HC /* Support PCI only */ static inline u32 uhci_readl(const struct uhci_hcd *uhci, int reg) @@ -539,7 +547,7 @@ static inline void uhci_writeb(const struct uhci_hcd *uhci, u8 val, int reg) #else /* Support non-PCI host controllers */ -#ifdef CONFIG_USB_PCI +#if defined(CONFIG_USB_PCI) && defined(HAS_IOPORT) /* Support PCI and non-PCI host controllers */ #define uhci_has_pci_registers(u) ((u)->io_addr != 0) #else @@ -587,7 +595,7 @@ static inline int uhci_aspeed_reg(unsigned int reg) static inline u32 uhci_readl(const struct uhci_hcd *uhci, int reg) { if (uhci_has_pci_registers(uhci)) - return inl(uhci->io_addr + reg); + return UHCI_IN(inl(uhci->io_addr + reg)); else if (uhci_is_aspeed(uhci)) return readl(uhci->regs + uhci_aspeed_reg(reg)); #ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO @@ -601,7 +609,7 @@ static inline u32 uhci_readl(const struct uhci_hcd *uhci, int reg) static inline void uhci_writel(const struct uhci_hcd *uhci, u32 val, int reg) { if (uhci_has_pci_registers(uhci)) - outl(val, uhci->io_addr + reg); + UHCI_OUT(outl(val, uhci->io_addr + reg)); else if (uhci_is_aspeed(uhci)) writel(val, uhci->regs + uhci_aspeed_reg(reg)); #ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO @@ -615,7 +623,7 @@ static inline void uhci_writel(const struct uhci_hcd *uhci, u32 val, int reg) static inline u16 uhci_readw(const struct uhci_hcd *uhci, int reg) { if (uhci_has_pci_registers(uhci)) - return inw(uhci->io_addr + reg); + return UHCI_IN(inw(uhci->io_addr + reg)); else if (uhci_is_aspeed(uhci)) return readl(uhci->regs + uhci_aspeed_reg(reg)); #ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO @@ -629,7 +637,7 @@ static inline u16 uhci_readw(const struct uhci_hcd *uhci, int reg) static inline void uhci_writew(const struct uhci_hcd *uhci, u16 val, int reg) { if (uhci_has_pci_registers(uhci)) - outw(val, uhci->io_addr + reg); + UHCI_OUT(outw(val, uhci->io_addr + reg)); else if (uhci_is_aspeed(uhci)) writel(val, uhci->regs + uhci_aspeed_reg(reg)); #ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO @@ -643,7 +651,7 @@ static inline void uhci_writew(const struct uhci_hcd *uhci, u16 val, int reg) static inline u8 uhci_readb(const struct uhci_hcd *uhci, int reg) { if (uhci_has_pci_registers(uhci)) - return inb(uhci->io_addr + reg); + return UHCI_IN(inb(uhci->io_addr + reg)); else if (uhci_is_aspeed(uhci)) return readl(uhci->regs + uhci_aspeed_reg(reg)); #ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO @@ -657,7 +665,7 @@ static inline u8 uhci_readb(const struct uhci_hcd *uhci, int reg) static inline void uhci_writeb(const struct uhci_hcd *uhci, u8 val, int reg) { if (uhci_has_pci_registers(uhci)) - outb(val, uhci->io_addr + reg); + UHCI_OUT(outb(val, uhci->io_addr + reg)); else if (uhci_is_aspeed(uhci)) writel(val, uhci->regs + uhci_aspeed_reg(reg)); #ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO @@ -668,6 +676,8 @@ static inline void uhci_writeb(const struct uhci_hcd *uhci, u8 val, int reg) writeb(val, uhci->regs + reg); } #endif /* CONFIG_USB_UHCI_SUPPORT_NON_PCI_HC */ +#undef UHCI_IN +#undef UHCI_OUT /* * The GRLIB GRUSBHC controller can use big endian format for its descriptors. diff --git a/drivers/usb/host/uhci-pci.c b/drivers/usb/host/uhci-pci.c index 7bd2fddde770..5edf6a08cf82 100644 --- a/drivers/usb/host/uhci-pci.c +++ b/drivers/usb/host/uhci-pci.c @@ -169,7 +169,7 @@ static void uhci_shutdown(struct pci_dev *pdev) #ifdef CONFIG_PM -static int uhci_pci_resume(struct usb_hcd *hcd, bool hibernated); +static int uhci_pci_resume(struct usb_hcd *hcd, pm_message_t state); static int uhci_pci_suspend(struct usb_hcd *hcd, bool do_wakeup) { @@ -204,14 +204,15 @@ done_okay: /* Check for race with a wakeup request */ if (do_wakeup && HCD_WAKEUP_PENDING(hcd)) { - uhci_pci_resume(hcd, false); + uhci_pci_resume(hcd, PMSG_SUSPEND); rc = -EBUSY; } return rc; } -static int uhci_pci_resume(struct usb_hcd *hcd, bool hibernated) +static int uhci_pci_resume(struct usb_hcd *hcd, pm_message_t msg) { + bool hibernated = (msg.event == PM_EVENT_RESTORE); struct uhci_hcd *uhci = hcd_to_uhci(hcd); dev_dbg(uhci_dev(uhci), "%s\n", __func__); diff --git a/drivers/usb/host/uhci-platform.c b/drivers/usb/host/uhci-platform.c index b2049b47a08d..71ca532fc086 100644 --- a/drivers/usb/host/uhci-platform.c +++ b/drivers/usb/host/uhci-platform.c @@ -152,7 +152,7 @@ err_rmr: return ret; } -static int uhci_hcd_platform_remove(struct platform_device *pdev) +static void uhci_hcd_platform_remove(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); struct uhci_hcd *uhci = hcd_to_uhci(hcd); @@ -160,8 +160,6 @@ static int uhci_hcd_platform_remove(struct platform_device *pdev) clk_disable_unprepare(uhci->clk); usb_remove_hcd(hcd); usb_put_hcd(hcd); - - return 0; } /* Make sure the controller is quiescent and that we're not using it @@ -187,7 +185,7 @@ MODULE_DEVICE_TABLE(of, platform_uhci_ids); static struct platform_driver uhci_platform_driver = { .probe = uhci_hcd_platform_probe, - .remove = uhci_hcd_platform_remove, + .remove_new = uhci_hcd_platform_remove, .shutdown = uhci_hcd_platform_shutdown, .driver = { .name = "platform-uhci", diff --git a/drivers/usb/host/xhci-histb.c b/drivers/usb/host/xhci-histb.c index 08369857686e..f9a4a4b0eb57 100644 --- a/drivers/usb/host/xhci-histb.c +++ b/drivers/usb/host/xhci-histb.c @@ -164,16 +164,6 @@ static void xhci_histb_host_disable(struct xhci_hcd_histb *histb) clk_disable_unprepare(histb->bus_clk); } -static void xhci_histb_quirks(struct device *dev, struct xhci_hcd *xhci) -{ - /* - * As of now platform drivers don't provide MSI support so we ensure - * here that the generic code does not try to make a pci_dev from our - * dev struct in order to setup MSI - */ - xhci->quirks |= XHCI_PLAT; -} - /* called during probe() after chip reset completes */ static int xhci_histb_setup(struct usb_hcd *hcd) { @@ -186,7 +176,7 @@ static int xhci_histb_setup(struct usb_hcd *hcd) return ret; } - return xhci_gen_setup(hcd, xhci_histb_quirks); + return xhci_gen_setup(hcd, NULL); } static const struct xhci_driver_overrides xhci_histb_overrides __initconst = { @@ -319,7 +309,7 @@ disable_pm: return ret; } -static int xhci_histb_remove(struct platform_device *dev) +static void xhci_histb_remove(struct platform_device *dev) { struct xhci_hcd_histb *histb = platform_get_drvdata(dev); struct usb_hcd *hcd = histb->hcd; @@ -339,8 +329,6 @@ static int xhci_histb_remove(struct platform_device *dev) usb_put_hcd(hcd); pm_runtime_put_sync(&dev->dev); pm_runtime_disable(&dev->dev); - - return 0; } static int __maybe_unused xhci_histb_suspend(struct device *dev) @@ -367,7 +355,7 @@ static int __maybe_unused xhci_histb_resume(struct device *dev) if (!device_may_wakeup(dev)) xhci_histb_host_enable(histb); - return xhci_resume(xhci, 0); + return xhci_resume(xhci, PMSG_RESUME); } static const struct dev_pm_ops xhci_histb_pm_ops = { @@ -385,7 +373,7 @@ MODULE_DEVICE_TABLE(of, histb_xhci_of_match); static struct platform_driver histb_xhci_driver = { .probe = xhci_histb_probe, - .remove = xhci_histb_remove, + .remove_new = xhci_histb_remove, .driver = { .name = "xhci-histb", .pm = DEV_PM_OPS, diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 7e106bd804ca..19a402123de0 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -143,7 +143,6 @@ static void xhci_link_rings(struct xhci_hcd *xhci, struct xhci_ring *ring, xhci_link_segments(ring->enq_seg, first, ring->type, chain_links); xhci_link_segments(last, next, ring->type, chain_links); ring->num_segs += num_segs; - ring->num_trbs_free += (TRBS_PER_SEGMENT - 1) * num_segs; if (ring->type != TYPE_EVENT && ring->enq_seg == ring->last_seg) { ring->last_seg->trbs[TRBS_PER_SEGMENT-1].link.control @@ -422,22 +421,14 @@ void xhci_free_endpoint_ring(struct xhci_hcd *xhci, * Allocate a new ring which has same segment numbers and link the two rings. */ int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring, - unsigned int num_trbs, gfp_t flags) + unsigned int num_new_segs, gfp_t flags) { struct xhci_segment *first; struct xhci_segment *last; - unsigned int num_segs; - unsigned int num_segs_needed; int ret; - num_segs_needed = (num_trbs + (TRBS_PER_SEGMENT - 1) - 1) / - (TRBS_PER_SEGMENT - 1); - - /* Allocate number of segments we needed, or double the ring size */ - num_segs = max(ring->num_segs, num_segs_needed); - ret = xhci_alloc_segments_for_ring(xhci, &first, &last, - num_segs, ring->cycle_state, ring->type, + num_new_segs, ring->cycle_state, ring->type, ring->bounce_buf_len, flags); if (ret) return -ENOMEM; @@ -457,7 +448,7 @@ int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring, return ret; } - xhci_link_rings(xhci, ring, first, last, num_segs); + xhci_link_rings(xhci, ring, first, last, num_new_segs); trace_xhci_ring_expansion(ring); xhci_dbg_trace(xhci, trace_xhci_dbg_ring_expansion, "ring expansion succeed, now has %d segments", @@ -1831,13 +1822,15 @@ xhci_free_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir) * low or high 32 bits of ERSTBA immediately causes the controller to * dereference the partially cleared 64 bit address, causing IOMMU error. */ - tmp = readl(&ir->ir_set->erst_size); - 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); + if (ir->ir_set) { + tmp = readl(&ir->ir_set->erst_size); + 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); + } /* free interrrupter event ring */ if (ir->event_ring) @@ -1968,7 +1961,7 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, { u32 temp, port_offset, port_count; int i; - u8 major_revision, minor_revision; + u8 major_revision, minor_revision, tmp_minor_revision; struct xhci_hub *rhub; struct device *dev = xhci_to_hcd(xhci)->self.sysdev; struct xhci_port_cap *port_cap; @@ -1988,6 +1981,15 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, */ if (minor_revision > 0x00 && minor_revision < 0x10) minor_revision <<= 4; + /* + * Some zhaoxin's xHCI controller that follow usb3.1 spec + * but only support Gen1. + */ + if (xhci->quirks & XHCI_ZHAOXIN_HOST) { + tmp_minor_revision = minor_revision; + minor_revision = 0; + } + } else if (major_revision <= 0x02) { rhub = &xhci->usb2_rhub; } else { @@ -1996,10 +1998,6 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, /* Ignoring port protocol we can't understand. FIXME */ return; } - rhub->maj_rev = XHCI_EXT_PORT_MAJOR(temp); - - if (rhub->min_rev < minor_revision) - rhub->min_rev = minor_revision; /* Port offset and count in the third dword, see section 7.2 */ temp = readl(addr + 2); @@ -2017,8 +2015,6 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, if (xhci->num_port_caps > max_caps) return; - port_cap->maj_rev = major_revision; - port_cap->min_rev = minor_revision; port_cap->psi_count = XHCI_EXT_PORT_PSIC(temp); if (port_cap->psi_count) { @@ -2039,6 +2035,11 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, XHCI_EXT_PORT_PSIV(port_cap->psi[i - 1]))) port_cap->psi_uid_count++; + if (xhci->quirks & XHCI_ZHAOXIN_HOST && + major_revision == 0x03 && + XHCI_EXT_PORT_PSIV(port_cap->psi[i]) >= 5) + minor_revision = tmp_minor_revision; + xhci_dbg(xhci, "PSIV:%d PSIE:%d PLT:%d PFD:%d LP:%d PSIM:%d\n", XHCI_EXT_PORT_PSIV(port_cap->psi[i]), XHCI_EXT_PORT_PSIE(port_cap->psi[i]), @@ -2048,6 +2049,15 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, XHCI_EXT_PORT_PSIM(port_cap->psi[i])); } } + + rhub->maj_rev = major_revision; + + if (rhub->min_rev < minor_revision) + rhub->min_rev = minor_revision; + + port_cap->maj_rev = major_revision; + port_cap->min_rev = minor_revision; + /* cache usb2 port capabilities */ if (major_revision < 0x03 && xhci->num_ext_caps < max_caps) xhci->ext_caps[xhci->num_ext_caps++] = temp; @@ -2227,43 +2237,50 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags) } static struct xhci_interrupter * -xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int intr_num, gfp_t flags) +xhci_alloc_interrupter(struct xhci_hcd *xhci, gfp_t flags) { struct device *dev = xhci_to_hcd(xhci)->self.sysdev; struct xhci_interrupter *ir; - u64 erst_base; - u32 erst_size; int ret; - if (intr_num > xhci->max_interrupters) { - xhci_warn(xhci, "Can't allocate interrupter %d, max interrupters %d\n", - intr_num, xhci->max_interrupters); - return NULL; - } - - if (xhci->interrupter) { - xhci_warn(xhci, "Can't allocate already set up interrupter %d\n", intr_num); - return NULL; - } - ir = kzalloc_node(sizeof(*ir), flags, dev_to_node(dev)); if (!ir) return NULL; - ir->ir_set = &xhci->run_regs->ir_set[intr_num]; ir->event_ring = xhci_ring_alloc(xhci, ERST_NUM_SEGS, 1, TYPE_EVENT, 0, flags); if (!ir->event_ring) { - xhci_warn(xhci, "Failed to allocate interrupter %d event ring\n", intr_num); - goto fail_ir; + xhci_warn(xhci, "Failed to allocate interrupter event ring\n"); + kfree(ir); + return NULL; } ret = xhci_alloc_erst(xhci, ir->event_ring, &ir->erst, flags); if (ret) { - xhci_warn(xhci, "Failed to allocate interrupter %d erst\n", intr_num); - goto fail_ev; + xhci_warn(xhci, "Failed to allocate interrupter erst\n"); + xhci_ring_free(xhci, ir->event_ring); + kfree(ir); + return NULL; + } + return ir; +} + +static int +xhci_add_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir, + unsigned int intr_num) +{ + u64 erst_base; + u32 erst_size; + + if (intr_num > xhci->max_interrupters) { + xhci_warn(xhci, "Can't add interrupter %d, max interrupters %d\n", + intr_num, xhci->max_interrupters); + return -EINVAL; } + + ir->ir_set = &xhci->run_regs->ir_set[intr_num]; + /* 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; @@ -2278,14 +2295,7 @@ xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int intr_num, gfp_t flags /* Set the event ring dequeue address of this interrupter */ xhci_set_hc_event_deq(xhci, ir); - return ir; - -fail_ev: - xhci_ring_free(xhci, ir->event_ring); -fail_ir: - kfree(ir); - - return NULL; + return 0; } int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) @@ -2352,8 +2362,12 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) * and our use of dma addresses in the trb_address_map radix tree needs * TRB_SEGMENT_SIZE alignment, so we pick the greater alignment need. */ - xhci->segment_pool = dma_pool_create("xHCI ring segments", dev, - TRB_SEGMENT_SIZE, TRB_SEGMENT_SIZE, xhci->page_size); + if (xhci->quirks & XHCI_ZHAOXIN_TRB_FETCH) + xhci->segment_pool = dma_pool_create("xHCI ring segments", dev, + TRB_SEGMENT_SIZE * 2, TRB_SEGMENT_SIZE * 2, xhci->page_size * 2); + else + xhci->segment_pool = dma_pool_create("xHCI ring segments", dev, + TRB_SEGMENT_SIZE, TRB_SEGMENT_SIZE, xhci->page_size); /* See Table 46 and Note on Figure 55 */ xhci->device_pool = dma_pool_create("xHCI input/output contexts", dev, @@ -2407,15 +2421,17 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) "// Doorbell array is located at offset 0x%x from cap regs base addr", val); xhci->dba = (void __iomem *) xhci->cap_regs + val; - /* Set ir_set to interrupt register set 0 */ - /* allocate and set up primary interrupter with an event ring. */ + /* Allocate and set up primary interrupter 0 with an event ring. */ xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Allocating primary event ring"); - xhci->interrupter = xhci_alloc_interrupter(xhci, 0, flags); + xhci->interrupter = xhci_alloc_interrupter(xhci, flags); if (!xhci->interrupter) goto fail; + if (xhci_add_interrupter(xhci, xhci->interrupter, 0)) + goto fail; + xhci->isoc_bei_interval = AVOID_BEI_INTERVAL_MAX; /* diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c index 90cf40d6d0c3..51d9d4d4f6a5 100644 --- a/drivers/usb/host/xhci-mtk.c +++ b/drivers/usb/host/xhci-mtk.c @@ -418,12 +418,6 @@ static void xhci_mtk_quirks(struct device *dev, struct xhci_hcd *xhci) struct usb_hcd *hcd = xhci_to_hcd(xhci); struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd); - /* - * As of now platform drivers don't provide MSI support so we ensure - * here that the generic code does not try to make a pci_dev from our - * dev struct in order to setup MSI - */ - xhci->quirks |= XHCI_PLAT; xhci->quirks |= XHCI_MTK_HOST; /* * MTK host controller gives a spurious successful event after a @@ -673,7 +667,7 @@ disable_pm: return ret; } -static int xhci_mtk_remove(struct platform_device *pdev) +static void xhci_mtk_remove(struct platform_device *pdev) { struct xhci_hcd_mtk *mtk = platform_get_drvdata(pdev); struct usb_hcd *hcd = mtk->hcd; @@ -703,8 +697,6 @@ static int xhci_mtk_remove(struct platform_device *pdev) pm_runtime_disable(dev); pm_runtime_put_noidle(dev); pm_runtime_set_suspended(dev); - - return 0; } static int __maybe_unused xhci_mtk_suspend(struct device *dev) @@ -824,7 +816,7 @@ MODULE_DEVICE_TABLE(of, mtk_xhci_of_match); static struct platform_driver mtk_xhci_driver = { .probe = xhci_mtk_probe, - .remove = xhci_mtk_remove, + .remove_new = xhci_mtk_remove, .driver = { .name = "xhci-mtk", .pm = DEV_PM_OPS, diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 79b3691f373f..c6742bae41c0 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -108,9 +108,6 @@ static void xhci_cleanup_msix(struct xhci_hcd *xhci) struct usb_hcd *hcd = xhci_to_hcd(xhci); struct pci_dev *pdev = to_pci_dev(hcd->self.controller); - if (xhci->quirks & XHCI_PLAT) - return; - /* return if using legacy interrupt */ if (hcd->irq > 0) return; @@ -208,10 +205,6 @@ static int xhci_try_enable_msi(struct usb_hcd *hcd) struct pci_dev *pdev; int ret; - /* The xhci platform device has set up IRQs through usb_add_hcd. */ - if (xhci->quirks & XHCI_PLAT) - return 0; - pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); /* * Some Fresco Logic host controllers advertise MSI, but fail to @@ -528,6 +521,19 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) pdev->device == PCI_DEVICE_ID_AMD_PROMONTORYA_4)) xhci->quirks |= XHCI_NO_SOFT_RETRY; + if (pdev->vendor == PCI_VENDOR_ID_ZHAOXIN) { + xhci->quirks |= XHCI_ZHAOXIN_HOST; + xhci->quirks |= XHCI_LPM_SUPPORT; + + if (pdev->device == 0x9202) { + xhci->quirks |= XHCI_RESET_ON_RESUME; + xhci->quirks |= XHCI_ZHAOXIN_TRB_FETCH; + } + + if (pdev->device == 0x9203) + xhci->quirks |= XHCI_ZHAOXIN_TRB_FETCH; + } + /* xHC spec requires PCI devices to support D3hot and D3cold */ if (xhci->hci_version >= 0x120) xhci->quirks |= XHCI_DEFAULT_PM_RUNTIME_ALLOW; @@ -832,7 +838,7 @@ static int xhci_pci_suspend(struct usb_hcd *hcd, bool do_wakeup) return ret; } -static int xhci_pci_resume(struct usb_hcd *hcd, bool hibernated) +static int xhci_pci_resume(struct usb_hcd *hcd, pm_message_t msg) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); struct pci_dev *pdev = to_pci_dev(hcd->self.controller); @@ -867,7 +873,7 @@ static int xhci_pci_resume(struct usb_hcd *hcd, bool hibernated) if (xhci->quirks & XHCI_PME_STUCK_QUIRK) xhci_pme_quirk(hcd); - retval = xhci_resume(xhci, hibernated); + retval = xhci_resume(xhci, msg); return retval; } diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index b0c8e8efc43b..b26ea7cb4357 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -78,12 +78,7 @@ static void xhci_plat_quirks(struct device *dev, struct xhci_hcd *xhci) { struct xhci_plat_priv *priv = xhci_to_priv(xhci); - /* - * As of now platform drivers don't provide MSI support so we ensure - * here that the generic code does not try to make a pci_dev from our - * dev struct in order to setup MSI - */ - xhci->quirks |= XHCI_PLAT | priv->quirks; + xhci->quirks |= priv->quirks; } /* called during probe() after chip reset completes */ @@ -294,10 +289,6 @@ int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, const s xhci->shared_hcd->usb_phy = devm_usb_get_phy_by_phandle(sysdev, "usb-phy", 1); if (IS_ERR(xhci->shared_hcd->usb_phy)) { - if (PTR_ERR(xhci->shared_hcd->usb_phy) != -ENODEV) - dev_err(sysdev, "%s get usb3phy fail (ret=%d)\n", - __func__, - (int)PTR_ERR(xhci->shared_hcd->usb_phy)); xhci->shared_hcd->usb_phy = NULL; } else { ret = usb_phy_init(xhci->shared_hcd->usb_phy); @@ -399,7 +390,7 @@ static int xhci_generic_plat_probe(struct platform_device *pdev) return xhci_plat_probe(pdev, sysdev, priv_match); } -int xhci_plat_remove(struct platform_device *dev) +void xhci_plat_remove(struct platform_device *dev) { struct usb_hcd *hcd = platform_get_drvdata(dev); struct xhci_hcd *xhci = hcd_to_xhci(hcd); @@ -407,8 +398,8 @@ int xhci_plat_remove(struct platform_device *dev) struct clk *reg_clk = xhci->reg_clk; struct usb_hcd *shared_hcd = xhci->shared_hcd; - pm_runtime_get_sync(&dev->dev); xhci->xhc_state |= XHCI_STATE_REMOVING; + pm_runtime_get_sync(&dev->dev); if (shared_hcd) { usb_remove_hcd(shared_hcd); @@ -430,8 +421,6 @@ int xhci_plat_remove(struct platform_device *dev) pm_runtime_disable(&dev->dev); pm_runtime_put_noidle(&dev->dev); pm_runtime_set_suspended(&dev->dev); - - return 0; } EXPORT_SYMBOL_GPL(xhci_plat_remove); @@ -478,7 +467,7 @@ static int __maybe_unused xhci_plat_resume(struct device *dev) if (ret) return ret; - ret = xhci_resume(xhci, 0); + ret = xhci_resume(xhci, PMSG_RESUME); if (ret) return ret; @@ -507,7 +496,7 @@ static int __maybe_unused xhci_plat_runtime_resume(struct device *dev) struct usb_hcd *hcd = dev_get_drvdata(dev); struct xhci_hcd *xhci = hcd_to_xhci(hcd); - return xhci_resume(xhci, 0); + return xhci_resume(xhci, PMSG_AUTO_RESUME); } const struct dev_pm_ops xhci_plat_pm_ops = { @@ -530,7 +519,7 @@ MODULE_DEVICE_TABLE(acpi, usb_xhci_acpi_match); static struct platform_driver usb_generic_xhci_driver = { .probe = xhci_generic_plat_probe, - .remove = xhci_plat_remove, + .remove_new = xhci_plat_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "xhci-hcd", diff --git a/drivers/usb/host/xhci-plat.h b/drivers/usb/host/xhci-plat.h index 83b5b5aa9f8e..2d15386f2c50 100644 --- a/drivers/usb/host/xhci-plat.h +++ b/drivers/usb/host/xhci-plat.h @@ -25,7 +25,7 @@ struct xhci_plat_priv { int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, const struct xhci_plat_priv *priv_match); -int xhci_plat_remove(struct platform_device *dev); +void xhci_plat_remove(struct platform_device *dev); extern const struct dev_pm_ops xhci_plat_pm_ops; #endif /* _XHCI_PLAT_H */ diff --git a/drivers/usb/host/xhci-rcar.c b/drivers/usb/host/xhci-rcar.c index ad966b797b89..bf5261fed32c 100644 --- a/drivers/usb/host/xhci-rcar.c +++ b/drivers/usb/host/xhci-rcar.c @@ -276,10 +276,10 @@ static int xhci_renesas_probe(struct platform_device *pdev) } static struct platform_driver usb_xhci_renesas_driver = { - .probe = xhci_renesas_probe, - .remove = xhci_plat_remove, + .probe = xhci_renesas_probe, + .remove_new = xhci_plat_remove, .shutdown = usb_hcd_platform_shutdown, - .driver = { + .driver = { .name = "xhci-renesas-hcd", .pm = &xhci_plat_pm_ops, .of_match_table = usb_xhci_of_match, diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 2bc82b3a2f98..646ff125def5 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -174,12 +174,10 @@ void inc_deq(struct xhci_hcd *xhci, struct xhci_ring *ring) /* All other rings have link trbs */ if (!trb_is_link(ring->dequeue)) { - if (last_trb_on_seg(ring->deq_seg, ring->dequeue)) { + if (last_trb_on_seg(ring->deq_seg, ring->dequeue)) xhci_warn(xhci, "Missing link TRB at end of segment\n"); - } else { + else ring->dequeue++; - ring->num_trbs_free++; - } } while (trb_is_link(ring->dequeue)) { @@ -221,9 +219,6 @@ static void inc_enq(struct xhci_hcd *xhci, struct xhci_ring *ring, unsigned int link_trb_count = 0; chain = le32_to_cpu(ring->enqueue->generic.field[3]) & TRB_CHAIN; - /* If this is not event ring, there is one less usable TRB */ - if (!trb_is_link(ring->enqueue)) - ring->num_trbs_free--; if (last_trb_on_seg(ring->enq_seg, ring->enqueue)) { xhci_err(xhci, "Tried to move enqueue past ring segment\n"); @@ -276,45 +271,84 @@ static void inc_enq(struct xhci_hcd *xhci, struct xhci_ring *ring, trace_xhci_inc_enq(ring); } -static int xhci_num_trbs_to(struct xhci_segment *start_seg, union xhci_trb *start, - struct xhci_segment *end_seg, union xhci_trb *end, - unsigned int num_segs) +/* + * Return number of free normal TRBs from enqueue to dequeue pointer on ring. + * Not counting an assumed link TRB at end of each TRBS_PER_SEGMENT sized segment. + * Only for transfer and command rings where driver is the producer, not for + * event rings. + */ +static unsigned int xhci_num_trbs_free(struct xhci_hcd *xhci, struct xhci_ring *ring) { + struct xhci_segment *enq_seg = ring->enq_seg; + union xhci_trb *enq = ring->enqueue; union xhci_trb *last_on_seg; - int num = 0; + unsigned int free = 0; int i = 0; + /* Ring might be empty even if enq != deq if enq is left on a link trb */ + if (trb_is_link(enq)) { + enq_seg = enq_seg->next; + enq = enq_seg->trbs; + } + + /* Empty ring, common case, don't walk the segments */ + if (enq == ring->dequeue) + return ring->num_segs * (TRBS_PER_SEGMENT - 1); + do { - if (start_seg == end_seg && end >= start) - return num + (end - start); - last_on_seg = &start_seg->trbs[TRBS_PER_SEGMENT - 1]; - num += last_on_seg - start; - start_seg = start_seg->next; - start = start_seg->trbs; - } while (i++ <= num_segs); - - return -EINVAL; + if (ring->deq_seg == enq_seg && ring->dequeue >= enq) + return free + (ring->dequeue - enq); + last_on_seg = &enq_seg->trbs[TRBS_PER_SEGMENT - 1]; + free += last_on_seg - enq; + enq_seg = enq_seg->next; + enq = enq_seg->trbs; + } while (i++ <= ring->num_segs); + + return free; } /* * Check to see if there's room to enqueue num_trbs on the ring and make sure * enqueue pointer will not advance into dequeue segment. See rules above. + * return number of new segments needed to ensure this. */ -static inline int room_on_ring(struct xhci_hcd *xhci, struct xhci_ring *ring, - unsigned int num_trbs) + +static unsigned int xhci_ring_expansion_needed(struct xhci_hcd *xhci, struct xhci_ring *ring, + unsigned int num_trbs) { - int num_trbs_in_deq_seg; + struct xhci_segment *seg; + int trbs_past_seg; + int enq_used; + int new_segs; + + enq_used = ring->enqueue - ring->enq_seg->trbs; + + /* how many trbs will be queued past the enqueue segment? */ + trbs_past_seg = enq_used + num_trbs - (TRBS_PER_SEGMENT - 1); - if (ring->num_trbs_free < num_trbs) + if (trbs_past_seg <= 0) return 0; - if (ring->type != TYPE_COMMAND && ring->type != TYPE_EVENT) { - num_trbs_in_deq_seg = ring->dequeue - ring->deq_seg->trbs; - if (ring->num_trbs_free < num_trbs + num_trbs_in_deq_seg) - return 0; + /* Empty ring special case, enqueue stuck on link trb while dequeue advanced */ + if (trb_is_link(ring->enqueue) && ring->enq_seg->next->trbs == ring->dequeue) + return 0; + + new_segs = 1 + (trbs_past_seg / (TRBS_PER_SEGMENT - 1)); + seg = ring->enq_seg; + + while (new_segs > 0) { + seg = seg->next; + if (seg == ring->deq_seg) { + xhci_dbg(xhci, "Ring expansion by %d segments needed\n", + new_segs); + xhci_dbg(xhci, "Adding %d trbs moves enq %d trbs into deq seg\n", + num_trbs, trbs_past_seg % TRBS_PER_SEGMENT); + return new_segs; + } + new_segs--; } - return 1; + return 0; } /* Ring the host controller doorbell after placing a command on the ring */ @@ -1268,10 +1302,7 @@ static void update_ring_for_set_deq_completion(struct xhci_hcd *xhci, unsigned int ep_index) { union xhci_trb *dequeue_temp; - int num_trbs_free_temp; - bool revert = false; - num_trbs_free_temp = ep_ring->num_trbs_free; dequeue_temp = ep_ring->dequeue; /* If we get two back-to-back stalls, and the first stalled transfer @@ -1287,7 +1318,6 @@ static void update_ring_for_set_deq_completion(struct xhci_hcd *xhci, while (ep_ring->dequeue != dev->eps[ep_index].queued_deq_ptr) { /* We have more usable TRBs */ - ep_ring->num_trbs_free++; ep_ring->dequeue++; if (trb_is_link(ep_ring->dequeue)) { if (ep_ring->dequeue == @@ -1297,15 +1327,10 @@ static void update_ring_for_set_deq_completion(struct xhci_hcd *xhci, ep_ring->dequeue = ep_ring->deq_seg->trbs; } if (ep_ring->dequeue == dequeue_temp) { - revert = true; + xhci_dbg(xhci, "Unable to find new dequeue pointer\n"); break; } } - - if (revert) { - xhci_dbg(xhci, "Unable to find new dequeue pointer\n"); - ep_ring->num_trbs_free = num_trbs_free_temp; - } } /* @@ -2160,7 +2185,6 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, u32 trb_comp_code) { struct xhci_ep_ctx *ep_ctx; - int trbs_freed; ep_ctx = xhci_get_ep_ctx(xhci, ep->vdev->out_ctx, ep->ep_index); @@ -2230,13 +2254,6 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, } /* Update ring dequeue pointer */ - trbs_freed = xhci_num_trbs_to(ep_ring->deq_seg, ep_ring->dequeue, - td->last_trb_seg, td->last_trb, - ep_ring->num_segs); - if (trbs_freed < 0) - xhci_dbg(xhci, "Failed to count freed trbs at TD finish\n"); - else - ep_ring->num_trbs_free += trbs_freed; ep_ring->dequeue = td->last_trb; ep_ring->deq_seg = td->last_trb_seg; inc_deq(xhci, ep_ring); @@ -2460,7 +2477,6 @@ static int skip_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, /* Update ring dequeue pointer */ ep->ring->dequeue = td->last_trb; ep->ring->deq_seg = td->last_trb_seg; - ep->ring->num_trbs_free += td->num_trbs - 1; inc_deq(xhci, ep->ring); return xhci_td_cleanup(xhci, td, ep->ring, status); @@ -3165,13 +3181,13 @@ static void queue_trb(struct xhci_hcd *xhci, struct xhci_ring *ring, /* * Does various checks on the endpoint ring, and makes it ready to queue num_trbs. - * FIXME allocate segments if the ring is full. + * expand ring if it start to be full. */ static int prepare_ring(struct xhci_hcd *xhci, struct xhci_ring *ep_ring, u32 ep_state, unsigned int num_trbs, gfp_t mem_flags) { - unsigned int num_trbs_needed; unsigned int link_trb_count = 0; + unsigned int new_segs = 0; /* Make sure the endpoint has been added to xHC schedule */ switch (ep_state) { @@ -3202,20 +3218,17 @@ static int prepare_ring(struct xhci_hcd *xhci, struct xhci_ring *ep_ring, return -EINVAL; } - while (1) { - if (room_on_ring(xhci, ep_ring, num_trbs)) - break; - - if (ep_ring == xhci->cmd_ring) { - xhci_err(xhci, "Do not support expand command ring\n"); - return -ENOMEM; - } + if (ep_ring != xhci->cmd_ring) { + new_segs = xhci_ring_expansion_needed(xhci, ep_ring, num_trbs); + } else if (xhci_num_trbs_free(xhci, ep_ring) <= num_trbs) { + xhci_err(xhci, "Do not support expand command ring\n"); + return -ENOMEM; + } + if (new_segs) { xhci_dbg_trace(xhci, trace_xhci_dbg_ring_expansion, "ERROR no room on ep ring, try ring expansion"); - num_trbs_needed = num_trbs - ep_ring->num_trbs_free; - if (xhci_ring_expansion(xhci, ep_ring, num_trbs_needed, - mem_flags)) { + if (xhci_ring_expansion(xhci, ep_ring, new_segs, mem_flags)) { xhci_err(xhci, "Ring expansion failed\n"); return -ENOMEM; } @@ -4185,7 +4198,6 @@ cleanup: ep_ring->enqueue = urb_priv->td[0].first_trb; ep_ring->enq_seg = urb_priv->td[0].start_seg; ep_ring->cycle_state = start_cycle; - ep_ring->num_trbs_free = ep_ring->num_trbs_free_temp; usb_hcd_unlink_urb_from_ep(bus_to_hcd(urb->dev->bus), urb); return ret; } @@ -4267,7 +4279,6 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags, } skip_start_over: - ep_ring->num_trbs_free_temp = ep_ring->num_trbs_free; return xhci_queue_isoc_tx(xhci, mem_flags, urb, slot_id, ep_index); } diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index c75d93244143..6ca8a37e53e1 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -1828,6 +1828,9 @@ static int tegra_xusb_probe(struct platform_device *pdev) goto remove_usb2; } + if (HCC_MAX_PSA(xhci->hcc_params) >= 4) + xhci->shared_hcd->can_do_streams = 1; + err = usb_add_hcd(xhci->shared_hcd, tegra->xhci_irq, IRQF_SHARED); if (err < 0) { dev_err(&pdev->dev, "failed to add shared HCD: %d\n", err); @@ -1909,7 +1912,7 @@ put_padctl: return err; } -static int tegra_xusb_remove(struct platform_device *pdev) +static void tegra_xusb_remove(struct platform_device *pdev) { struct tegra_xusb *tegra = platform_get_drvdata(pdev); struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); @@ -1939,8 +1942,6 @@ static int tegra_xusb_remove(struct platform_device *pdev) tegra_xusb_clk_disable(tegra); regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies); tegra_xusb_padctl_put(tegra->padctl); - - return 0; } static bool xhci_hub_ports_suspended(struct xhci_hub *hub) @@ -2272,7 +2273,7 @@ static int tegra_xusb_exit_elpg(struct tegra_xusb *tegra, bool runtime) if (wakeup) tegra_xhci_disable_phy_sleepwalk(tegra); - err = xhci_resume(xhci, 0); + err = xhci_resume(xhci, runtime ? PMSG_AUTO_RESUME : PMSG_RESUME); if (err < 0) { dev_err(tegra->dev, "failed to resume XHCI: %d\n", err); goto disable_phy; @@ -2650,7 +2651,7 @@ MODULE_DEVICE_TABLE(of, tegra_xusb_of_match); static struct platform_driver tegra_xusb_driver = { .probe = tegra_xusb_probe, - .remove = tegra_xusb_remove, + .remove_new = tegra_xusb_remove, .driver = { .name = "tegra-xusb", .pm = &tegra_xusb_pm_ops, @@ -2662,7 +2663,6 @@ static void tegra_xhci_quirks(struct device *dev, struct xhci_hcd *xhci) { struct tegra_xusb *tegra = dev_get_drvdata(dev); - xhci->quirks |= XHCI_PLAT; if (tegra && tegra->soc->lpm_support) xhci->quirks |= XHCI_LPM_SUPPORT; } diff --git a/drivers/usb/host/xhci-trace.h b/drivers/usb/host/xhci-trace.h index 4286dba5b157..d6b32f2ad90e 100644 --- a/drivers/usb/host/xhci-trace.h +++ b/drivers/usb/host/xhci-trace.h @@ -80,20 +80,16 @@ DECLARE_EVENT_CLASS(xhci_log_ctx, __field(dma_addr_t, ctx_dma) __field(u8 *, ctx_va) __field(unsigned, ctx_ep_num) - __field(int, slot_id) __dynamic_array(u32, ctx_data, ((HCC_64BYTE_CONTEXT(xhci->hcc_params) + 1) * 8) * ((ctx->type == XHCI_CTX_TYPE_INPUT) + ep_num + 1)) ), TP_fast_assign( - struct usb_device *udev; - udev = to_usb_device(xhci_to_hcd(xhci)->self.controller); __entry->ctx_64 = HCC_64BYTE_CONTEXT(xhci->hcc_params); __entry->ctx_type = ctx->type; __entry->ctx_dma = ctx->dma; __entry->ctx_va = ctx->bytes; - __entry->slot_id = udev->slot_id; __entry->ctx_ep_num = ep_num; memcpy(__get_dynamic_array(ctx_data), ctx->bytes, ((HCC_64BYTE_CONTEXT(xhci->hcc_params) + 1) * 32) * @@ -462,7 +458,6 @@ DECLARE_EVENT_CLASS(xhci_log_ring, __field(unsigned int, num_segs) __field(unsigned int, stream_id) __field(unsigned int, cycle_state) - __field(unsigned int, num_trbs_free) __field(unsigned int, bounce_buf_len) ), TP_fast_assign( @@ -473,18 +468,16 @@ DECLARE_EVENT_CLASS(xhci_log_ring, __entry->enq_seg = ring->enq_seg->dma; __entry->deq_seg = ring->deq_seg->dma; __entry->cycle_state = ring->cycle_state; - __entry->num_trbs_free = ring->num_trbs_free; __entry->bounce_buf_len = ring->bounce_buf_len; __entry->enq = xhci_trb_virt_to_dma(ring->enq_seg, ring->enqueue); __entry->deq = xhci_trb_virt_to_dma(ring->deq_seg, ring->dequeue); ), - TP_printk("%s %p: enq %pad(%pad) deq %pad(%pad) segs %d stream %d free_trbs %d bounce %d cycle %d", + TP_printk("%s %p: enq %pad(%pad) deq %pad(%pad) segs %d stream %d bounce %d cycle %d", xhci_ring_type_string(__entry->type), __entry->ring, &__entry->enq, &__entry->enq_seg, &__entry->deq, &__entry->deq_seg, __entry->num_segs, __entry->stream_id, - __entry->num_trbs_free, __entry->bounce_buf_len, __entry->cycle_state ) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 78790dc13c5f..fae994f679d4 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -833,7 +833,7 @@ static bool xhci_pending_portevent(struct xhci_hcd *xhci) ports = xhci->usb3_rhub.ports; while (port_index--) { portsc = readl(ports[port_index]->addr); - if (portsc & PORT_CHANGE_MASK || + if (portsc & (PORT_CHANGE_MASK | PORT_CAS) || (portsc & PORT_PLS_MASK) == XDEV_RESUME) return true; } @@ -960,8 +960,9 @@ EXPORT_SYMBOL_GPL(xhci_suspend); * This is called when the machine transition from S3/S4 mode. * */ -int xhci_resume(struct xhci_hcd *xhci, bool hibernated) +int xhci_resume(struct xhci_hcd *xhci, pm_message_t msg) { + bool hibernated = (msg.event == PM_EVENT_RESTORE); u32 command, temp = 0; struct usb_hcd *hcd = xhci_to_hcd(xhci); int retval = 0; @@ -1027,7 +1028,8 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) temp = readl(&xhci->op_regs->status); /* re-initialize the HC on Restore Error, or Host Controller Error */ - if (temp & (STS_SRE | STS_HCE)) { + if ((temp & (STS_SRE | STS_HCE)) && + !(xhci->xhc_state & XHCI_STATE_REMOVING)) { reinit_xhc = true; if (!xhci->broken_suspend) xhci_warn(xhci, "xHC error in resume, USBSTS 0x%x, Reinit\n", temp); @@ -1116,7 +1118,7 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) * the first wake signalling failed, give it that chance. */ pending_portevent = xhci_pending_portevent(xhci); - if (!pending_portevent) { + if (!pending_portevent && msg.event == PM_EVENT_AUTO_RESUME) { msleep(120); pending_portevent = xhci_pending_portevent(xhci); } @@ -4604,7 +4606,7 @@ static u16 xhci_calculate_u1_timeout(struct xhci_hcd *xhci, } } - if (xhci->quirks & XHCI_INTEL_HOST) + if (xhci->quirks & (XHCI_INTEL_HOST | XHCI_ZHAOXIN_HOST)) timeout_ns = xhci_calculate_intel_u1_timeout(udev, desc); else timeout_ns = udev->u1_params.sel; @@ -4668,7 +4670,7 @@ static u16 xhci_calculate_u2_timeout(struct xhci_hcd *xhci, } } - if (xhci->quirks & XHCI_INTEL_HOST) + if (xhci->quirks & (XHCI_INTEL_HOST | XHCI_ZHAOXIN_HOST)) timeout_ns = xhci_calculate_intel_u2_timeout(udev, desc); else timeout_ns = udev->u2_params.sel; @@ -4740,37 +4742,30 @@ static int xhci_update_timeout_for_interface(struct xhci_hcd *xhci, return 0; } -static int xhci_check_intel_tier_policy(struct usb_device *udev, +static int xhci_check_tier_policy(struct xhci_hcd *xhci, + struct usb_device *udev, enum usb3_link_state state) { - struct usb_device *parent; - unsigned int num_hubs; + struct usb_device *parent = udev->parent; + int tier = 1; /* roothub is tier1 */ - /* Don't enable U1 if the device is on a 2nd tier hub or lower. */ - for (parent = udev->parent, num_hubs = 0; parent->parent; - parent = parent->parent) - num_hubs++; + while (parent) { + parent = parent->parent; + tier++; + } - if (num_hubs < 2) - return 0; + if (xhci->quirks & XHCI_INTEL_HOST && tier > 3) + goto fail; + if (xhci->quirks & XHCI_ZHAOXIN_HOST && tier > 2) + goto fail; - dev_dbg(&udev->dev, "Disabling U1/U2 link state for device" - " below second-tier hub.\n"); - dev_dbg(&udev->dev, "Plug device into first-tier hub " - "to decrease power consumption.\n"); + return 0; +fail: + dev_dbg(&udev->dev, "Tier policy prevents U1/U2 LPM states for devices at tier %d\n", + tier); return -E2BIG; } -static int xhci_check_tier_policy(struct xhci_hcd *xhci, - struct usb_device *udev, - enum usb3_link_state state) -{ - if (xhci->quirks & XHCI_INTEL_HOST) - return xhci_check_intel_tier_policy(udev, state); - else - return 0; -} - /* Returns the U1 or U2 timeout that should be enabled. * If the tier check or timeout setting functions return with a non-zero exit * code, that means the timeout value has been finalized and we shouldn't look @@ -5180,7 +5175,8 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) xhci->quirks |= quirks; - get_quirks(dev, xhci); + if (get_quirks) + get_quirks(dev, xhci); /* In xhci controllers which follow xhci 1.0 spec gives a spurious * success event after a short transfer. This quirk will ignore such diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 6b690ec91ff3..7e282b4522c0 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1633,8 +1633,7 @@ struct xhci_ring { u32 cycle_state; unsigned int stream_id; unsigned int num_segs; - unsigned int num_trbs_free; - unsigned int num_trbs_free_temp; + unsigned int num_trbs_free; /* used only by xhci DbC */ unsigned int bounce_buf_len; enum xhci_ring_type type; bool last_td_was_short; @@ -1874,7 +1873,7 @@ struct xhci_hcd { #define XHCI_SPURIOUS_REBOOT BIT_ULL(13) #define XHCI_COMP_MODE_QUIRK BIT_ULL(14) #define XHCI_AVOID_BEI BIT_ULL(15) -#define XHCI_PLAT BIT_ULL(16) +#define XHCI_PLAT BIT_ULL(16) /* Deprecated */ #define XHCI_SLOW_SUSPEND BIT_ULL(17) #define XHCI_SPURIOUS_WAKEUP BIT_ULL(18) /* For controllers with a broken beyond repair streams implementation */ @@ -1905,6 +1904,8 @@ struct xhci_hcd { #define XHCI_EP_CTX_BROKEN_DCS BIT_ULL(42) #define XHCI_SUSPEND_RESUME_CLKS BIT_ULL(43) #define XHCI_RESET_TO_DEFAULT BIT_ULL(44) +#define XHCI_ZHAOXIN_TRB_FETCH BIT_ULL(45) +#define XHCI_ZHAOXIN_HOST BIT_ULL(46) unsigned int num_active_eps; unsigned int limit_active_eps; @@ -2140,7 +2141,7 @@ int xhci_disable_slot(struct xhci_hcd *xhci, u32 slot_id); int xhci_ext_cap_init(struct xhci_hcd *xhci); int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup); -int xhci_resume(struct xhci_hcd *xhci, bool hibernated); +int xhci_resume(struct xhci_hcd *xhci, pm_message_t msg); irqreturn_t xhci_irq(struct usb_hcd *hcd); irqreturn_t xhci_msi_irq(int irq, void *hcd); diff --git a/drivers/usb/isp1760/isp1760-if.c b/drivers/usb/isp1760/isp1760-if.c index 65ba5aca2a4f..fe1e3985419a 100644 --- a/drivers/usb/isp1760/isp1760-if.c +++ b/drivers/usb/isp1760/isp1760-if.c @@ -246,11 +246,9 @@ static int isp1760_plat_probe(struct platform_device *pdev) return 0; } -static int isp1760_plat_remove(struct platform_device *pdev) +static void isp1760_plat_remove(struct platform_device *pdev) { isp1760_unregister(&pdev->dev); - - return 0; } #ifdef CONFIG_OF @@ -265,7 +263,7 @@ MODULE_DEVICE_TABLE(of, isp1760_of_match); static struct platform_driver isp1760_plat_driver = { .probe = isp1760_plat_probe, - .remove = isp1760_plat_remove, + .remove_new = isp1760_plat_remove, .driver = { .name = "isp1760", .of_match_table = of_match_ptr(isp1760_of_match), diff --git a/drivers/usb/misc/onboard_usb_hub.c b/drivers/usb/misc/onboard_usb_hub.c index 12fc6eb67c3b..83f14ca1d38e 100644 --- a/drivers/usb/misc/onboard_usb_hub.c +++ b/drivers/usb/misc/onboard_usb_hub.c @@ -278,7 +278,7 @@ static int onboard_hub_probe(struct platform_device *pdev) return 0; } -static int onboard_hub_remove(struct platform_device *pdev) +static void onboard_hub_remove(struct platform_device *pdev) { struct onboard_hub *hub = dev_get_drvdata(&pdev->dev); struct usbdev_node *node; @@ -306,7 +306,7 @@ static int onboard_hub_remove(struct platform_device *pdev) mutex_unlock(&hub->lock); - return onboard_hub_power_off(hub); + onboard_hub_power_off(hub); } MODULE_DEVICE_TABLE(of, onboard_hub_match); @@ -317,7 +317,7 @@ static const struct dev_pm_ops __maybe_unused onboard_hub_pm_ops = { static struct platform_driver onboard_hub_driver = { .probe = onboard_hub_probe, - .remove = onboard_hub_remove, + .remove_new = onboard_hub_remove, .driver = { .name = "onboard-usb-hub", diff --git a/drivers/usb/misc/qcom_eud.c b/drivers/usb/misc/qcom_eud.c index b7f13df00764..7f371ea1248c 100644 --- a/drivers/usb/misc/qcom_eud.c +++ b/drivers/usb/misc/qcom_eud.c @@ -22,10 +22,10 @@ #define EUD_REG_VBUS_INT_CLR 0x0080 #define EUD_REG_CSR_EUD_EN 0x1014 #define EUD_REG_SW_ATTACH_DET 0x1018 -#define EUD_REG_EUD_EN2 0x0000 +#define EUD_REG_EUD_EN2 0x0000 #define EUD_ENABLE BIT(0) -#define EUD_INT_PET_EUD BIT(0) +#define EUD_INT_PET_EUD BIT(0) #define EUD_INT_VBUS BIT(2) #define EUD_INT_SAFE_MODE BIT(4) #define EUD_INT_ALL (EUD_INT_VBUS | EUD_INT_SAFE_MODE) @@ -217,7 +217,7 @@ static int eud_probe(struct platform_device *pdev) return 0; } -static int eud_remove(struct platform_device *pdev) +static void eud_remove(struct platform_device *pdev) { struct eud_chip *chip = platform_get_drvdata(pdev); @@ -226,8 +226,6 @@ static int eud_remove(struct platform_device *pdev) device_init_wakeup(&pdev->dev, false); disable_irq_wake(chip->irq); - - return 0; } static const struct of_device_id eud_dt_match[] = { @@ -238,7 +236,7 @@ MODULE_DEVICE_TABLE(of, eud_dt_match); static struct platform_driver eud_driver = { .probe = eud_probe, - .remove = eud_remove, + .remove_new = eud_remove, .driver = { .name = "qcom_eud", .dev_groups = eud_groups, diff --git a/drivers/usb/misc/usb251xb.c b/drivers/usb/misc/usb251xb.c index ce1da80d3365..e4edb486b69e 100644 --- a/drivers/usb/misc/usb251xb.c +++ b/drivers/usb/misc/usb251xb.c @@ -416,14 +416,13 @@ static int usb251xb_get_ofdata(struct usb251xb *hub, return dev_err_probe(dev, PTR_ERR(hub->gpio_reset), "unable to request GPIO reset pin\n"); - if (of_property_read_u16_array(np, "vendor-id", &hub->vendor_id, 1)) + if (of_property_read_u16(np, "vendor-id", &hub->vendor_id)) hub->vendor_id = USB251XB_DEF_VENDOR_ID; - if (of_property_read_u16_array(np, "product-id", - &hub->product_id, 1)) + if (of_property_read_u16(np, "product-id", &hub->product_id)) hub->product_id = data->product_id; - if (of_property_read_u16_array(np, "device-id", &hub->device_id, 1)) + if (of_property_read_u16(np, "device-id", &hub->device_id)) hub->device_id = USB251XB_DEF_DEVICE_ID; hub->conf_data1 = USB251XB_DEF_CONFIG_DATA_1; @@ -532,7 +531,7 @@ static int usb251xb_get_ofdata(struct usb251xb *hub, if (!of_property_read_u32(np, "power-on-time-ms", &property_u32)) hub->power_on_time = min_t(u8, property_u32 / 2, 255); - if (of_property_read_u16_array(np, "language-id", &hub->lang_id, 1)) + if (of_property_read_u16(np, "language-id", &hub->lang_id)) hub->lang_id = USB251XB_DEF_LANGUAGE_ID; if (of_property_read_u8(np, "boost-up", &hub->boost_up)) @@ -746,7 +745,7 @@ static struct i2c_driver usb251xb_i2c_driver = { .of_match_table = usb251xb_of_match, .pm = &usb251xb_pm_ops, }, - .probe_new = usb251xb_i2c_probe, + .probe = usb251xb_i2c_probe, .id_table = usb251xb_id, }; diff --git a/drivers/usb/misc/usb3503.c b/drivers/usb/misc/usb3503.c index c6cfd1edaf76..72765077932c 100644 --- a/drivers/usb/misc/usb3503.c +++ b/drivers/usb/misc/usb3503.c @@ -335,14 +335,12 @@ static int usb3503_platform_probe(struct platform_device *pdev) return usb3503_probe(hub); } -static int usb3503_platform_remove(struct platform_device *pdev) +static void usb3503_platform_remove(struct platform_device *pdev) { struct usb3503 *hub; hub = platform_get_drvdata(pdev); clk_disable_unprepare(hub->clk); - - return 0; } static int __maybe_unused usb3503_suspend(struct usb3503 *hub) @@ -413,7 +411,7 @@ static struct i2c_driver usb3503_i2c_driver = { .pm = pm_ptr(&usb3503_i2c_pm_ops), .of_match_table = of_match_ptr(usb3503_of_match), }, - .probe_new = usb3503_i2c_probe, + .probe = usb3503_i2c_probe, .remove = usb3503_i2c_remove, .id_table = usb3503_id, }; @@ -425,7 +423,7 @@ static struct platform_driver usb3503_platform_driver = { .pm = pm_ptr(&usb3503_platform_pm_ops), }, .probe = usb3503_platform_probe, - .remove = usb3503_platform_remove, + .remove_new = usb3503_platform_remove, }; static int __init usb3503_init(void) diff --git a/drivers/usb/misc/usb4604.c b/drivers/usb/misc/usb4604.c index 6b5e77231efa..065e269ba4e3 100644 --- a/drivers/usb/misc/usb4604.c +++ b/drivers/usb/misc/usb4604.c @@ -154,7 +154,7 @@ static struct i2c_driver usb4604_i2c_driver = { .pm = pm_ptr(&usb4604_i2c_pm_ops), .of_match_table = of_match_ptr(usb4604_of_match), }, - .probe_new = usb4604_i2c_probe, + .probe = usb4604_i2c_probe, .id_table = usb4604_id, }; module_i2c_driver(usb4604_i2c_driver); diff --git a/drivers/usb/mon/mon_bin.c b/drivers/usb/mon/mon_bin.c index 952c56789258..9ca9305243fe 100644 --- a/drivers/usb/mon/mon_bin.c +++ b/drivers/usb/mon/mon_bin.c @@ -213,7 +213,10 @@ static unsigned char xfer_to_pipe[4] = { PIPE_CONTROL, PIPE_ISOCHRONOUS, PIPE_BULK, PIPE_INTERRUPT }; -static struct class *mon_bin_class; +static const struct class mon_bin_class = { + .name = "usbmon", +}; + static dev_t mon_bin_dev0; static struct cdev mon_bin_cdev; @@ -1360,7 +1363,7 @@ int mon_bin_add(struct mon_bus *mbus, const struct usb_bus *ubus) if (minor >= MON_BIN_MAX_MINOR) return 0; - dev = device_create(mon_bin_class, ubus ? ubus->controller : NULL, + dev = device_create(&mon_bin_class, ubus ? ubus->controller : NULL, MKDEV(MAJOR(mon_bin_dev0), minor), NULL, "usbmon%d", minor); if (IS_ERR(dev)) @@ -1372,18 +1375,16 @@ int mon_bin_add(struct mon_bus *mbus, const struct usb_bus *ubus) void mon_bin_del(struct mon_bus *mbus) { - device_destroy(mon_bin_class, mbus->classdev->devt); + device_destroy(&mon_bin_class, mbus->classdev->devt); } int __init mon_bin_init(void) { int rc; - mon_bin_class = class_create("usbmon"); - if (IS_ERR(mon_bin_class)) { - rc = PTR_ERR(mon_bin_class); + rc = class_register(&mon_bin_class); + if (rc) goto err_class; - } rc = alloc_chrdev_region(&mon_bin_dev0, 0, MON_BIN_MAX_MINOR, "usbmon"); if (rc < 0) @@ -1401,7 +1402,7 @@ int __init mon_bin_init(void) err_add: unregister_chrdev_region(mon_bin_dev0, MON_BIN_MAX_MINOR); err_dev: - class_destroy(mon_bin_class); + class_unregister(&mon_bin_class); err_class: return rc; } @@ -1410,5 +1411,5 @@ void mon_bin_exit(void) { cdev_del(&mon_bin_cdev); unregister_chrdev_region(mon_bin_dev0, MON_BIN_MAX_MINOR); - class_destroy(mon_bin_class); + class_unregister(&mon_bin_class); } diff --git a/drivers/usb/phy/phy-isp1301.c b/drivers/usb/phy/phy-isp1301.c index f4ee14d98585..993d7525a102 100644 --- a/drivers/usb/phy/phy-isp1301.c +++ b/drivers/usb/phy/phy-isp1301.c @@ -132,7 +132,7 @@ static struct i2c_driver isp1301_driver = { .name = DRV_NAME, .of_match_table = isp1301_of_match, }, - .probe_new = isp1301_probe, + .probe = isp1301_probe, .remove = isp1301_remove, .id_table = isp1301_id, }; diff --git a/drivers/usb/phy/phy-tahvo.c b/drivers/usb/phy/phy-tahvo.c index 47562d49dfc1..5cac31c6029b 100644 --- a/drivers/usb/phy/phy-tahvo.c +++ b/drivers/usb/phy/phy-tahvo.c @@ -391,7 +391,7 @@ static int tahvo_usb_probe(struct platform_device *pdev) tu->irq = ret = platform_get_irq(pdev, 0); if (ret < 0) - return ret; + goto err_remove_phy; ret = request_threaded_irq(tu->irq, NULL, tahvo_usb_vbus_interrupt, IRQF_ONESHOT, "tahvo-vbus", tu); diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index fa34efabcccf..111b7ee152c4 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -762,7 +762,7 @@ probe_end_pipe_exit: return ret; } -static int usbhs_remove(struct platform_device *pdev) +static void usbhs_remove(struct platform_device *pdev) { struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); @@ -780,8 +780,6 @@ static int usbhs_remove(struct platform_device *pdev) usbhs_mod_remove(priv); usbhs_fifo_remove(priv); usbhs_pipe_remove(priv); - - return 0; } static __maybe_unused int usbhsc_suspend(struct device *dev) @@ -826,7 +824,7 @@ static struct platform_driver renesas_usbhs_driver = { .of_match_table = usbhs_of_match, }, .probe = usbhs_probe, - .remove = usbhs_remove, + .remove_new = usbhs_remove, }; module_platform_driver(renesas_usbhs_driver); diff --git a/drivers/usb/roles/class.c b/drivers/usb/roles/class.c index 0395bd5dbd3e..ae41578bd014 100644 --- a/drivers/usb/roles/class.c +++ b/drivers/usb/roles/class.c @@ -14,7 +14,9 @@ #include <linux/mutex.h> #include <linux/slab.h> -static struct class *role_class; +static const struct class role_class = { + .name = "usb_role", +}; struct usb_role_switch { struct device dev; @@ -95,7 +97,7 @@ static void *usb_role_switch_match(const struct fwnode_handle *fwnode, const cha if (id && !fwnode_property_present(fwnode, id)) return NULL; - dev = class_find_device_by_fwnode(role_class, fwnode); + dev = class_find_device_by_fwnode(&role_class, fwnode); return dev ? to_role_switch(dev) : ERR_PTR(-EPROBE_DEFER); } @@ -111,7 +113,7 @@ usb_role_switch_is_parent(struct fwnode_handle *fwnode) return NULL; } - dev = class_find_device_by_fwnode(role_class, parent); + dev = class_find_device_by_fwnode(&role_class, parent); fwnode_handle_put(parent); return dev ? to_role_switch(dev) : ERR_PTR(-EPROBE_DEFER); } @@ -191,7 +193,7 @@ usb_role_switch_find_by_fwnode(const struct fwnode_handle *fwnode) if (!fwnode) return NULL; - dev = class_find_device_by_fwnode(role_class, fwnode); + dev = class_find_device_by_fwnode(&role_class, fwnode); if (dev) WARN_ON(!try_module_get(dev->parent->driver->owner)); @@ -338,7 +340,7 @@ usb_role_switch_register(struct device *parent, sw->dev.parent = parent; sw->dev.fwnode = desc->fwnode; - sw->dev.class = role_class; + sw->dev.class = &role_class; sw->dev.type = &usb_role_dev_type; dev_set_drvdata(&sw->dev, desc->driver_data); dev_set_name(&sw->dev, "%s-role-switch", @@ -392,14 +394,13 @@ EXPORT_SYMBOL_GPL(usb_role_switch_get_drvdata); static int __init usb_roles_init(void) { - role_class = class_create("usb_role"); - return PTR_ERR_OR_ZERO(role_class); + return class_register(&role_class); } subsys_initcall(usb_roles_init); static void __exit usb_roles_exit(void) { - class_destroy(role_class); + class_unregister(&role_class); } module_exit(usb_roles_exit); diff --git a/drivers/usb/roles/intel-xhci-usb-role-switch.c b/drivers/usb/roles/intel-xhci-usb-role-switch.c index 5c96e929acea..e5c6c413a075 100644 --- a/drivers/usb/roles/intel-xhci-usb-role-switch.c +++ b/drivers/usb/roles/intel-xhci-usb-role-switch.c @@ -195,7 +195,7 @@ static int intel_xhci_usb_probe(struct platform_device *pdev) return 0; } -static int intel_xhci_usb_remove(struct platform_device *pdev) +static void intel_xhci_usb_remove(struct platform_device *pdev) { struct intel_xhci_usb_data *data = platform_get_drvdata(pdev); @@ -203,8 +203,6 @@ static int intel_xhci_usb_remove(struct platform_device *pdev) usb_role_switch_unregister(data->role_sw); fwnode_handle_put(software_node_fwnode(&intel_xhci_usb_node)); - - return 0; } static const struct platform_device_id intel_xhci_usb_table[] = { @@ -219,7 +217,7 @@ static struct platform_driver intel_xhci_usb_driver = { }, .id_table = intel_xhci_usb_table, .probe = intel_xhci_usb_probe, - .remove = intel_xhci_usb_remove, + .remove_new = intel_xhci_usb_remove, }; module_platform_driver(intel_xhci_usb_driver); diff --git a/drivers/usb/serial/ark3116.c b/drivers/usb/serial/ark3116.c index 9452291f1703..67a07cc007f0 100644 --- a/drivers/usb/serial/ark3116.c +++ b/drivers/usb/serial/ark3116.c @@ -433,10 +433,11 @@ static int ark3116_tiocmset(struct tty_struct *tty, return 0; } -static void ark3116_break_ctl(struct tty_struct *tty, int break_state) +static int ark3116_break_ctl(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; struct ark3116_private *priv = usb_get_serial_port_data(port); + int ret; /* LCR is also used for other things: protect access */ mutex_lock(&priv->hw_lock); @@ -446,9 +447,11 @@ static void ark3116_break_ctl(struct tty_struct *tty, int break_state) else priv->lcr &= ~UART_LCR_SBC; - ark3116_write_reg(port->serial, UART_LCR, priv->lcr); + ret = ark3116_write_reg(port->serial, UART_LCR, priv->lcr); mutex_unlock(&priv->hw_lock); + + return ret; } static void ark3116_update_msr(struct usb_serial_port *port, __u8 msr) diff --git a/drivers/usb/serial/belkin_sa.c b/drivers/usb/serial/belkin_sa.c index 9331a562dac0..cf47ee4ae5d3 100644 --- a/drivers/usb/serial/belkin_sa.c +++ b/drivers/usb/serial/belkin_sa.c @@ -46,7 +46,7 @@ static void belkin_sa_process_read_urb(struct urb *urb); static void belkin_sa_set_termios(struct tty_struct *tty, struct usb_serial_port *port, const struct ktermios *old_termios); -static void belkin_sa_break_ctl(struct tty_struct *tty, int break_state); +static int belkin_sa_break_ctl(struct tty_struct *tty, int break_state); static int belkin_sa_tiocmget(struct tty_struct *tty); static int belkin_sa_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear); @@ -399,13 +399,19 @@ static void belkin_sa_set_termios(struct tty_struct *tty, spin_unlock_irqrestore(&priv->lock, flags); } -static void belkin_sa_break_ctl(struct tty_struct *tty, int break_state) +static int belkin_sa_break_ctl(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; struct usb_serial *serial = port->serial; + int ret; - if (BSA_USB_CMD(BELKIN_SA_SET_BREAK_REQUEST, break_state ? 1 : 0) < 0) + ret = BSA_USB_CMD(BELKIN_SA_SET_BREAK_REQUEST, break_state ? 1 : 0); + if (ret < 0) { dev_err(&port->dev, "Set break_ctl %d\n", break_state); + return ret; + } + + return 0; } static int belkin_sa_tiocmget(struct tty_struct *tty) diff --git a/drivers/usb/serial/ch341.c b/drivers/usb/serial/ch341.c index 6e1b87e67304..612bea504d7a 100644 --- a/drivers/usb/serial/ch341.c +++ b/drivers/usb/serial/ch341.c @@ -562,12 +562,12 @@ static void ch341_set_termios(struct tty_struct *tty, * TCSBRKP. Due to how the simulation is implemented the duration can't be * controlled. The duration is always about (1s / 46bd * 9bit) = 196ms. */ -static void ch341_simulate_break(struct tty_struct *tty, int break_state) +static int ch341_simulate_break(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; struct ch341_private *priv = usb_get_serial_port_data(port); unsigned long now, delay; - int r; + int r, r2; if (break_state != 0) { dev_dbg(&port->dev, "enter break state requested\n"); @@ -599,7 +599,7 @@ static void ch341_simulate_break(struct tty_struct *tty, int break_state) */ priv->break_end = jiffies + (11 * HZ / CH341_MIN_BPS); - return; + return 0; } dev_dbg(&port->dev, "leave break state requested\n"); @@ -615,17 +615,22 @@ static void ch341_simulate_break(struct tty_struct *tty, int break_state) schedule_timeout_interruptible(delay); } + r = 0; restore: /* Restore original baud rate */ - r = ch341_set_baudrate_lcr(port->serial->dev, priv, priv->baud_rate, - priv->lcr); - if (r < 0) + r2 = ch341_set_baudrate_lcr(port->serial->dev, priv, priv->baud_rate, + priv->lcr); + if (r2 < 0) { dev_err(&port->dev, "restoring original baud rate of %u failed: %d\n", - priv->baud_rate, r); + priv->baud_rate, r2); + return r2; + } + + return r; } -static void ch341_break_ctl(struct tty_struct *tty, int break_state) +static int ch341_break_ctl(struct tty_struct *tty, int break_state) { const uint16_t ch341_break_reg = ((uint16_t) CH341_REG_LCR << 8) | CH341_REG_BREAK; @@ -635,17 +640,17 @@ static void ch341_break_ctl(struct tty_struct *tty, int break_state) uint16_t reg_contents; uint8_t break_reg[2]; - if (priv->quirks & CH341_QUIRK_SIMULATE_BREAK) { - ch341_simulate_break(tty, break_state); - return; - } + if (priv->quirks & CH341_QUIRK_SIMULATE_BREAK) + return ch341_simulate_break(tty, break_state); r = ch341_control_in(port->serial->dev, CH341_REQ_READ_REG, ch341_break_reg, 0, break_reg, 2); if (r) { dev_err(&port->dev, "%s - USB control read error (%d)\n", __func__, r); - return; + if (r > 0) + r = -EIO; + return r; } dev_dbg(&port->dev, "%s - initial ch341 break register contents - reg1: %x, reg2: %x\n", __func__, break_reg[0], break_reg[1]); @@ -663,9 +668,13 @@ static void ch341_break_ctl(struct tty_struct *tty, int break_state) reg_contents = get_unaligned_le16(break_reg); r = ch341_control_out(port->serial->dev, CH341_REQ_WRITE_REG, ch341_break_reg, reg_contents); - if (r < 0) + if (r < 0) { dev_err(&port->dev, "%s - USB control write error (%d)\n", __func__, r); + return r; + } + + return 0; } static int ch341_tiocmset(struct tty_struct *tty, diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index cdea1bff3b70..1e61fe043171 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -39,7 +39,7 @@ static int cp210x_tiocmget(struct tty_struct *); static int cp210x_tiocmset(struct tty_struct *, unsigned int, unsigned int); static int cp210x_tiocmset_port(struct usb_serial_port *port, unsigned int, unsigned int); -static void cp210x_break_ctl(struct tty_struct *, int); +static int cp210x_break_ctl(struct tty_struct *, int); static int cp210x_attach(struct usb_serial *); static void cp210x_disconnect(struct usb_serial *); static void cp210x_release(struct usb_serial *); @@ -1434,18 +1434,26 @@ static int cp210x_tiocmget(struct tty_struct *tty) return result; } -static void cp210x_break_ctl(struct tty_struct *tty, int break_state) +static int cp210x_break_ctl(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; + struct cp210x_serial_private *priv = usb_get_serial_data(port->serial); u16 state; + if (priv->partnum == CP210X_PARTNUM_CP2105) { + if (cp210x_interface_num(port->serial) == 1) + return -ENOTTY; + } + if (break_state == 0) state = BREAK_OFF; else state = BREAK_ON; + dev_dbg(&port->dev, "%s - turning break %s\n", __func__, state == BREAK_OFF ? "off" : "on"); - cp210x_write_u16_reg(port, CP210X_SET_BREAK, state); + + return cp210x_write_u16_reg(port, CP210X_SET_BREAK, state); } #ifdef CONFIG_GPIOLIB diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 45d688e9b93f..d1dea3850576 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -217,7 +217,7 @@ static void digi_rx_unthrottle(struct tty_struct *tty); static void digi_set_termios(struct tty_struct *tty, struct usb_serial_port *port, const struct ktermios *old_termios); -static void digi_break_ctl(struct tty_struct *tty, int break_state); +static int digi_break_ctl(struct tty_struct *tty, int break_state); static int digi_tiocmget(struct tty_struct *tty); static int digi_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear); @@ -839,7 +839,7 @@ static void digi_set_termios(struct tty_struct *tty, } -static void digi_break_ctl(struct tty_struct *tty, int break_state) +static int digi_break_ctl(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; unsigned char buf[4]; @@ -848,7 +848,8 @@ static void digi_break_ctl(struct tty_struct *tty, int break_state) buf[1] = 2; /* length */ buf[2] = break_state ? 1 : 0; buf[3] = 0; /* pad */ - digi_write_inb_command(port, buf, 4, 0); + + return digi_write_inb_command(port, buf, 4, 0); } diff --git a/drivers/usb/serial/f81232.c b/drivers/usb/serial/f81232.c index 891fb1fe69df..5f7a46bcace6 100644 --- a/drivers/usb/serial/f81232.c +++ b/drivers/usb/serial/f81232.c @@ -448,7 +448,7 @@ static void f81534a_process_read_urb(struct urb *urb) tty_flip_buffer_push(&port->port); } -static void f81232_break_ctl(struct tty_struct *tty, int break_state) +static int f81232_break_ctl(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; struct f81232_private *priv = usb_get_serial_port_data(port); @@ -467,6 +467,8 @@ static void f81232_break_ctl(struct tty_struct *tty, int break_state) dev_err(&port->dev, "set break failed: %d\n", status); mutex_unlock(&priv->lock); + + return status; } static int f81232_find_clk(speed_t baudrate) diff --git a/drivers/usb/serial/f81534.c b/drivers/usb/serial/f81534.c index 4083ae961be4..ef126cd3d94f 100644 --- a/drivers/usb/serial/f81534.c +++ b/drivers/usb/serial/f81534.c @@ -656,7 +656,7 @@ out_unlock: return status; } -static void f81534_break_ctl(struct tty_struct *tty, int break_state) +static int f81534_break_ctl(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; struct f81534_port_private *port_priv = usb_get_serial_port_data(port); @@ -675,6 +675,8 @@ static void f81534_break_ctl(struct tty_struct *tty, int break_state) dev_err(&port->dev, "set break failed: %d\n", status); mutex_unlock(&port_priv->lcr_mutex); + + return status; } static int f81534_update_mctrl(struct usb_serial_port *port, unsigned int set, diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index 05e28a5ce42b..1bf23611be12 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -2550,11 +2550,12 @@ static void ftdi_process_read_urb(struct urb *urb) tty_flip_buffer_push(&port->port); } -static void ftdi_break_ctl(struct tty_struct *tty, int break_state) +static int ftdi_break_ctl(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; struct ftdi_private *priv = usb_get_serial_port_data(port); u16 value; + int ret; /* break_state = -1 to turn on break, and 0 to turn off break */ /* see drivers/char/tty_io.c to see it used */ @@ -2565,19 +2566,22 @@ static void ftdi_break_ctl(struct tty_struct *tty, int break_state) else value = priv->last_set_data_value; - if (usb_control_msg(port->serial->dev, + ret = usb_control_msg(port->serial->dev, usb_sndctrlpipe(port->serial->dev, 0), FTDI_SIO_SET_DATA_REQUEST, FTDI_SIO_SET_DATA_REQUEST_TYPE, value, priv->channel, - NULL, 0, WDR_TIMEOUT) < 0) { + NULL, 0, WDR_TIMEOUT); + if (ret < 0) { dev_err(&port->dev, "%s FAILED to enable/disable break state (state was %d)\n", __func__, break_state); + return ret; } dev_dbg(&port->dev, "%s break state is %d - urb is %d\n", __func__, break_state, value); + return 0; } static bool ftdi_tx_empty(struct usb_serial_port *port) diff --git a/drivers/usb/serial/io_edgeport.c b/drivers/usb/serial/io_edgeport.c index 3a4c0febf335..abe4bbb0ac65 100644 --- a/drivers/usb/serial/io_edgeport.c +++ b/drivers/usb/serial/io_edgeport.c @@ -1560,12 +1560,12 @@ static int edge_ioctl(struct tty_struct *tty, * SerialBreak * this function sends a break to the port *****************************************************************************/ -static void edge_break(struct tty_struct *tty, int break_state) +static int edge_break(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; struct edgeport_port *edge_port = usb_get_serial_port_data(port); struct edgeport_serial *edge_serial = usb_get_serial_data(port->serial); - int status; + int status = 0; if (!edge_serial->is_epic || edge_serial->epic_descriptor.Supports.IOSPChase) { @@ -1597,6 +1597,8 @@ static void edge_break(struct tty_struct *tty, int break_state) dev_dbg(&port->dev, "%s - error sending break set/clear command.\n", __func__); } + + return status; } diff --git a/drivers/usb/serial/io_ti.c b/drivers/usb/serial/io_ti.c index bc3c24ea42c1..7a3a6e539456 100644 --- a/drivers/usb/serial/io_ti.c +++ b/drivers/usb/serial/io_ti.c @@ -2421,7 +2421,7 @@ static int edge_tiocmget(struct tty_struct *tty) return result; } -static void edge_break(struct tty_struct *tty, int break_state) +static int edge_break(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; struct edgeport_port *edge_port = usb_get_serial_port_data(port); @@ -2430,10 +2430,15 @@ static void edge_break(struct tty_struct *tty, int break_state) if (break_state == -1) bv = 1; /* On */ + status = ti_do_config(edge_port, UMPC_SET_CLR_BREAK, bv); - if (status) + if (status) { dev_dbg(&port->dev, "%s - error %d sending break set/clear command.\n", __func__, status); + return status; + } + + return 0; } static void edge_heartbeat_schedule(struct edgeport_serial *edge_serial) diff --git a/drivers/usb/serial/keyspan.c b/drivers/usb/serial/keyspan.c index 2966e0c4941e..93b17e0e05a3 100644 --- a/drivers/usb/serial/keyspan.c +++ b/drivers/usb/serial/keyspan.c @@ -599,7 +599,7 @@ struct keyspan_port_private { #include "keyspan_usa67msg.h" -static void keyspan_break_ctl(struct tty_struct *tty, int break_state) +static int keyspan_break_ctl(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; struct keyspan_port_private *p_priv; @@ -611,7 +611,10 @@ static void keyspan_break_ctl(struct tty_struct *tty, int break_state) else p_priv->break_on = 0; + /* FIXME: return errors */ keyspan_send_setup(port, 0); + + return 0; } diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index 6fd15cd9e1eb..0eef358b314a 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -299,7 +299,7 @@ static speed_t keyspan_pda_setbaud(struct usb_serial *serial, speed_t baud) return baud; } -static void keyspan_pda_break_ctl(struct tty_struct *tty, int break_state) +static int keyspan_pda_break_ctl(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; struct usb_serial *serial = port->serial; @@ -315,9 +315,13 @@ static void keyspan_pda_break_ctl(struct tty_struct *tty, int break_state) 4, /* set break */ USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT, value, 0, NULL, 0, 2000); - if (result < 0) + if (result < 0) { dev_dbg(&port->dev, "%s - error %d from usb_control_msg\n", __func__, result); + return result; + } + + return 0; } static void keyspan_pda_set_termios(struct tty_struct *tty, diff --git a/drivers/usb/serial/mct_u232.c b/drivers/usb/serial/mct_u232.c index d3852feb81a4..6570c8817a80 100644 --- a/drivers/usb/serial/mct_u232.c +++ b/drivers/usb/serial/mct_u232.c @@ -47,7 +47,7 @@ static void mct_u232_read_int_callback(struct urb *urb); static void mct_u232_set_termios(struct tty_struct *tty, struct usb_serial_port *port, const struct ktermios *old_termios); -static void mct_u232_break_ctl(struct tty_struct *tty, int break_state); +static int mct_u232_break_ctl(struct tty_struct *tty, int break_state); static int mct_u232_tiocmget(struct tty_struct *tty); static int mct_u232_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear); @@ -677,7 +677,7 @@ static void mct_u232_set_termios(struct tty_struct *tty, spin_unlock_irqrestore(&priv->lock, flags); } /* mct_u232_set_termios */ -static void mct_u232_break_ctl(struct tty_struct *tty, int break_state) +static int mct_u232_break_ctl(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; struct mct_u232_private *priv = usb_get_serial_port_data(port); @@ -691,7 +691,7 @@ static void mct_u232_break_ctl(struct tty_struct *tty, int break_state) lcr |= MCT_U232_SET_BREAK; spin_unlock_irqrestore(&priv->lock, flags); - mct_u232_set_line_ctrl(port, lcr); + return mct_u232_set_line_ctrl(port, lcr); } /* mct_u232_break_ctl */ diff --git a/drivers/usb/serial/mos7720.c b/drivers/usb/serial/mos7720.c index 1d1f85fabc28..23544074eb1c 100644 --- a/drivers/usb/serial/mos7720.c +++ b/drivers/usb/serial/mos7720.c @@ -996,7 +996,7 @@ static void mos7720_close(struct usb_serial_port *port) mos7720_port->open = 0; } -static void mos7720_break(struct tty_struct *tty, int break_state) +static int mos7720_break(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; unsigned char data; @@ -1007,7 +1007,7 @@ static void mos7720_break(struct tty_struct *tty, int break_state) mos7720_port = usb_get_serial_port_data(port); if (mos7720_port == NULL) - return; + return -ENODEV; if (break_state == -1) data = mos7720_port->shadowLCR | UART_LCR_SBC; @@ -1015,8 +1015,9 @@ static void mos7720_break(struct tty_struct *tty, int break_state) data = mos7720_port->shadowLCR & ~UART_LCR_SBC; mos7720_port->shadowLCR = data; - write_mos_reg(serial, port->port_number, MOS7720_LCR, - mos7720_port->shadowLCR); + + return write_mos_reg(serial, port->port_number, MOS7720_LCR, + mos7720_port->shadowLCR); } /* diff --git a/drivers/usb/serial/mos7840.c b/drivers/usb/serial/mos7840.c index 73370eaae0ab..8b0308d84270 100644 --- a/drivers/usb/serial/mos7840.c +++ b/drivers/usb/serial/mos7840.c @@ -787,7 +787,7 @@ static void mos7840_close(struct usb_serial_port *port) * mos7840_break * this function sends a break to the port *****************************************************************************/ -static void mos7840_break(struct tty_struct *tty, int break_state) +static int mos7840_break(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; struct moschip_port *mos7840_port = usb_get_serial_port_data(port); @@ -801,8 +801,9 @@ static void mos7840_break(struct tty_struct *tty, int break_state) /* FIXME: no locking on shadowLCR anywhere in driver */ mos7840_port->shadowLCR = data; dev_dbg(&port->dev, "%s mos7840_port->shadowLCR is %x\n", __func__, mos7840_port->shadowLCR); - mos7840_set_uart_reg(port, LINE_CONTROL_REGISTER, - mos7840_port->shadowLCR); + + return mos7840_set_uart_reg(port, LINE_CONTROL_REGISTER, + mos7840_port->shadowLCR); } /***************************************************************************** diff --git a/drivers/usb/serial/mxuport.c b/drivers/usb/serial/mxuport.c index faa0eedfe245..1f7bb3e4fcf2 100644 --- a/drivers/usb/serial/mxuport.c +++ b/drivers/usb/serial/mxuport.c @@ -1230,7 +1230,7 @@ static void mxuport_close(struct usb_serial_port *port) } /* Send a break to the port. */ -static void mxuport_break_ctl(struct tty_struct *tty, int break_state) +static int mxuport_break_ctl(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; struct usb_serial *serial = port->serial; @@ -1244,8 +1244,8 @@ static void mxuport_break_ctl(struct tty_struct *tty, int break_state) dev_dbg(&port->dev, "%s - clearing break\n", __func__); } - mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_BREAK, - enable, port->port_number); + return mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_BREAK, + enable, port->port_number); } static int mxuport_resume(struct usb_serial *serial) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index fd42e3a0bd18..288a96a74266 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -1151,6 +1151,10 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE(QUALCOMM_VENDOR_ID, 0x90fa), .driver_info = RSVD(3) }, /* u-blox products */ + { USB_DEVICE(UBLOX_VENDOR_ID, 0x1311) }, /* u-blox LARA-R6 01B */ + { USB_DEVICE(UBLOX_VENDOR_ID, 0x1312), /* u-blox LARA-R6 01B (RMNET) */ + .driver_info = RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(UBLOX_VENDOR_ID, 0x1313, 0xff) }, /* u-blox LARA-R6 01B (ECM) */ { USB_DEVICE(UBLOX_VENDOR_ID, 0x1341) }, /* u-blox LARA-L6 */ { USB_DEVICE(UBLOX_VENDOR_ID, 0x1342), /* u-blox LARA-L6 (RMNET) */ .driver_info = RSVD(4) }, diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c index 8949c1891164..d93f5d584557 100644 --- a/drivers/usb/serial/pl2303.c +++ b/drivers/usb/serial/pl2303.c @@ -173,7 +173,7 @@ MODULE_DEVICE_TABLE(usb, id_table); #define PL2303_HXN_FLOWCTRL_RTS_CTS 0x18 #define PL2303_HXN_FLOWCTRL_XON_XOFF 0x0c -static void pl2303_set_break(struct usb_serial_port *port, bool enable); +static int pl2303_set_break(struct usb_serial_port *port, bool enable); enum pl2303_type { TYPE_H, @@ -1060,7 +1060,7 @@ static int pl2303_carrier_raised(struct usb_serial_port *port) return 0; } -static void pl2303_set_break(struct usb_serial_port *port, bool enable) +static int pl2303_set_break(struct usb_serial_port *port, bool enable) { struct usb_serial *serial = port->serial; u16 state; @@ -1077,15 +1077,19 @@ static void pl2303_set_break(struct usb_serial_port *port, bool enable) result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), BREAK_REQUEST, BREAK_REQUEST_TYPE, state, 0, NULL, 0, 100); - if (result) + if (result) { dev_err(&port->dev, "error sending break = %d\n", result); + return result; + } + + return 0; } -static void pl2303_break_ctl(struct tty_struct *tty, int state) +static int pl2303_break_ctl(struct tty_struct *tty, int state) { struct usb_serial_port *port = tty->driver_data; - pl2303_set_break(port, state); + return pl2303_set_break(port, state); } static void pl2303_update_line_status(struct usb_serial_port *port, diff --git a/drivers/usb/serial/quatech2.c b/drivers/usb/serial/quatech2.c index fee581409bf6..821f25e52ec2 100644 --- a/drivers/usb/serial/quatech2.c +++ b/drivers/usb/serial/quatech2.c @@ -741,7 +741,7 @@ static int qt2_tiocmset(struct tty_struct *tty, return update_mctrl(port_priv, set, clear); } -static void qt2_break_ctl(struct tty_struct *tty, int break_state) +static int qt2_break_ctl(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; struct qt2_port_private *port_priv; @@ -754,10 +754,14 @@ static void qt2_break_ctl(struct tty_struct *tty, int break_state) status = qt2_control_msg(port->serial->dev, QT2_BREAK_CONTROL, val, port_priv->device_port); - if (status < 0) + if (status < 0) { dev_warn(&port->dev, "%s - failed to send control message: %i\n", __func__, status); + return status; + } + + return 0; } diff --git a/drivers/usb/serial/ti_usb_3410_5052.c b/drivers/usb/serial/ti_usb_3410_5052.c index b99f78224846..0fba25abf671 100644 --- a/drivers/usb/serial/ti_usb_3410_5052.c +++ b/drivers/usb/serial/ti_usb_3410_5052.c @@ -319,7 +319,7 @@ static void ti_set_termios(struct tty_struct *tty, static int ti_tiocmget(struct tty_struct *tty); static int ti_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear); -static void ti_break(struct tty_struct *tty, int break_state); +static int ti_break(struct tty_struct *tty, int break_state); static void ti_interrupt_callback(struct urb *urb); static void ti_bulk_in_callback(struct urb *urb); static void ti_bulk_out_callback(struct urb *urb); @@ -1071,7 +1071,7 @@ static int ti_tiocmset(struct tty_struct *tty, } -static void ti_break(struct tty_struct *tty, int break_state) +static int ti_break(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; struct ti_port *tport = usb_get_serial_port_data(port); @@ -1083,8 +1083,12 @@ static void ti_break(struct tty_struct *tty, int break_state) tport->tp_uart_base_addr + TI_UART_OFFSET_LCR, TI_LCR_BREAK, break_state == -1 ? TI_LCR_BREAK : 0); - if (status) + if (status) { dev_dbg(&port->dev, "%s - error setting break, %d\n", __func__, status); + return status; + } + + return 0; } static int ti_get_port_from_code(unsigned char code) diff --git a/drivers/usb/serial/upd78f0730.c b/drivers/usb/serial/upd78f0730.c index c47439bd90fa..46952182e04f 100644 --- a/drivers/usb/serial/upd78f0730.c +++ b/drivers/usb/serial/upd78f0730.c @@ -238,12 +238,13 @@ static int upd78f0730_tiocmset(struct tty_struct *tty, return res; } -static void upd78f0730_break_ctl(struct tty_struct *tty, int break_state) +static int upd78f0730_break_ctl(struct tty_struct *tty, int break_state) { struct upd78f0730_port_private *private; struct usb_serial_port *port = tty->driver_data; struct upd78f0730_set_dtr_rts request; struct device *dev = &port->dev; + int res; private = usb_get_serial_port_data(port); @@ -258,8 +259,10 @@ static void upd78f0730_break_ctl(struct tty_struct *tty, int break_state) request.opcode = UPD78F0730_CMD_SET_DTR_RTS; request.params = private->line_signals; - upd78f0730_send_ctl(port, &request, sizeof(request)); + res = upd78f0730_send_ctl(port, &request, sizeof(request)); mutex_unlock(&private->lock); + + return res; } static void upd78f0730_dtr_rts(struct usb_serial_port *port, int on) diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index f8404073558b..7b4805c1004d 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -539,9 +539,9 @@ static int serial_break(struct tty_struct *tty, int break_state) dev_dbg(&port->dev, "%s\n", __func__); if (port->serial->type->break_ctl) - port->serial->type->break_ctl(tty, break_state); + return port->serial->type->break_ctl(tty, break_state); - return 0; + return -ENOTTY; } static int serial_proc_show(struct seq_file *m, void *v) diff --git a/drivers/usb/serial/usb_debug.c b/drivers/usb/serial/usb_debug.c index aaf4813e4971..6934970f180d 100644 --- a/drivers/usb/serial/usb_debug.c +++ b/drivers/usb/serial/usb_debug.c @@ -47,12 +47,19 @@ MODULE_DEVICE_TABLE(usb, id_table_combined); /* This HW really does not support a serial break, so one will be * emulated when ever the break state is set to true. */ -static void usb_debug_break_ctl(struct tty_struct *tty, int break_state) +static int usb_debug_break_ctl(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; + int ret; + if (!break_state) - return; - usb_serial_generic_write(tty, port, USB_DEBUG_BRK, USB_DEBUG_BRK_SIZE); + return 0; + + ret = usb_serial_generic_write(tty, port, USB_DEBUG_BRK, USB_DEBUG_BRK_SIZE); + if (ret < 0) + return ret; + + return 0; } static void usb_debug_process_read_urb(struct urb *urb) diff --git a/drivers/usb/serial/whiteheat.c b/drivers/usb/serial/whiteheat.c index 7f82d40753ee..ca48e90a8e81 100644 --- a/drivers/usb/serial/whiteheat.c +++ b/drivers/usb/serial/whiteheat.c @@ -87,7 +87,7 @@ static void whiteheat_set_termios(struct tty_struct *tty, static int whiteheat_tiocmget(struct tty_struct *tty); static int whiteheat_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear); -static void whiteheat_break_ctl(struct tty_struct *tty, int break_state); +static int whiteheat_break_ctl(struct tty_struct *tty, int break_state); static struct usb_serial_driver whiteheat_fake_device = { .driver = { @@ -449,10 +449,11 @@ static void whiteheat_set_termios(struct tty_struct *tty, firm_setup_port(tty); } -static void whiteheat_break_ctl(struct tty_struct *tty, int break_state) +static int whiteheat_break_ctl(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; - firm_set_break(port, break_state); + + return firm_set_break(port, break_state); } diff --git a/drivers/usb/serial/xr_serial.c b/drivers/usb/serial/xr_serial.c index fdb0aae546c3..4ec7c5892b84 100644 --- a/drivers/usb/serial/xr_serial.c +++ b/drivers/usb/serial/xr_serial.c @@ -503,7 +503,7 @@ static void xr_dtr_rts(struct usb_serial_port *port, int on) xr_tiocmset_port(port, 0, TIOCM_DTR | TIOCM_RTS); } -static void xr_break_ctl(struct tty_struct *tty, int break_state) +static int xr_break_ctl(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; struct xr_data *data = usb_get_serial_port_data(port); @@ -517,7 +517,7 @@ static void xr_break_ctl(struct tty_struct *tty, int break_state) dev_dbg(&port->dev, "Turning break %s\n", state == 0 ? "off" : "on"); - xr_set_reg_uart(port, type->tx_break, state); + return xr_set_reg_uart(port, type->tx_break, state); } /* Tx and Rx clock mask values obtained from section 3.3.4 of datasheet */ diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig index 831e7049977d..2f80c2792dbd 100644 --- a/drivers/usb/typec/Kconfig +++ b/drivers/usb/typec/Kconfig @@ -100,19 +100,6 @@ config TYPEC_STUSB160X If you choose to build this driver as a dynamically linked module, the module will be called stusb160x.ko. -config TYPEC_QCOM_PMIC - tristate "Qualcomm PMIC USB Type-C driver" - depends on ARCH_QCOM || COMPILE_TEST - depends on USB_ROLE_SWITCH || !USB_ROLE_SWITCH - help - Driver for supporting role switch over the Qualcomm PMIC. This will - handle the USB Type-C role and orientation detection reported by the - QCOM PMIC if the PMIC has the capability to handle USB Type-C - detection. - - It will also enable the VBUS output to connected devices when a - DFP connection is made. - config TYPEC_WUSB3801 tristate "Willsemi WUSB3801 Type-C port controller driver" depends on I2C diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile index 4a83dad51a6c..7a368fea61bc 100644 --- a/drivers/usb/typec/Makefile +++ b/drivers/usb/typec/Makefile @@ -8,7 +8,6 @@ obj-$(CONFIG_TYPEC_UCSI) += ucsi/ obj-$(CONFIG_TYPEC_TPS6598X) += tipd/ obj-$(CONFIG_TYPEC_ANX7411) += anx7411.o obj-$(CONFIG_TYPEC_HD3SS3220) += hd3ss3220.o -obj-$(CONFIG_TYPEC_QCOM_PMIC) += qcom-pmic-typec.o obj-$(CONFIG_TYPEC_STUSB160X) += stusb160x.o obj-$(CONFIG_TYPEC_RT1719) += rt1719.o obj-$(CONFIG_TYPEC_WUSB3801) += wusb3801.o diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c index 3d5edce270a4..221604f933a4 100644 --- a/drivers/usb/typec/anx7411.c +++ b/drivers/usb/typec/anx7411.c @@ -1584,7 +1584,7 @@ static struct i2c_driver anx7411_driver = { .of_match_table = anx_match_table, .pm = &anx7411_pm_ops, }, - .probe_new = anx7411_i2c_probe, + .probe = anx7411_i2c_probe, .remove = anx7411_i2c_remove, .id_table = anx7411_id, diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 349cc2030c90..faa184ae3dac 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -2110,7 +2110,7 @@ typec_port_register_altmode(struct typec_port *port, struct typec_mux *mux; struct typec_retimer *retimer; - mux = typec_mux_get(&port->dev, desc); + mux = typec_mux_get(&port->dev); if (IS_ERR(mux)) return ERR_CAST(mux); @@ -2274,7 +2274,7 @@ struct typec_port *typec_register_port(struct device *parent, return ERR_PTR(ret); } - port->mux = typec_mux_get(&port->dev, NULL); + port->mux = typec_mux_get(&port->dev); if (IS_ERR(port->mux)) { ret = PTR_ERR(port->mux); put_device(&port->dev); diff --git a/drivers/usb/typec/hd3ss3220.c b/drivers/usb/typec/hd3ss3220.c index 8bbeb9b1e439..fb1242e82ffd 100644 --- a/drivers/usb/typec/hd3ss3220.c +++ b/drivers/usb/typec/hd3ss3220.c @@ -292,8 +292,8 @@ static struct i2c_driver hd3ss3220_driver = { .name = "hd3ss3220", .of_match_table = dev_ids, }, - .probe_new = hd3ss3220_probe, - .remove = hd3ss3220_remove, + .probe = hd3ss3220_probe, + .remove = hd3ss3220_remove, }; module_i2c_driver(hd3ss3220_driver); diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c index d9eaf9a0b0bf..80dd91938d96 100644 --- a/drivers/usb/typec/mux.c +++ b/drivers/usb/typec/mux.c @@ -265,60 +265,19 @@ static int mux_fwnode_match(struct device *dev, const void *fwnode) static void *typec_mux_match(const struct fwnode_handle *fwnode, const char *id, void *data) { - const struct typec_altmode_desc *desc = data; struct device *dev; - bool match; - int nval; - u16 *val; - int ret; - int i; /* - * Check has the identifier already been "consumed". If it - * has, no need to do any extra connection identification. + * Device graph (OF graph) does not give any means to identify the + * device type or the device class of the remote port parent that @fwnode + * represents, so in order to identify the type or the class of @fwnode + * an additional device property is needed. With typec muxes the + * property is named "mode-switch" (@id). The value of the device + * property is ignored. */ - match = !id; - if (match) - goto find_mux; - - if (!desc) { - /* - * Accessory Mode muxes & muxes which explicitly specify - * the required identifier can avoid SVID matching. - */ - match = fwnode_property_present(fwnode, "accessory") || - fwnode_property_present(fwnode, id); - if (match) - goto find_mux; - return NULL; - } - - /* Alternate Mode muxes */ - nval = fwnode_property_count_u16(fwnode, "svid"); - if (nval <= 0) + if (id && !fwnode_property_present(fwnode, id)) return NULL; - val = kcalloc(nval, sizeof(*val), GFP_KERNEL); - if (!val) - return ERR_PTR(-ENOMEM); - - ret = fwnode_property_read_u16_array(fwnode, "svid", val, nval); - if (ret < 0) { - kfree(val); - return ERR_PTR(ret); - } - - for (i = 0; i < nval; i++) { - match = val[i] == desc->svid; - if (match) { - kfree(val); - goto find_mux; - } - } - kfree(val); - return NULL; - -find_mux: dev = class_find_device(&typec_mux_class, NULL, fwnode, mux_fwnode_match); @@ -328,15 +287,13 @@ find_mux: /** * fwnode_typec_mux_get - Find USB Type-C Multiplexer * @fwnode: The caller device node - * @desc: Alt Mode description * * Finds a mux linked to the caller. This function is primarily meant for the * Type-C drivers. Returns a reference to the mux on success, NULL if no * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection * was found but the mux has not been enumerated yet. */ -struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode, - const struct typec_altmode_desc *desc) +struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode) { struct typec_mux_dev *mux_devs[TYPEC_MUX_MAX_DEVS]; struct typec_mux *mux; @@ -349,7 +306,7 @@ struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode, return ERR_PTR(-ENOMEM); count = fwnode_connection_find_matches(fwnode, "mode-switch", - (void *)desc, typec_mux_match, + NULL, typec_mux_match, (void **)mux_devs, ARRAY_SIZE(mux_devs)); if (count <= 0) { diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig index c46fa4f9d3df..784b9d8107e9 100644 --- a/drivers/usb/typec/mux/Kconfig +++ b/drivers/usb/typec/mux/Kconfig @@ -35,4 +35,14 @@ config TYPEC_MUX_INTEL_PMC control the USB role switch and also the multiplexer/demultiplexer switches used with USB Type-C Alternate Modes. +config TYPEC_MUX_NB7VPQ904M + tristate "On Semiconductor NB7VPQ904M 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 On Semiconductor NB7VPQ904M Type-C + redriver chip found on some devices with a Type-C port. + endmenu diff --git a/drivers/usb/typec/mux/Makefile b/drivers/usb/typec/mux/Makefile index dda67e19b58b..76196096ef41 100644 --- a/drivers/usb/typec/mux/Makefile +++ b/drivers/usb/typec/mux/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_TYPEC_MUX_FSA4480) += fsa4480.o 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 diff --git a/drivers/usb/typec/mux/fsa4480.c b/drivers/usb/typec/mux/fsa4480.c index d6495e533e58..e0ee1f621abb 100644 --- a/drivers/usb/typec/mux/fsa4480.c +++ b/drivers/usb/typec/mux/fsa4480.c @@ -25,15 +25,24 @@ #define FSA4480_DELAY_L_MIC 0x0e #define FSA4480_DELAY_L_SENSE 0x0f #define FSA4480_DELAY_L_AGND 0x10 +#define FSA4480_FUNCTION_ENABLE 0x12 #define FSA4480_RESET 0x1e #define FSA4480_MAX_REGISTER 0x1f #define FSA4480_ENABLE_DEVICE BIT(7) #define FSA4480_ENABLE_SBU GENMASK(6, 5) #define FSA4480_ENABLE_USB GENMASK(4, 3) +#define FSA4480_ENABLE_SENSE BIT(2) +#define FSA4480_ENABLE_MIC BIT(1) +#define FSA4480_ENABLE_AGND BIT(0) #define FSA4480_SEL_SBU_REVERSE GENMASK(6, 5) #define FSA4480_SEL_USB GENMASK(4, 3) +#define FSA4480_SEL_SENSE BIT(2) +#define FSA4480_SEL_MIC BIT(1) +#define FSA4480_SEL_AGND BIT(0) + +#define FSA4480_ENABLE_AUTO_JACK_DETECT BIT(0) struct fsa4480 { struct i2c_client *client; @@ -46,8 +55,11 @@ struct fsa4480 { struct regmap *regmap; + enum typec_orientation orientation; + unsigned long mode; + unsigned int svid; + u8 cur_enable; - u8 cur_select; }; static const struct regmap_config fsa4480_regmap_config = { @@ -58,19 +70,45 @@ static const struct regmap_config fsa4480_regmap_config = { .disable_locking = true, }; -static int fsa4480_switch_set(struct typec_switch_dev *sw, - enum typec_orientation orientation) +static int fsa4480_set(struct fsa4480 *fsa) { - struct fsa4480 *fsa = typec_switch_get_drvdata(sw); - u8 new_sel; - - mutex_lock(&fsa->lock); - new_sel = FSA4480_SEL_USB; - if (orientation == TYPEC_ORIENTATION_REVERSE) - new_sel |= FSA4480_SEL_SBU_REVERSE; - - if (new_sel == fsa->cur_select) - goto out_unlock; + bool reverse = (fsa->orientation == TYPEC_ORIENTATION_REVERSE); + u8 enable = FSA4480_ENABLE_DEVICE; + u8 sel = 0; + + /* USB Mode */ + if (fsa->mode < TYPEC_STATE_MODAL || + (!fsa->svid && (fsa->mode == TYPEC_MODE_USB2 || + fsa->mode == TYPEC_MODE_USB3))) { + enable |= FSA4480_ENABLE_USB; + sel = FSA4480_SEL_USB; + } else if (fsa->svid) { + switch (fsa->mode) { + /* DP Only */ + case TYPEC_DP_STATE_C: + case TYPEC_DP_STATE_E: + enable |= FSA4480_ENABLE_SBU; + if (reverse) + sel = FSA4480_SEL_SBU_REVERSE; + break; + + /* DP + USB */ + case TYPEC_DP_STATE_D: + case TYPEC_DP_STATE_F: + enable |= FSA4480_ENABLE_USB | FSA4480_ENABLE_SBU; + sel = FSA4480_SEL_USB; + if (reverse) + sel |= FSA4480_SEL_SBU_REVERSE; + break; + + default: + return -EOPNOTSUPP; + } + } else if (fsa->mode == TYPEC_MODE_AUDIO) { + /* Audio Accessory Mode, setup to auto Jack Detection */ + enable |= FSA4480_ENABLE_USB | FSA4480_ENABLE_AGND; + } else + return -EOPNOTSUPP; if (fsa->cur_enable & FSA4480_ENABLE_SBU) { /* Disable SBU output while re-configuring the switch */ @@ -81,48 +119,64 @@ static int fsa4480_switch_set(struct typec_switch_dev *sw, usleep_range(35, 1000); } - regmap_write(fsa->regmap, FSA4480_SWITCH_SELECT, new_sel); - fsa->cur_select = new_sel; + regmap_write(fsa->regmap, FSA4480_SWITCH_SELECT, sel); + regmap_write(fsa->regmap, FSA4480_SWITCH_ENABLE, enable); - if (fsa->cur_enable & FSA4480_ENABLE_SBU) { - regmap_write(fsa->regmap, FSA4480_SWITCH_ENABLE, fsa->cur_enable); + /* Start AUDIO JACK DETECTION to setup MIC, AGND & Sense muxes */ + if (enable & FSA4480_ENABLE_AGND) + regmap_write(fsa->regmap, FSA4480_FUNCTION_ENABLE, + FSA4480_ENABLE_AUTO_JACK_DETECT); + if (enable & FSA4480_ENABLE_SBU) { /* 15us to allow the SBU switch to turn on again */ usleep_range(15, 1000); } -out_unlock: - mutex_unlock(&fsa->lock); + fsa->cur_enable = enable; return 0; } +static int fsa4480_switch_set(struct typec_switch_dev *sw, + enum typec_orientation orientation) +{ + struct fsa4480 *fsa = typec_switch_get_drvdata(sw); + int ret = 0; + + mutex_lock(&fsa->lock); + + if (fsa->orientation != orientation) { + fsa->orientation = orientation; + + ret = fsa4480_set(fsa); + } + + mutex_unlock(&fsa->lock); + + return ret; +} + static int fsa4480_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) { struct fsa4480 *fsa = typec_mux_get_drvdata(mux); - u8 new_enable; + int ret = 0; mutex_lock(&fsa->lock); - new_enable = FSA4480_ENABLE_DEVICE | FSA4480_ENABLE_USB; - if (state->mode >= TYPEC_DP_STATE_A) - new_enable |= FSA4480_ENABLE_SBU; + if (fsa->mode != state->mode) { + fsa->mode = state->mode; - if (new_enable == fsa->cur_enable) - goto out_unlock; + if (state->alt) + fsa->svid = state->alt->svid; + else + fsa->svid = 0; // No SVID - regmap_write(fsa->regmap, FSA4480_SWITCH_ENABLE, new_enable); - fsa->cur_enable = new_enable; - - if (new_enable & FSA4480_ENABLE_SBU) { - /* 15us to allow the SBU switch to turn off */ - usleep_range(15, 1000); + ret = fsa4480_set(fsa); } -out_unlock: mutex_unlock(&fsa->lock); - return 0; + return ret; } static int fsa4480_probe(struct i2c_client *client) @@ -143,8 +197,10 @@ static int fsa4480_probe(struct i2c_client *client) if (IS_ERR(fsa->regmap)) return dev_err_probe(dev, PTR_ERR(fsa->regmap), "failed to initialize regmap\n"); + /* Safe mode */ fsa->cur_enable = FSA4480_ENABLE_DEVICE | FSA4480_ENABLE_USB; - fsa->cur_select = FSA4480_SEL_USB; + fsa->mode = TYPEC_STATE_SAFE; + fsa->orientation = TYPEC_ORIENTATION_NONE; /* set default settings */ regmap_write(fsa->regmap, FSA4480_SLOW_L, 0x00); @@ -156,7 +212,7 @@ static int fsa4480_probe(struct i2c_client *client) regmap_write(fsa->regmap, FSA4480_DELAY_L_MIC, 0x00); regmap_write(fsa->regmap, FSA4480_DELAY_L_SENSE, 0x00); regmap_write(fsa->regmap, FSA4480_DELAY_L_AGND, 0x09); - regmap_write(fsa->regmap, FSA4480_SWITCH_SELECT, fsa->cur_select); + regmap_write(fsa->regmap, FSA4480_SWITCH_SELECT, FSA4480_SEL_USB); regmap_write(fsa->regmap, FSA4480_SWITCH_ENABLE, fsa->cur_enable); sw_desc.drvdata = fsa; @@ -206,7 +262,7 @@ static struct i2c_driver fsa4480_driver = { .name = "fsa4480", .of_match_table = fsa4480_of_table, }, - .probe_new = fsa4480_probe, + .probe = fsa4480_probe, .remove = fsa4480_remove, .id_table = fsa4480_table, }; diff --git a/drivers/usb/typec/mux/gpio-sbu-mux.c b/drivers/usb/typec/mux/gpio-sbu-mux.c index f62516dafe8f..ad60fd4e8431 100644 --- a/drivers/usb/typec/mux/gpio-sbu-mux.c +++ b/drivers/usb/typec/mux/gpio-sbu-mux.c @@ -3,14 +3,11 @@ * Copyright (C) 2022 Linaro Ltd. */ -#include <linux/bits.h> -#include <linux/i2c.h> -#include <linux/kernel.h> +#include <linux/device.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/gpio/consumer.h> #include <linux/platform_device.h> -#include <linux/regmap.h> #include <linux/usb/typec_dp.h> #include <linux/usb/typec_mux.h> @@ -140,7 +137,7 @@ static int gpio_sbu_mux_probe(struct platform_device *pdev) return 0; } -static int gpio_sbu_mux_remove(struct platform_device *pdev) +static void gpio_sbu_mux_remove(struct platform_device *pdev) { struct gpio_sbu_mux *sbu_mux = platform_get_drvdata(pdev); @@ -148,8 +145,6 @@ static int gpio_sbu_mux_remove(struct platform_device *pdev) typec_mux_unregister(sbu_mux->mux); typec_switch_unregister(sbu_mux->sw); - - return 0; } static const struct of_device_id gpio_sbu_mux_match[] = { @@ -160,7 +155,7 @@ MODULE_DEVICE_TABLE(of, gpio_sbu_mux_match); static struct platform_driver gpio_sbu_mux_driver = { .probe = gpio_sbu_mux_probe, - .remove = gpio_sbu_mux_remove, + .remove_new = gpio_sbu_mux_remove, .driver = { .name = "gpio_sbu_mux", .of_match_table = gpio_sbu_mux_match, diff --git a/drivers/usb/typec/mux/intel_pmc_mux.c b/drivers/usb/typec/mux/intel_pmc_mux.c index 34e4188a40ff..5e8edf3881c0 100644 --- a/drivers/usb/typec/mux/intel_pmc_mux.c +++ b/drivers/usb/typec/mux/intel_pmc_mux.c @@ -15,6 +15,8 @@ #include <linux/usb/typec_mux.h> #include <linux/usb/typec_dp.h> #include <linux/usb/typec_tbt.h> +#include <linux/debugfs.h> +#include <linux/usb.h> #include <asm/intel_scu_ipc.h> @@ -143,8 +145,12 @@ struct pmc_usb { struct acpi_device *iom_adev; void __iomem *iom_base; u32 iom_port_status_offset; + + struct dentry *dentry; }; +static struct dentry *pmc_mux_debugfs_root; + static void update_port_status(struct pmc_usb_port *port) { u8 port_num; @@ -639,6 +645,29 @@ static int pmc_usb_probe_iom(struct pmc_usb *pmc) return 0; } +static int port_iom_status_show(struct seq_file *s, void *unused) +{ + struct pmc_usb_port *port = s->private; + + update_port_status(port); + seq_printf(s, "0x%08x\n", port->iom_status); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(port_iom_status); + +static void pmc_mux_port_debugfs_init(struct pmc_usb_port *port) +{ + struct dentry *debugfs_dir; + char name[6]; + + snprintf(name, sizeof(name), "port%d", port->usb3_port - 1); + + debugfs_dir = debugfs_create_dir(name, port->pmc->dentry); + debugfs_create_file("iom_status", 0400, debugfs_dir, port, + &port_iom_status_fops); +} + static int pmc_usb_probe(struct platform_device *pdev) { struct fwnode_handle *fwnode = NULL; @@ -674,6 +703,8 @@ static int pmc_usb_probe(struct platform_device *pdev) if (ret) return ret; + pmc->dentry = debugfs_create_dir(dev_name(pmc->dev), pmc_mux_debugfs_root); + /* * For every physical USB connector (USB2 and USB3 combo) there is a * child ACPI device node under the PMC mux ACPI device object. @@ -688,6 +719,8 @@ static int pmc_usb_probe(struct platform_device *pdev) fwnode_handle_put(fwnode); goto err_remove_ports; } + + pmc_mux_port_debugfs_init(&pmc->port[i]); } platform_set_drvdata(pdev, pmc); @@ -703,10 +736,12 @@ err_remove_ports: acpi_dev_put(pmc->iom_adev); + debugfs_remove(pmc->dentry); + return ret; } -static int pmc_usb_remove(struct platform_device *pdev) +static void pmc_usb_remove(struct platform_device *pdev) { struct pmc_usb *pmc = platform_get_drvdata(pdev); int i; @@ -719,7 +754,7 @@ static int pmc_usb_remove(struct platform_device *pdev) acpi_dev_put(pmc->iom_adev); - return 0; + debugfs_remove(pmc->dentry); } static const struct acpi_device_id pmc_usb_acpi_ids[] = { @@ -734,10 +769,23 @@ static struct platform_driver pmc_usb_driver = { .acpi_match_table = ACPI_PTR(pmc_usb_acpi_ids), }, .probe = pmc_usb_probe, - .remove = pmc_usb_remove, + .remove_new = pmc_usb_remove, }; -module_platform_driver(pmc_usb_driver); +static int __init pmc_usb_init(void) +{ + pmc_mux_debugfs_root = debugfs_create_dir("intel_pmc_mux", usb_debug_root); + + return platform_driver_register(&pmc_usb_driver); +} +module_init(pmc_usb_init); + +static void __exit pmc_usb_exit(void) +{ + platform_driver_unregister(&pmc_usb_driver); + debugfs_remove(pmc_mux_debugfs_root); +} +module_exit(pmc_usb_exit); MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/typec/mux/nb7vpq904m.c b/drivers/usb/typec/mux/nb7vpq904m.c new file mode 100644 index 000000000000..80e580d50129 --- /dev/null +++ b/drivers/usb/typec/mux/nb7vpq904m.c @@ -0,0 +1,529 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * OnSemi NB7VPQ904M Type-C driver + * + * Copyright (C) 2023 Dmitry Baryshkov <dmitry.baryshkov@linaro.org> + */ +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/bitfield.h> +#include <linux/of_graph.h> +#include <drm/drm_bridge.h> +#include <linux/usb/typec_dp.h> +#include <linux/usb/typec_mux.h> +#include <linux/usb/typec_retimer.h> +#include <linux/gpio/consumer.h> +#include <linux/regulator/consumer.h> + +#define NB7_CHNA 0 +#define NB7_CHNB 1 +#define NB7_CHNC 2 +#define NB7_CHND 3 +#define NB7_IS_CHAN_AD(channel) (channel == NB7_CHNA || channel == NB7_CHND) + +#define GEN_DEV_SET_REG 0x00 + +#define GEN_DEV_SET_CHIP_EN BIT(0) +#define GEN_DEV_SET_CHNA_EN BIT(4) +#define GEN_DEV_SET_CHNB_EN BIT(5) +#define GEN_DEV_SET_CHNC_EN BIT(6) +#define GEN_DEV_SET_CHND_EN BIT(7) + +#define GEN_DEV_SET_OP_MODE_MASK GENMASK(3, 1) + +#define GEN_DEV_SET_OP_MODE_DP_CC2 0 +#define GEN_DEV_SET_OP_MODE_DP_CC1 1 +#define GEN_DEV_SET_OP_MODE_DP_4LANE 2 +#define GEN_DEV_SET_OP_MODE_USB 5 + +#define EQ_SETTING_REG_BASE 0x01 +#define EQ_SETTING_REG(n) (EQ_SETTING_REG_BASE + (n) * 2) +#define EQ_SETTING_MASK GENMASK(3, 1) + +#define OUTPUT_COMPRESSION_AND_POL_REG_BASE 0x02 +#define OUTPUT_COMPRESSION_AND_POL_REG(n) (OUTPUT_COMPRESSION_AND_POL_REG_BASE + (n) * 2) +#define OUTPUT_COMPRESSION_MASK GENMASK(2, 1) + +#define FLAT_GAIN_REG_BASE 0x18 +#define FLAT_GAIN_REG(n) (FLAT_GAIN_REG_BASE + (n) * 2) +#define FLAT_GAIN_MASK GENMASK(1, 0) + +#define LOSS_MATCH_REG_BASE 0x19 +#define LOSS_MATCH_REG(n) (LOSS_MATCH_REG_BASE + (n) * 2) +#define LOSS_MATCH_MASK GENMASK(1, 0) + +#define AUX_CC_REG 0x09 + +#define CHIP_VERSION_REG 0x17 + +struct nb7vpq904m { + struct i2c_client *client; + struct gpio_desc *enable_gpio; + struct regulator *vcc_supply; + struct regmap *regmap; + struct typec_switch_dev *sw; + struct typec_retimer *retimer; + + bool swap_data_lanes; + 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 void nb7vpq904m_set_channel(struct nb7vpq904m *nb7, unsigned int channel, bool dp) +{ + u8 eq, out_comp, flat_gain, loss_match; + + if (dp) { + eq = NB7_IS_CHAN_AD(channel) ? 0x6 : 0x4; + out_comp = 0x3; + flat_gain = NB7_IS_CHAN_AD(channel) ? 0x2 : 0x1; + loss_match = 0x3; + } else { + eq = 0x4; + out_comp = 0x3; + flat_gain = NB7_IS_CHAN_AD(channel) ? 0x3 : 0x1; + loss_match = NB7_IS_CHAN_AD(channel) ? 0x1 : 0x3; + } + + regmap_update_bits(nb7->regmap, EQ_SETTING_REG(channel), + EQ_SETTING_MASK, FIELD_PREP(EQ_SETTING_MASK, eq)); + regmap_update_bits(nb7->regmap, OUTPUT_COMPRESSION_AND_POL_REG(channel), + OUTPUT_COMPRESSION_MASK, FIELD_PREP(OUTPUT_COMPRESSION_MASK, out_comp)); + regmap_update_bits(nb7->regmap, FLAT_GAIN_REG(channel), + FLAT_GAIN_MASK, FIELD_PREP(FLAT_GAIN_MASK, flat_gain)); + regmap_update_bits(nb7->regmap, LOSS_MATCH_REG(channel), + LOSS_MATCH_MASK, FIELD_PREP(LOSS_MATCH_MASK, loss_match)); +} + +static int nb7vpq904m_set(struct nb7vpq904m *nb7) +{ + bool reverse = (nb7->orientation == TYPEC_ORIENTATION_REVERSE); + + switch (nb7->mode) { + case TYPEC_STATE_SAFE: + regmap_write(nb7->regmap, GEN_DEV_SET_REG, + GEN_DEV_SET_CHIP_EN | + GEN_DEV_SET_CHNA_EN | + GEN_DEV_SET_CHNB_EN | + GEN_DEV_SET_CHNC_EN | + GEN_DEV_SET_CHND_EN | + FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK, + GEN_DEV_SET_OP_MODE_USB)); + nb7vpq904m_set_channel(nb7, NB7_CHNA, false); + nb7vpq904m_set_channel(nb7, NB7_CHNB, false); + nb7vpq904m_set_channel(nb7, NB7_CHNC, false); + nb7vpq904m_set_channel(nb7, NB7_CHND, false); + regmap_write(nb7->regmap, AUX_CC_REG, 0x2); + + 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 + * + * Reversed if data lanes are swapped + */ + if (reverse ^ nb7->swap_data_lanes) { + regmap_write(nb7->regmap, GEN_DEV_SET_REG, + GEN_DEV_SET_CHIP_EN | + GEN_DEV_SET_CHNA_EN | + GEN_DEV_SET_CHNB_EN | + FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK, + GEN_DEV_SET_OP_MODE_USB)); + nb7vpq904m_set_channel(nb7, NB7_CHNA, false); + nb7vpq904m_set_channel(nb7, NB7_CHNB, false); + } else { + regmap_write(nb7->regmap, GEN_DEV_SET_REG, + GEN_DEV_SET_CHIP_EN | + GEN_DEV_SET_CHNC_EN | + GEN_DEV_SET_CHND_EN | + FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK, + GEN_DEV_SET_OP_MODE_USB)); + nb7vpq904m_set_channel(nb7, NB7_CHNC, false); + nb7vpq904m_set_channel(nb7, NB7_CHND, false); + } + regmap_write(nb7->regmap, AUX_CC_REG, 0x2); + + return 0; + + default: + if (nb7->svid != USB_TYPEC_DP_SID) + return -EINVAL; + + break; + } + + /* DP Altmode Setup */ + + regmap_write(nb7->regmap, AUX_CC_REG, reverse ? 0x1 : 0x0); + + switch (nb7->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 + */ + regmap_write(nb7->regmap, GEN_DEV_SET_REG, + GEN_DEV_SET_CHIP_EN | + GEN_DEV_SET_CHNA_EN | + GEN_DEV_SET_CHNB_EN | + GEN_DEV_SET_CHNC_EN | + GEN_DEV_SET_CHND_EN | + FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK, + GEN_DEV_SET_OP_MODE_DP_4LANE)); + nb7vpq904m_set_channel(nb7, NB7_CHNA, true); + nb7vpq904m_set_channel(nb7, NB7_CHNB, true); + nb7vpq904m_set_channel(nb7, NB7_CHNC, true); + nb7vpq904m_set_channel(nb7, NB7_CHND, true); + break; + + case TYPEC_DP_STATE_D: + case TYPEC_DP_STATE_F: + regmap_write(nb7->regmap, GEN_DEV_SET_REG, + GEN_DEV_SET_CHIP_EN | + GEN_DEV_SET_CHNA_EN | + GEN_DEV_SET_CHNB_EN | + GEN_DEV_SET_CHNC_EN | + GEN_DEV_SET_CHND_EN | + FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK, + reverse ^ nb7->swap_data_lanes ? + GEN_DEV_SET_OP_MODE_DP_CC2 + : GEN_DEV_SET_OP_MODE_DP_CC1)); + + /* + * 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 + * + * Reversed if data lanes are swapped + */ + if (nb7->swap_data_lanes) { + nb7vpq904m_set_channel(nb7, NB7_CHNA, !reverse); + nb7vpq904m_set_channel(nb7, NB7_CHNB, !reverse); + nb7vpq904m_set_channel(nb7, NB7_CHNC, reverse); + nb7vpq904m_set_channel(nb7, NB7_CHND, reverse); + } else { + nb7vpq904m_set_channel(nb7, NB7_CHNA, reverse); + nb7vpq904m_set_channel(nb7, NB7_CHNB, reverse); + nb7vpq904m_set_channel(nb7, NB7_CHNC, !reverse); + nb7vpq904m_set_channel(nb7, NB7_CHND, !reverse); + } + break; + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int nb7vpq904m_sw_set(struct typec_switch_dev *sw, enum typec_orientation orientation) +{ + struct nb7vpq904m *nb7 = typec_switch_get_drvdata(sw); + int ret; + + ret = typec_switch_set(nb7->typec_switch, orientation); + if (ret) + return ret; + + mutex_lock(&nb7->lock); + + if (nb7->orientation != orientation) { + nb7->orientation = orientation; + + ret = nb7vpq904m_set(nb7); + } + + mutex_unlock(&nb7->lock); + + return ret; +} + +static int nb7vpq904m_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state) +{ + struct nb7vpq904m *nb7 = typec_retimer_get_drvdata(retimer); + int ret = 0; + + mutex_lock(&nb7->lock); + + if (nb7->mode != state->mode) { + nb7->mode = state->mode; + + if (state->alt) + nb7->svid = state->alt->svid; + else + nb7->svid = 0; // No SVID + + ret = nb7vpq904m_set(nb7); + } + + mutex_unlock(&nb7->lock); + + return ret; +} + +#if IS_ENABLED(CONFIG_OF) && IS_ENABLED(CONFIG_DRM_PANEL_BRIDGE) +static int nb7vpq904m_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct nb7vpq904m *nb7 = container_of(bridge, struct nb7vpq904m, bridge); + struct drm_bridge *next_bridge; + + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) + return -EINVAL; + + next_bridge = devm_drm_of_get_bridge(&nb7->client->dev, nb7->client->dev.of_node, 0, 0); + if (IS_ERR(next_bridge)) { + dev_err(&nb7->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 nb7vpq904m_bridge_funcs = { + .attach = nb7vpq904m_bridge_attach, +}; + +static int nb7vpq904m_register_bridge(struct nb7vpq904m *nb7) +{ + nb7->bridge.funcs = &nb7vpq904m_bridge_funcs; + nb7->bridge.of_node = nb7->client->dev.of_node; + + return devm_drm_bridge_add(&nb7->client->dev, &nb7->bridge); +} +#else +static int nb7vpq904m_register_bridge(struct nb7vpq904m *nb7) +{ + return 0; +} +#endif + +static const struct regmap_config nb7_regmap = { + .max_register = 0x1f, + .reg_bits = 8, + .val_bits = 8, +}; + +enum { + NORMAL_LANE_MAPPING, + INVERT_LANE_MAPPING, +}; + +#define DATA_LANES_COUNT 4 + +static const int supported_data_lane_mapping[][DATA_LANES_COUNT] = { + [NORMAL_LANE_MAPPING] = { 0, 1, 2, 3 }, + [INVERT_LANE_MAPPING] = { 3, 2, 1, 0 }, +}; + +static int nb7vpq904m_parse_data_lanes_mapping(struct nb7vpq904m *nb7) +{ + struct device_node *ep; + u32 data_lanes[4]; + int ret, i, j; + + ep = of_graph_get_endpoint_by_regs(nb7->client->dev.of_node, 1, 0); + + if (ep) { + ret = of_property_count_u32_elems(ep, "data-lanes"); + if (ret == -EINVAL) + /* Property isn't here, consider default mapping */ + goto out_done; + if (ret < 0) + goto out_error; + + if (ret != DATA_LANES_COUNT) { + dev_err(&nb7->client->dev, "expected 4 data lanes\n"); + ret = -EINVAL; + goto out_error; + } + + ret = of_property_read_u32_array(ep, "data-lanes", data_lanes, DATA_LANES_COUNT); + if (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: + nb7->swap_data_lanes = true; + dev_info(&nb7->client->dev, "using inverted data lanes mapping\n"); + break; + default: + dev_err(&nb7->client->dev, "invalid data lanes mapping\n"); + ret = -EINVAL; + goto out_error; + } + } + +out_done: + ret = 0; + +out_error: + of_node_put(ep); + + return ret; +} + +static int nb7vpq904m_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct typec_switch_desc sw_desc = { }; + struct typec_retimer_desc retimer_desc = { }; + struct nb7vpq904m *nb7; + int ret; + + nb7 = devm_kzalloc(dev, sizeof(*nb7), GFP_KERNEL); + if (!nb7) + return -ENOMEM; + + nb7->client = client; + + nb7->regmap = devm_regmap_init_i2c(client, &nb7_regmap); + if (IS_ERR(nb7->regmap)) { + dev_err(&client->dev, "Failed to allocate register map\n"); + return PTR_ERR(nb7->regmap); + } + + nb7->mode = TYPEC_STATE_SAFE; + nb7->orientation = TYPEC_ORIENTATION_NONE; + + mutex_init(&nb7->lock); + + nb7->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(nb7->enable_gpio)) + return dev_err_probe(dev, PTR_ERR(nb7->enable_gpio), + "unable to acquire enable gpio\n"); + + nb7->vcc_supply = devm_regulator_get_optional(dev, "vcc"); + if (IS_ERR(nb7->vcc_supply)) + return PTR_ERR(nb7->vcc_supply); + + nb7->typec_switch = fwnode_typec_switch_get(dev->fwnode); + if (IS_ERR(nb7->typec_switch)) + return dev_err_probe(dev, PTR_ERR(nb7->typec_switch), + "failed to acquire orientation-switch\n"); + + ret = nb7vpq904m_parse_data_lanes_mapping(nb7); + if (ret) + return ret; + + ret = regulator_enable(nb7->vcc_supply); + if (ret) + dev_warn(dev, "Failed to enable vcc: %d\n", ret); + + gpiod_set_value(nb7->enable_gpio, 1); + + ret = nb7vpq904m_register_bridge(nb7); + if (ret) + return ret; + + sw_desc.drvdata = nb7; + sw_desc.fwnode = dev->fwnode; + sw_desc.set = nb7vpq904m_sw_set; + + nb7->sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(nb7->sw)) + return dev_err_probe(dev, PTR_ERR(nb7->sw), + "Error registering typec switch\n"); + + retimer_desc.drvdata = nb7; + retimer_desc.fwnode = dev->fwnode; + retimer_desc.set = nb7vpq904m_retimer_set; + + nb7->retimer = typec_retimer_register(dev, &retimer_desc); + if (IS_ERR(nb7->retimer)) { + typec_switch_unregister(nb7->sw); + return dev_err_probe(dev, PTR_ERR(nb7->retimer), + "Error registering typec retimer\n"); + } + + return 0; +} + +static void nb7vpq904m_remove(struct i2c_client *client) +{ + struct nb7vpq904m *nb7 = i2c_get_clientdata(client); + + typec_retimer_unregister(nb7->retimer); + typec_switch_unregister(nb7->sw); + + gpiod_set_value(nb7->enable_gpio, 0); + + regulator_disable(nb7->vcc_supply); +} + +static const struct i2c_device_id nb7vpq904m_table[] = { + { "nb7vpq904m" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, nb7vpq904m_table); + +static const struct of_device_id nb7vpq904m_of_table[] = { + { .compatible = "onnn,nb7vpq904m" }, + { } +}; +MODULE_DEVICE_TABLE(of, nb7vpq904m_of_table); + +static struct i2c_driver nb7vpq904m_driver = { + .driver = { + .name = "nb7vpq904m", + .of_match_table = nb7vpq904m_of_table, + }, + .probe_new = nb7vpq904m_probe, + .remove = nb7vpq904m_remove, + .id_table = nb7vpq904m_table, +}; + +module_i2c_driver(nb7vpq904m_driver); + +MODULE_AUTHOR("Dmitry Baryshkov <dmitry.baryshkov@linaro.org>"); +MODULE_DESCRIPTION("OnSemi NB7VPQ904M Type-C driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/mux/pi3usb30532.c b/drivers/usb/typec/mux/pi3usb30532.c index 1cd388b55c30..8eeec135dcdb 100644 --- a/drivers/usb/typec/mux/pi3usb30532.c +++ b/drivers/usb/typec/mux/pi3usb30532.c @@ -178,7 +178,7 @@ static struct i2c_driver pi3usb30532_driver = { .driver = { .name = "pi3usb30532", }, - .probe_new = pi3usb30532_probe, + .probe = pi3usb30532_probe, .remove = pi3usb30532_remove, .id_table = pi3usb30532_table, }; diff --git a/drivers/usb/typec/qcom-pmic-typec.c b/drivers/usb/typec/qcom-pmic-typec.c deleted file mode 100644 index 432ea62f1bab..000000000000 --- a/drivers/usb/typec/qcom-pmic-typec.c +++ /dev/null @@ -1,261 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (c) 2020, The Linux Foundation. All rights reserved. - */ - -#include <linux/err.h> -#include <linux/interrupt.h> -#include <linux/kernel.h> -#include <linux/mod_devicetable.h> -#include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/regmap.h> -#include <linux/regulator/consumer.h> -#include <linux/slab.h> -#include <linux/usb/role.h> -#include <linux/usb/typec_mux.h> - -#define TYPEC_MISC_STATUS 0xb -#define CC_ATTACHED BIT(0) -#define CC_ORIENTATION BIT(1) -#define SNK_SRC_MODE BIT(6) -#define TYPEC_MODE_CFG 0x44 -#define TYPEC_DISABLE_CMD BIT(0) -#define EN_SNK_ONLY BIT(1) -#define EN_SRC_ONLY BIT(2) -#define TYPEC_VCONN_CONTROL 0x46 -#define VCONN_EN_SRC BIT(0) -#define VCONN_EN_VAL BIT(1) -#define TYPEC_EXIT_STATE_CFG 0x50 -#define SEL_SRC_UPPER_REF BIT(2) -#define TYPEC_INTR_EN_CFG_1 0x5e -#define TYPEC_INTR_EN_CFG_1_MASK GENMASK(7, 0) - -struct qcom_pmic_typec { - struct device *dev; - struct regmap *regmap; - u32 base; - - struct typec_port *port; - struct usb_role_switch *role_sw; - - struct regulator *vbus_reg; - bool vbus_enabled; -}; - -static void qcom_pmic_typec_enable_vbus_regulator(struct qcom_pmic_typec - *qcom_usb, bool enable) -{ - int ret; - - if (enable == qcom_usb->vbus_enabled) - return; - - if (enable) { - ret = regulator_enable(qcom_usb->vbus_reg); - if (ret) - return; - } else { - ret = regulator_disable(qcom_usb->vbus_reg); - if (ret) - return; - } - qcom_usb->vbus_enabled = enable; -} - -static void qcom_pmic_typec_check_connection(struct qcom_pmic_typec *qcom_usb) -{ - enum typec_orientation orientation; - enum usb_role role; - unsigned int stat; - bool enable_vbus; - - regmap_read(qcom_usb->regmap, qcom_usb->base + TYPEC_MISC_STATUS, - &stat); - - if (stat & CC_ATTACHED) { - orientation = (stat & CC_ORIENTATION) ? - TYPEC_ORIENTATION_REVERSE : - TYPEC_ORIENTATION_NORMAL; - typec_set_orientation(qcom_usb->port, orientation); - - role = (stat & SNK_SRC_MODE) ? USB_ROLE_HOST : USB_ROLE_DEVICE; - if (role == USB_ROLE_HOST) - enable_vbus = true; - else - enable_vbus = false; - } else { - role = USB_ROLE_NONE; - enable_vbus = false; - } - - qcom_pmic_typec_enable_vbus_regulator(qcom_usb, enable_vbus); - usb_role_switch_set_role(qcom_usb->role_sw, role); -} - -static irqreturn_t qcom_pmic_typec_interrupt(int irq, void *_qcom_usb) -{ - struct qcom_pmic_typec *qcom_usb = _qcom_usb; - - qcom_pmic_typec_check_connection(qcom_usb); - return IRQ_HANDLED; -} - -static void qcom_pmic_typec_typec_hw_init(struct qcom_pmic_typec *qcom_usb, - enum typec_port_type type) -{ - u8 mode = 0; - - regmap_update_bits(qcom_usb->regmap, - qcom_usb->base + TYPEC_INTR_EN_CFG_1, - TYPEC_INTR_EN_CFG_1_MASK, 0); - - if (type == TYPEC_PORT_SRC) - mode = EN_SRC_ONLY; - else if (type == TYPEC_PORT_SNK) - mode = EN_SNK_ONLY; - - regmap_update_bits(qcom_usb->regmap, qcom_usb->base + TYPEC_MODE_CFG, - EN_SNK_ONLY | EN_SRC_ONLY, mode); - - regmap_update_bits(qcom_usb->regmap, - qcom_usb->base + TYPEC_VCONN_CONTROL, - VCONN_EN_SRC | VCONN_EN_VAL, VCONN_EN_SRC); - regmap_update_bits(qcom_usb->regmap, - qcom_usb->base + TYPEC_EXIT_STATE_CFG, - SEL_SRC_UPPER_REF, SEL_SRC_UPPER_REF); -} - -static int qcom_pmic_typec_probe(struct platform_device *pdev) -{ - struct qcom_pmic_typec *qcom_usb; - struct device *dev = &pdev->dev; - struct fwnode_handle *fwnode; - struct typec_capability cap; - const char *buf; - int ret, irq, role; - u32 reg; - - ret = device_property_read_u32(dev, "reg", ®); - if (ret < 0) { - dev_err(dev, "missing base address\n"); - return ret; - } - - qcom_usb = devm_kzalloc(dev, sizeof(*qcom_usb), GFP_KERNEL); - if (!qcom_usb) - return -ENOMEM; - - qcom_usb->dev = dev; - qcom_usb->base = reg; - - qcom_usb->regmap = dev_get_regmap(dev->parent, NULL); - if (!qcom_usb->regmap) { - dev_err(dev, "Failed to get regmap\n"); - return -EINVAL; - } - - qcom_usb->vbus_reg = devm_regulator_get(qcom_usb->dev, "usb_vbus"); - if (IS_ERR(qcom_usb->vbus_reg)) - return PTR_ERR(qcom_usb->vbus_reg); - - fwnode = device_get_named_child_node(dev, "connector"); - if (!fwnode) - return -EINVAL; - - ret = fwnode_property_read_string(fwnode, "power-role", &buf); - if (!ret) { - role = typec_find_port_power_role(buf); - if (role < 0) - role = TYPEC_PORT_SNK; - } else { - role = TYPEC_PORT_SNK; - } - cap.type = role; - - ret = fwnode_property_read_string(fwnode, "data-role", &buf); - if (!ret) { - role = typec_find_port_data_role(buf); - if (role < 0) - role = TYPEC_PORT_UFP; - } else { - role = TYPEC_PORT_UFP; - } - cap.data = role; - - cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; - cap.fwnode = fwnode; - qcom_usb->port = typec_register_port(dev, &cap); - if (IS_ERR(qcom_usb->port)) { - ret = PTR_ERR(qcom_usb->port); - dev_err(dev, "Failed to register type c port %d\n", ret); - goto err_put_node; - } - fwnode_handle_put(fwnode); - - qcom_usb->role_sw = fwnode_usb_role_switch_get(dev_fwnode(qcom_usb->dev)); - if (IS_ERR(qcom_usb->role_sw)) { - ret = dev_err_probe(dev, PTR_ERR(qcom_usb->role_sw), - "failed to get role switch\n"); - goto err_typec_port; - } - - irq = platform_get_irq(pdev, 0); - if (irq < 0) - goto err_usb_role_sw; - - ret = devm_request_threaded_irq(qcom_usb->dev, irq, NULL, - qcom_pmic_typec_interrupt, IRQF_ONESHOT, - "qcom-pmic-typec", qcom_usb); - if (ret) { - dev_err(&pdev->dev, "Could not request IRQ\n"); - goto err_usb_role_sw; - } - - platform_set_drvdata(pdev, qcom_usb); - qcom_pmic_typec_typec_hw_init(qcom_usb, cap.type); - qcom_pmic_typec_check_connection(qcom_usb); - - return 0; - -err_usb_role_sw: - usb_role_switch_put(qcom_usb->role_sw); -err_typec_port: - typec_unregister_port(qcom_usb->port); -err_put_node: - fwnode_handle_put(fwnode); - - return ret; -} - -static int qcom_pmic_typec_remove(struct platform_device *pdev) -{ - struct qcom_pmic_typec *qcom_usb = platform_get_drvdata(pdev); - - usb_role_switch_set_role(qcom_usb->role_sw, USB_ROLE_NONE); - qcom_pmic_typec_enable_vbus_regulator(qcom_usb, 0); - - typec_unregister_port(qcom_usb->port); - usb_role_switch_put(qcom_usb->role_sw); - - return 0; -} - -static const struct of_device_id qcom_pmic_typec_table[] = { - { .compatible = "qcom,pm8150b-usb-typec" }, - { } -}; -MODULE_DEVICE_TABLE(of, qcom_pmic_typec_table); - -static struct platform_driver qcom_pmic_typec = { - .driver = { - .name = "qcom,pmic-typec", - .of_match_table = qcom_pmic_typec_table, - }, - .probe = qcom_pmic_typec_probe, - .remove = qcom_pmic_typec_remove, -}; -module_platform_driver(qcom_pmic_typec); - -MODULE_DESCRIPTION("QCOM PMIC USB type C driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/typec/rt1719.c b/drivers/usb/typec/rt1719.c index ea8b700b0ceb..be02d420920e 100644 --- a/drivers/usb/typec/rt1719.c +++ b/drivers/usb/typec/rt1719.c @@ -949,7 +949,7 @@ static struct i2c_driver rt1719_driver = { .name = "rt1719", .of_match_table = rt1719_device_table, }, - .probe_new = rt1719_probe, + .probe = rt1719_probe, .remove = rt1719_remove, }; module_i2c_driver(rt1719_driver); diff --git a/drivers/usb/typec/stusb160x.c b/drivers/usb/typec/stusb160x.c index 494b371151e0..3ab118df1bd4 100644 --- a/drivers/usb/typec/stusb160x.c +++ b/drivers/usb/typec/stusb160x.c @@ -870,7 +870,7 @@ static struct i2c_driver stusb160x_driver = { .pm = &stusb160x_pm_ops, .of_match_table = stusb160x_of_match, }, - .probe_new = stusb160x_probe, + .probe = stusb160x_probe, .remove = stusb160x_remove, }; module_i2c_driver(stusb160x_driver); diff --git a/drivers/usb/typec/tcpm/Kconfig b/drivers/usb/typec/tcpm/Kconfig index e6b88ca4a4b9..5d393f520fc2 100644 --- a/drivers/usb/typec/tcpm/Kconfig +++ b/drivers/usb/typec/tcpm/Kconfig @@ -76,4 +76,15 @@ config TYPEC_WCOVE To compile this driver as module, choose M here: the module will be called typec_wcove.ko +config TYPEC_QCOM_PMIC + tristate "Qualcomm PMIC USB Type-C Port Controller Manager driver" + depends on ARCH_QCOM || COMPILE_TEST + help + A Type-C port and Power Delivery driver which aggregates two + discrete pieces of silicon in the PM8150b PMIC block: the + Type-C port controller and the Power Delivery PHY. + + This driver enables Type-C role switching, orientation, Alternate + mode and Power Delivery support both for VBUS and VCONN. + endif # TYPEC_TCPM diff --git a/drivers/usb/typec/tcpm/Makefile b/drivers/usb/typec/tcpm/Makefile index 08e57bb499cb..7a8cad0c0bdb 100644 --- a/drivers/usb/typec/tcpm/Makefile +++ b/drivers/usb/typec/tcpm/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_TYPEC_MT6360) += tcpci_mt6360.o obj-$(CONFIG_TYPEC_TCPCI_MT6370) += tcpci_mt6370.o obj-$(CONFIG_TYPEC_TCPCI_MAXIM) += tcpci_maxim.o tcpci_maxim-y += tcpci_maxim_core.o maxim_contaminant.o +obj-$(CONFIG_TYPEC_QCOM_PMIC) += qcom/ diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c index 62ba53357612..7fc1ffa14f76 100644 --- a/drivers/usb/typec/tcpm/fusb302.c +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -1836,7 +1836,7 @@ static struct i2c_driver fusb302_driver = { .pm = &fusb302_pm_ops, .of_match_table = of_match_ptr(fusb302_dt_match), }, - .probe_new = fusb302_probe, + .probe = fusb302_probe, .remove = fusb302_remove, .id_table = fusb302_i2c_device_id, }; diff --git a/drivers/usb/typec/tcpm/qcom/Makefile b/drivers/usb/typec/tcpm/qcom/Makefile new file mode 100644 index 000000000000..dc1e8832e197 --- /dev/null +++ b/drivers/usb/typec/tcpm/qcom/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +# +obj-$(CONFIG_TYPEC_QCOM_PMIC) += qcom_pmic_tcpm.o +qcom_pmic_tcpm-y += qcom_pmic_typec.o \ + qcom_pmic_typec_port.o \ + qcom_pmic_typec_pdphy.o diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c new file mode 100644 index 000000000000..a905160dd860 --- /dev/null +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2023, Linaro Ltd. All rights reserved. + */ + +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/usb/role.h> +#include <linux/usb/tcpm.h> +#include <linux/usb/typec_mux.h> +#include "qcom_pmic_typec_pdphy.h" +#include "qcom_pmic_typec_port.h" + +struct pmic_typec_resources { + struct pmic_typec_pdphy_resources *pdphy_res; + struct pmic_typec_port_resources *port_res; +}; + +struct pmic_typec { + struct device *dev; + struct tcpm_port *tcpm_port; + struct tcpc_dev tcpc; + struct pmic_typec_pdphy *pmic_typec_pdphy; + struct pmic_typec_port *pmic_typec_port; + bool vbus_enabled; + struct mutex lock; /* VBUS state serialization */ +}; + +#define tcpc_to_tcpm(_tcpc_) container_of(_tcpc_, struct pmic_typec, tcpc) + +static int qcom_pmic_typec_get_vbus(struct tcpc_dev *tcpc) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + int ret; + + mutex_lock(&tcpm->lock); + ret = tcpm->vbus_enabled || qcom_pmic_typec_port_get_vbus(tcpm->pmic_typec_port); + mutex_unlock(&tcpm->lock); + + return ret; +} + +static int qcom_pmic_typec_set_vbus(struct tcpc_dev *tcpc, bool on, bool sink) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + int ret = 0; + + mutex_lock(&tcpm->lock); + if (tcpm->vbus_enabled == on) + goto done; + + ret = qcom_pmic_typec_port_set_vbus(tcpm->pmic_typec_port, on); + if (ret) + goto done; + + tcpm->vbus_enabled = on; + tcpm_vbus_change(tcpm->tcpm_port); + +done: + dev_dbg(tcpm->dev, "set_vbus set: %d result %d\n", on, ret); + mutex_unlock(&tcpm->lock); + + return ret; +} + +static int qcom_pmic_typec_set_vconn(struct tcpc_dev *tcpc, bool on) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + + return qcom_pmic_typec_port_set_vconn(tcpm->pmic_typec_port, on); +} + +static int qcom_pmic_typec_get_cc(struct tcpc_dev *tcpc, + enum typec_cc_status *cc1, + enum typec_cc_status *cc2) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + + return qcom_pmic_typec_port_get_cc(tcpm->pmic_typec_port, cc1, cc2); +} + +static int qcom_pmic_typec_set_cc(struct tcpc_dev *tcpc, + enum typec_cc_status cc) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + + return qcom_pmic_typec_port_set_cc(tcpm->pmic_typec_port, cc); +} + +static int qcom_pmic_typec_set_polarity(struct tcpc_dev *tcpc, + enum typec_cc_polarity pol) +{ + /* Polarity is set separately by phy-qcom-qmp.c */ + return 0; +} + +static int qcom_pmic_typec_start_toggling(struct tcpc_dev *tcpc, + enum typec_port_type port_type, + enum typec_cc_status cc) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + + return qcom_pmic_typec_port_start_toggling(tcpm->pmic_typec_port, + port_type, cc); +} + +static int qcom_pmic_typec_set_roles(struct tcpc_dev *tcpc, bool attached, + enum typec_role power_role, + enum typec_data_role data_role) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + + return qcom_pmic_typec_pdphy_set_roles(tcpm->pmic_typec_pdphy, + data_role, power_role); +} + +static int qcom_pmic_typec_set_pd_rx(struct tcpc_dev *tcpc, bool on) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + + return qcom_pmic_typec_pdphy_set_pd_rx(tcpm->pmic_typec_pdphy, on); +} + +static int qcom_pmic_typec_pd_transmit(struct tcpc_dev *tcpc, + enum tcpm_transmit_type type, + const struct pd_message *msg, + unsigned int negotiated_rev) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + + return qcom_pmic_typec_pdphy_pd_transmit(tcpm->pmic_typec_pdphy, type, + msg, negotiated_rev); +} + +static int qcom_pmic_typec_init(struct tcpc_dev *tcpc) +{ + return 0; +} + +static int qcom_pmic_typec_probe(struct platform_device *pdev) +{ + struct pmic_typec *tcpm; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + const struct pmic_typec_resources *res; + struct regmap *regmap; + u32 base[2]; + int ret; + + res = of_device_get_match_data(dev); + if (!res) + return -ENODEV; + + tcpm = devm_kzalloc(dev, sizeof(*tcpm), GFP_KERNEL); + if (!tcpm) + return -ENOMEM; + + tcpm->dev = dev; + tcpm->tcpc.init = qcom_pmic_typec_init; + tcpm->tcpc.get_vbus = qcom_pmic_typec_get_vbus; + tcpm->tcpc.set_vbus = qcom_pmic_typec_set_vbus; + tcpm->tcpc.set_cc = qcom_pmic_typec_set_cc; + tcpm->tcpc.get_cc = qcom_pmic_typec_get_cc; + tcpm->tcpc.set_polarity = qcom_pmic_typec_set_polarity; + tcpm->tcpc.set_vconn = qcom_pmic_typec_set_vconn; + tcpm->tcpc.start_toggling = qcom_pmic_typec_start_toggling; + tcpm->tcpc.set_pd_rx = qcom_pmic_typec_set_pd_rx; + tcpm->tcpc.set_roles = qcom_pmic_typec_set_roles; + tcpm->tcpc.pd_transmit = qcom_pmic_typec_pd_transmit; + + regmap = dev_get_regmap(dev->parent, NULL); + if (!regmap) { + dev_err(dev, "Failed to get regmap\n"); + return -ENODEV; + } + + ret = of_property_read_u32_array(np, "reg", base, 2); + if (ret) + return ret; + + tcpm->pmic_typec_port = qcom_pmic_typec_port_alloc(dev); + if (IS_ERR(tcpm->pmic_typec_port)) + return PTR_ERR(tcpm->pmic_typec_port); + + tcpm->pmic_typec_pdphy = qcom_pmic_typec_pdphy_alloc(dev); + if (IS_ERR(tcpm->pmic_typec_pdphy)) + return PTR_ERR(tcpm->pmic_typec_pdphy); + + ret = qcom_pmic_typec_port_probe(pdev, tcpm->pmic_typec_port, + res->port_res, regmap, base[0]); + if (ret) + return ret; + + ret = qcom_pmic_typec_pdphy_probe(pdev, tcpm->pmic_typec_pdphy, + res->pdphy_res, regmap, base[1]); + if (ret) + return ret; + + mutex_init(&tcpm->lock); + platform_set_drvdata(pdev, tcpm); + + tcpm->tcpc.fwnode = device_get_named_child_node(tcpm->dev, "connector"); + if (IS_ERR(tcpm->tcpc.fwnode)) + return PTR_ERR(tcpm->tcpc.fwnode); + + tcpm->tcpm_port = tcpm_register_port(tcpm->dev, &tcpm->tcpc); + if (IS_ERR(tcpm->tcpm_port)) { + ret = PTR_ERR(tcpm->tcpm_port); + goto fwnode_remove; + } + + ret = qcom_pmic_typec_port_start(tcpm->pmic_typec_port, + tcpm->tcpm_port); + if (ret) + goto fwnode_remove; + + ret = qcom_pmic_typec_pdphy_start(tcpm->pmic_typec_pdphy, + tcpm->tcpm_port); + if (ret) + goto fwnode_remove; + + return 0; + +fwnode_remove: + fwnode_remove_software_node(tcpm->tcpc.fwnode); + + return ret; +} + +static void qcom_pmic_typec_remove(struct platform_device *pdev) +{ + struct pmic_typec *tcpm = platform_get_drvdata(pdev); + + qcom_pmic_typec_pdphy_stop(tcpm->pmic_typec_pdphy); + qcom_pmic_typec_port_stop(tcpm->pmic_typec_port); + tcpm_unregister_port(tcpm->tcpm_port); + fwnode_remove_software_node(tcpm->tcpc.fwnode); +} + +static struct pmic_typec_pdphy_resources pm8150b_pdphy_res = { + .irq_params = { + { + .virq = PMIC_PDPHY_SIG_TX_IRQ, + .irq_name = "sig-tx", + }, + { + .virq = PMIC_PDPHY_SIG_RX_IRQ, + .irq_name = "sig-rx", + }, + { + .virq = PMIC_PDPHY_MSG_TX_IRQ, + .irq_name = "msg-tx", + }, + { + .virq = PMIC_PDPHY_MSG_RX_IRQ, + .irq_name = "msg-rx", + }, + { + .virq = PMIC_PDPHY_MSG_TX_FAIL_IRQ, + .irq_name = "msg-tx-failed", + }, + { + .virq = PMIC_PDPHY_MSG_TX_DISCARD_IRQ, + .irq_name = "msg-tx-discarded", + }, + { + .virq = PMIC_PDPHY_MSG_RX_DISCARD_IRQ, + .irq_name = "msg-rx-discarded", + }, + }, + .nr_irqs = 7, +}; + +static struct pmic_typec_port_resources pm8150b_port_res = { + .irq_params = { + { + .irq_name = "vpd-detect", + .virq = PMIC_TYPEC_VPD_IRQ, + }, + + { + .irq_name = "cc-state-change", + .virq = PMIC_TYPEC_CC_STATE_IRQ, + }, + { + .irq_name = "vconn-oc", + .virq = PMIC_TYPEC_VCONN_OC_IRQ, + }, + + { + .irq_name = "vbus-change", + .virq = PMIC_TYPEC_VBUS_IRQ, + }, + + { + .irq_name = "attach-detach", + .virq = PMIC_TYPEC_ATTACH_DETACH_IRQ, + }, + { + .irq_name = "legacy-cable-detect", + .virq = PMIC_TYPEC_LEGACY_CABLE_IRQ, + }, + + { + .irq_name = "try-snk-src-detect", + .virq = PMIC_TYPEC_TRY_SNK_SRC_IRQ, + }, + }, + .nr_irqs = 7, +}; + +static struct pmic_typec_resources pm8150b_typec_res = { + .pdphy_res = &pm8150b_pdphy_res, + .port_res = &pm8150b_port_res, +}; + +static const struct of_device_id qcom_pmic_typec_table[] = { + { .compatible = "qcom,pm8150b-typec", .data = &pm8150b_typec_res }, + { } +}; +MODULE_DEVICE_TABLE(of, qcom_pmic_typec_table); + +static struct platform_driver qcom_pmic_typec_driver = { + .driver = { + .name = "qcom,pmic-typec", + .of_match_table = qcom_pmic_typec_table, + }, + .probe = qcom_pmic_typec_probe, + .remove_new = qcom_pmic_typec_remove, +}; + +module_platform_driver(qcom_pmic_typec_driver); + +MODULE_DESCRIPTION("QCOM PMIC USB Type-C Port Manager Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c new file mode 100644 index 000000000000..4e1b846627d2 --- /dev/null +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c @@ -0,0 +1,528 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2023, Linaro Ltd. All rights reserved. + */ + +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/usb/pd.h> +#include <linux/usb/tcpm.h> +#include "qcom_pmic_typec_pdphy.h" + +struct pmic_typec_pdphy_irq_data { + int virq; + int irq; + struct pmic_typec_pdphy *pmic_typec_pdphy; +}; + +struct pmic_typec_pdphy { + struct device *dev; + struct tcpm_port *tcpm_port; + struct regmap *regmap; + u32 base; + + unsigned int nr_irqs; + struct pmic_typec_pdphy_irq_data *irq_data; + + struct work_struct reset_work; + struct work_struct receive_work; + struct regulator *vdd_pdphy; + spinlock_t lock; /* Register atomicity */ +}; + +static void qcom_pmic_typec_pdphy_reset_on(struct pmic_typec_pdphy *pmic_typec_pdphy) +{ + struct device *dev = pmic_typec_pdphy->dev; + int ret; + + /* Terminate TX */ + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_TX_CONTROL_REG, 0); + if (ret) + goto err; + + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_FRAME_FILTER_REG, 0); + if (ret) + goto err; + + return; +err: + dev_err(dev, "pd_reset_on error\n"); +} + +static void qcom_pmic_typec_pdphy_reset_off(struct pmic_typec_pdphy *pmic_typec_pdphy) +{ + struct device *dev = pmic_typec_pdphy->dev; + int ret; + + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_FRAME_FILTER_REG, + FRAME_FILTER_EN_SOP | FRAME_FILTER_EN_HARD_RESET); + if (ret) + dev_err(dev, "pd_reset_off error\n"); +} + +static void qcom_pmic_typec_pdphy_sig_reset_work(struct work_struct *work) +{ + struct pmic_typec_pdphy *pmic_typec_pdphy = container_of(work, struct pmic_typec_pdphy, + reset_work); + unsigned long flags; + + spin_lock_irqsave(&pmic_typec_pdphy->lock, flags); + + qcom_pmic_typec_pdphy_reset_on(pmic_typec_pdphy); + qcom_pmic_typec_pdphy_reset_off(pmic_typec_pdphy); + + spin_unlock_irqrestore(&pmic_typec_pdphy->lock, flags); + + tcpm_pd_hard_reset(pmic_typec_pdphy->tcpm_port); +} + +static int +qcom_pmic_typec_pdphy_clear_tx_control_reg(struct pmic_typec_pdphy *pmic_typec_pdphy) +{ + struct device *dev = pmic_typec_pdphy->dev; + unsigned int val; + int ret; + + /* Clear TX control register */ + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_TX_CONTROL_REG, 0); + if (ret) + goto done; + + /* Perform readback to ensure sufficient delay for command to latch */ + ret = regmap_read(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_TX_CONTROL_REG, &val); + +done: + if (ret) + dev_err(dev, "pd_clear_tx_control_reg: clear tx flag\n"); + + return ret; +} + +static int +qcom_pmic_typec_pdphy_pd_transmit_signal(struct pmic_typec_pdphy *pmic_typec_pdphy, + enum tcpm_transmit_type type, + unsigned int negotiated_rev) +{ + struct device *dev = pmic_typec_pdphy->dev; + unsigned int val; + unsigned long flags; + int ret; + + spin_lock_irqsave(&pmic_typec_pdphy->lock, flags); + + /* Clear TX control register */ + ret = qcom_pmic_typec_pdphy_clear_tx_control_reg(pmic_typec_pdphy); + if (ret) + goto done; + + val = TX_CONTROL_SEND_SIGNAL; + if (negotiated_rev == PD_REV30) + val |= TX_CONTROL_RETRY_COUNT(2); + else + val |= TX_CONTROL_RETRY_COUNT(3); + + if (type == TCPC_TX_CABLE_RESET || type == TCPC_TX_HARD_RESET) + val |= TX_CONTROL_FRAME_TYPE(1); + + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_TX_CONTROL_REG, val); + +done: + spin_unlock_irqrestore(&pmic_typec_pdphy->lock, flags); + + dev_vdbg(dev, "pd_transmit_signal: type %d negotiate_rev %d send %d\n", + type, negotiated_rev, ret); + + return ret; +} + +static int +qcom_pmic_typec_pdphy_pd_transmit_payload(struct pmic_typec_pdphy *pmic_typec_pdphy, + enum tcpm_transmit_type type, + const struct pd_message *msg, + unsigned int negotiated_rev) +{ + struct device *dev = pmic_typec_pdphy->dev; + unsigned int val, hdr_len, txbuf_len, txsize_len; + unsigned long flags; + int ret; + + spin_lock_irqsave(&pmic_typec_pdphy->lock, flags); + + ret = regmap_read(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_RX_ACKNOWLEDGE_REG, + &val); + if (ret) + goto done; + + if (val) { + dev_err(dev, "pd_transmit_payload: RX message pending\n"); + ret = -EBUSY; + goto done; + } + + /* Clear TX control register */ + ret = qcom_pmic_typec_pdphy_clear_tx_control_reg(pmic_typec_pdphy); + if (ret) + goto done; + + hdr_len = sizeof(msg->header); + txbuf_len = pd_header_cnt_le(msg->header) * 4; + txsize_len = hdr_len + txbuf_len - 1; + + /* Write message header sizeof(u16) to USB_PDPHY_TX_BUFFER_HDR_REG */ + ret = regmap_bulk_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_TX_BUFFER_HDR_REG, + &msg->header, hdr_len); + if (ret) + goto done; + + /* Write payload to USB_PDPHY_TX_BUFFER_DATA_REG for txbuf_len */ + if (txbuf_len) { + ret = regmap_bulk_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_TX_BUFFER_DATA_REG, + &msg->payload, txbuf_len); + if (ret) + goto done; + } + + /* Write total length ((header + data) - 1) to USB_PDPHY_TX_SIZE_REG */ + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_TX_SIZE_REG, + txsize_len); + if (ret) + goto done; + + /* Clear TX control register */ + ret = qcom_pmic_typec_pdphy_clear_tx_control_reg(pmic_typec_pdphy); + if (ret) + goto done; + + /* Initiate transmit with retry count as indicated by PD revision */ + val = TX_CONTROL_FRAME_TYPE(type) | TX_CONTROL_SEND_MSG; + if (pd_header_rev(msg->header) == PD_REV30) + val |= TX_CONTROL_RETRY_COUNT(2); + else + val |= TX_CONTROL_RETRY_COUNT(3); + + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_TX_CONTROL_REG, val); + +done: + spin_unlock_irqrestore(&pmic_typec_pdphy->lock, flags); + + if (ret) { + dev_err(dev, "pd_transmit_payload: hdr %*ph data %*ph ret %d\n", + hdr_len, &msg->header, txbuf_len, &msg->payload, ret); + } + + return ret; +} + +int qcom_pmic_typec_pdphy_pd_transmit(struct pmic_typec_pdphy *pmic_typec_pdphy, + enum tcpm_transmit_type type, + const struct pd_message *msg, + unsigned int negotiated_rev) +{ + struct device *dev = pmic_typec_pdphy->dev; + int ret; + + if (msg) { + ret = qcom_pmic_typec_pdphy_pd_transmit_payload(pmic_typec_pdphy, + type, msg, + negotiated_rev); + } else { + ret = qcom_pmic_typec_pdphy_pd_transmit_signal(pmic_typec_pdphy, + type, + negotiated_rev); + } + + if (ret) + dev_dbg(dev, "pd_transmit: type %x result %d\n", type, ret); + + return ret; +} + +static void qcom_pmic_typec_pdphy_pd_receive(struct pmic_typec_pdphy *pmic_typec_pdphy) +{ + struct device *dev = pmic_typec_pdphy->dev; + struct pd_message msg; + unsigned int size, rx_status; + unsigned long flags; + int ret; + + spin_lock_irqsave(&pmic_typec_pdphy->lock, flags); + + ret = regmap_read(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_RX_SIZE_REG, &size); + if (ret) + goto done; + + /* Hardware requires +1 of the real read value to be passed */ + if (size < 1 || size > sizeof(msg.payload) + 1) { + dev_dbg(dev, "pd_receive: invalid size %d\n", size); + goto done; + } + + size += 1; + ret = regmap_read(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_RX_STATUS_REG, + &rx_status); + + if (ret) + goto done; + + ret = regmap_bulk_read(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_RX_BUFFER_REG, + (u8 *)&msg, size); + if (ret) + goto done; + + /* Return ownership of RX buffer to hardware */ + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_RX_ACKNOWLEDGE_REG, 0); + +done: + spin_unlock_irqrestore(&pmic_typec_pdphy->lock, flags); + + if (!ret) { + dev_vdbg(dev, "pd_receive: handing %d bytes to tcpm\n", size); + tcpm_pd_receive(pmic_typec_pdphy->tcpm_port, &msg); + } +} + +static irqreturn_t qcom_pmic_typec_pdphy_isr(int irq, void *dev_id) +{ + struct pmic_typec_pdphy_irq_data *irq_data = dev_id; + struct pmic_typec_pdphy *pmic_typec_pdphy = irq_data->pmic_typec_pdphy; + struct device *dev = pmic_typec_pdphy->dev; + + switch (irq_data->virq) { + case PMIC_PDPHY_SIG_TX_IRQ: + dev_err(dev, "isr: tx_sig\n"); + break; + case PMIC_PDPHY_SIG_RX_IRQ: + schedule_work(&pmic_typec_pdphy->reset_work); + break; + case PMIC_PDPHY_MSG_TX_IRQ: + tcpm_pd_transmit_complete(pmic_typec_pdphy->tcpm_port, + TCPC_TX_SUCCESS); + break; + case PMIC_PDPHY_MSG_RX_IRQ: + qcom_pmic_typec_pdphy_pd_receive(pmic_typec_pdphy); + break; + case PMIC_PDPHY_MSG_TX_FAIL_IRQ: + tcpm_pd_transmit_complete(pmic_typec_pdphy->tcpm_port, + TCPC_TX_FAILED); + break; + case PMIC_PDPHY_MSG_TX_DISCARD_IRQ: + tcpm_pd_transmit_complete(pmic_typec_pdphy->tcpm_port, + TCPC_TX_DISCARDED); + break; + } + + return IRQ_HANDLED; +} + +int qcom_pmic_typec_pdphy_set_pd_rx(struct pmic_typec_pdphy *pmic_typec_pdphy, bool on) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&pmic_typec_pdphy->lock, flags); + + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_RX_ACKNOWLEDGE_REG, !on); + + spin_unlock_irqrestore(&pmic_typec_pdphy->lock, flags); + + dev_dbg(pmic_typec_pdphy->dev, "set_pd_rx: %s\n", on ? "on" : "off"); + + return ret; +} + +int qcom_pmic_typec_pdphy_set_roles(struct pmic_typec_pdphy *pmic_typec_pdphy, + bool data_role_host, bool power_role_src) +{ + struct device *dev = pmic_typec_pdphy->dev; + unsigned long flags; + int ret; + + spin_lock_irqsave(&pmic_typec_pdphy->lock, flags); + + ret = regmap_update_bits(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_MSG_CONFIG_REG, + MSG_CONFIG_PORT_DATA_ROLE | + MSG_CONFIG_PORT_POWER_ROLE, + data_role_host << 3 | power_role_src << 2); + + spin_unlock_irqrestore(&pmic_typec_pdphy->lock, flags); + + dev_dbg(dev, "pdphy_set_roles: data_role_host=%d power_role_src=%d\n", + data_role_host, power_role_src); + + return ret; +} + +static int qcom_pmic_typec_pdphy_enable(struct pmic_typec_pdphy *pmic_typec_pdphy) +{ + struct device *dev = pmic_typec_pdphy->dev; + int ret; + + ret = regulator_enable(pmic_typec_pdphy->vdd_pdphy); + if (ret) + return ret; + + /* PD 2.0, DR=TYPEC_DEVICE, PR=TYPEC_SINK */ + ret = regmap_update_bits(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_MSG_CONFIG_REG, + MSG_CONFIG_SPEC_REV_MASK, PD_REV20); + if (ret) + goto done; + + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_EN_CONTROL_REG, 0); + if (ret) + goto done; + + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_EN_CONTROL_REG, + CONTROL_ENABLE); + if (ret) + goto done; + + qcom_pmic_typec_pdphy_reset_off(pmic_typec_pdphy); +done: + if (ret) { + regulator_disable(pmic_typec_pdphy->vdd_pdphy); + dev_err(dev, "pdphy_enable fail %d\n", ret); + } + + return ret; +} + +static int qcom_pmic_typec_pdphy_disable(struct pmic_typec_pdphy *pmic_typec_pdphy) +{ + int ret; + + qcom_pmic_typec_pdphy_reset_on(pmic_typec_pdphy); + + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_EN_CONTROL_REG, 0); + + regulator_disable(pmic_typec_pdphy->vdd_pdphy); + + return ret; +} + +static int pmic_typec_pdphy_reset(struct pmic_typec_pdphy *pmic_typec_pdphy) +{ + int ret; + + ret = qcom_pmic_typec_pdphy_disable(pmic_typec_pdphy); + if (ret) + goto done; + + usleep_range(400, 500); + ret = qcom_pmic_typec_pdphy_enable(pmic_typec_pdphy); +done: + return ret; +} + +int qcom_pmic_typec_pdphy_start(struct pmic_typec_pdphy *pmic_typec_pdphy, + struct tcpm_port *tcpm_port) +{ + int i; + int ret; + + pmic_typec_pdphy->tcpm_port = tcpm_port; + + ret = pmic_typec_pdphy_reset(pmic_typec_pdphy); + if (ret) + return ret; + + for (i = 0; i < pmic_typec_pdphy->nr_irqs; i++) + enable_irq(pmic_typec_pdphy->irq_data[i].irq); + + return 0; +} + +void qcom_pmic_typec_pdphy_stop(struct pmic_typec_pdphy *pmic_typec_pdphy) +{ + int i; + + for (i = 0; i < pmic_typec_pdphy->nr_irqs; i++) + disable_irq(pmic_typec_pdphy->irq_data[i].irq); + + qcom_pmic_typec_pdphy_reset_on(pmic_typec_pdphy); +} + +struct pmic_typec_pdphy *qcom_pmic_typec_pdphy_alloc(struct device *dev) +{ + return devm_kzalloc(dev, sizeof(struct pmic_typec_pdphy), GFP_KERNEL); +} + +int qcom_pmic_typec_pdphy_probe(struct platform_device *pdev, + struct pmic_typec_pdphy *pmic_typec_pdphy, + struct pmic_typec_pdphy_resources *res, + struct regmap *regmap, + u32 base) +{ + struct device *dev = &pdev->dev; + struct pmic_typec_pdphy_irq_data *irq_data; + int i, ret, irq; + + if (!res->nr_irqs || res->nr_irqs > PMIC_PDPHY_MAX_IRQS) + return -EINVAL; + + irq_data = devm_kzalloc(dev, sizeof(*irq_data) * res->nr_irqs, + GFP_KERNEL); + if (!irq_data) + return -ENOMEM; + + pmic_typec_pdphy->vdd_pdphy = devm_regulator_get(dev, "vdd-pdphy"); + if (IS_ERR(pmic_typec_pdphy->vdd_pdphy)) + return PTR_ERR(pmic_typec_pdphy->vdd_pdphy); + + pmic_typec_pdphy->dev = dev; + pmic_typec_pdphy->base = base; + pmic_typec_pdphy->regmap = regmap; + pmic_typec_pdphy->nr_irqs = res->nr_irqs; + pmic_typec_pdphy->irq_data = irq_data; + spin_lock_init(&pmic_typec_pdphy->lock); + INIT_WORK(&pmic_typec_pdphy->reset_work, qcom_pmic_typec_pdphy_sig_reset_work); + + for (i = 0; i < res->nr_irqs; i++, irq_data++) { + irq = platform_get_irq_byname(pdev, res->irq_params[i].irq_name); + if (irq < 0) + return irq; + + irq_data->pmic_typec_pdphy = pmic_typec_pdphy; + irq_data->irq = irq; + irq_data->virq = res->irq_params[i].virq; + + ret = devm_request_threaded_irq(dev, irq, NULL, + qcom_pmic_typec_pdphy_isr, + IRQF_ONESHOT | IRQF_NO_AUTOEN, + res->irq_params[i].irq_name, + irq_data); + if (ret) + return ret; + } + + return 0; +} diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.h b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.h new file mode 100644 index 000000000000..e67954e31b14 --- /dev/null +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.h @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved. + * Copyright (c) 2023, Linaro Ltd. All rights reserved. + */ +#ifndef __QCOM_PMIC_PDPHY_H__ +#define __QCOM_PMIC_PDPHY_H__ + +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/usb/tcpm.h> + +#define USB_PDPHY_MAX_DATA_OBJ_LEN 28 +#define USB_PDPHY_MSG_HDR_LEN 2 + +/* PD PHY register offsets and bit fields */ +#define USB_PDPHY_MSG_CONFIG_REG 0x40 +#define MSG_CONFIG_PORT_DATA_ROLE BIT(3) +#define MSG_CONFIG_PORT_POWER_ROLE BIT(2) +#define MSG_CONFIG_SPEC_REV_MASK (BIT(1) | BIT(0)) + +#define USB_PDPHY_EN_CONTROL_REG 0x46 +#define CONTROL_ENABLE BIT(0) + +#define USB_PDPHY_RX_STATUS_REG 0x4A +#define RX_FRAME_TYPE (BIT(0) | BIT(1) | BIT(2)) + +#define USB_PDPHY_FRAME_FILTER_REG 0x4C +#define FRAME_FILTER_EN_HARD_RESET BIT(5) +#define FRAME_FILTER_EN_SOP BIT(0) + +#define USB_PDPHY_TX_SIZE_REG 0x42 +#define TX_SIZE_MASK 0xF + +#define USB_PDPHY_TX_CONTROL_REG 0x44 +#define TX_CONTROL_RETRY_COUNT(n) (((n) & 0x3) << 5) +#define TX_CONTROL_FRAME_TYPE(n) (((n) & 0x7) << 2) +#define TX_CONTROL_FRAME_TYPE_CABLE_RESET (0x1 << 2) +#define TX_CONTROL_SEND_SIGNAL BIT(1) +#define TX_CONTROL_SEND_MSG BIT(0) + +#define USB_PDPHY_RX_SIZE_REG 0x48 + +#define USB_PDPHY_RX_ACKNOWLEDGE_REG 0x4B +#define RX_BUFFER_TOKEN BIT(0) + +#define USB_PDPHY_BIST_MODE_REG 0x4E +#define BIST_MODE_MASK 0xF +#define BIST_ENABLE BIT(7) +#define PD_MSG_BIST 0x3 +#define PD_BIST_TEST_DATA_MODE 0x8 + +#define USB_PDPHY_TX_BUFFER_HDR_REG 0x60 +#define USB_PDPHY_TX_BUFFER_DATA_REG 0x62 + +#define USB_PDPHY_RX_BUFFER_REG 0x80 + +/* VDD regulator */ +#define VDD_PDPHY_VOL_MIN 2800000 /* uV */ +#define VDD_PDPHY_VOL_MAX 3300000 /* uV */ +#define VDD_PDPHY_HPM_LOAD 3000 /* uA */ + +/* Message Spec Rev field */ +#define PD_MSG_HDR_REV(hdr) (((hdr) >> 6) & 3) + +/* timers */ +#define RECEIVER_RESPONSE_TIME 15 /* tReceiverResponse */ +#define HARD_RESET_COMPLETE_TIME 5 /* tHardResetComplete */ + +/* Interrupt numbers */ +#define PMIC_PDPHY_SIG_TX_IRQ 0x0 +#define PMIC_PDPHY_SIG_RX_IRQ 0x1 +#define PMIC_PDPHY_MSG_TX_IRQ 0x2 +#define PMIC_PDPHY_MSG_RX_IRQ 0x3 +#define PMIC_PDPHY_MSG_TX_FAIL_IRQ 0x4 +#define PMIC_PDPHY_MSG_TX_DISCARD_IRQ 0x5 +#define PMIC_PDPHY_MSG_RX_DISCARD_IRQ 0x6 +#define PMIC_PDPHY_FR_SWAP_IRQ 0x7 + +/* Resources */ +#define PMIC_PDPHY_MAX_IRQS 0x08 + +struct pmic_typec_pdphy_irq_params { + int virq; + char *irq_name; +}; + +struct pmic_typec_pdphy_resources { + unsigned int nr_irqs; + struct pmic_typec_pdphy_irq_params irq_params[PMIC_PDPHY_MAX_IRQS]; +}; + +/* API */ +struct pmic_typec_pdphy; + +struct pmic_typec_pdphy *qcom_pmic_typec_pdphy_alloc(struct device *dev); + +int qcom_pmic_typec_pdphy_probe(struct platform_device *pdev, + struct pmic_typec_pdphy *pmic_typec_pdphy, + struct pmic_typec_pdphy_resources *res, + struct regmap *regmap, + u32 base); + +int qcom_pmic_typec_pdphy_start(struct pmic_typec_pdphy *pmic_typec_pdphy, + struct tcpm_port *tcpm_port); + +void qcom_pmic_typec_pdphy_stop(struct pmic_typec_pdphy *pmic_typec_pdphy); + +int qcom_pmic_typec_pdphy_set_roles(struct pmic_typec_pdphy *pmic_typec_pdphy, + bool power_role_src, bool data_role_host); + +int qcom_pmic_typec_pdphy_set_pd_rx(struct pmic_typec_pdphy *pmic_typec_pdphy, bool on); + +int qcom_pmic_typec_pdphy_pd_transmit(struct pmic_typec_pdphy *pmic_typec_pdphy, + enum tcpm_transmit_type type, + const struct pd_message *msg, + unsigned int negotiated_rev); + +#endif /* __QCOM_PMIC_TYPEC_PDPHY_H__ */ diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c new file mode 100644 index 000000000000..94285f64b67d --- /dev/null +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c @@ -0,0 +1,556 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2023, Linaro Ltd. All rights reserved. + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/usb/tcpm.h> +#include <linux/usb/typec_mux.h> +#include <linux/workqueue.h> +#include "qcom_pmic_typec_port.h" + +struct pmic_typec_port_irq_data { + int virq; + int irq; + struct pmic_typec_port *pmic_typec_port; +}; + +struct pmic_typec_port { + struct device *dev; + struct tcpm_port *tcpm_port; + struct regmap *regmap; + u32 base; + unsigned int nr_irqs; + struct pmic_typec_port_irq_data *irq_data; + + struct regulator *vdd_vbus; + + int cc; + bool debouncing_cc; + struct delayed_work cc_debounce_dwork; + + spinlock_t lock; /* Register atomicity */ +}; + +static const char * const typec_cc_status_name[] = { + [TYPEC_CC_OPEN] = "Open", + [TYPEC_CC_RA] = "Ra", + [TYPEC_CC_RD] = "Rd", + [TYPEC_CC_RP_DEF] = "Rp-def", + [TYPEC_CC_RP_1_5] = "Rp-1.5", + [TYPEC_CC_RP_3_0] = "Rp-3.0", +}; + +static const char *rp_unknown = "unknown"; + +static const char *cc_to_name(enum typec_cc_status cc) +{ + if (cc > TYPEC_CC_RP_3_0) + return rp_unknown; + + return typec_cc_status_name[cc]; +} + +static const char * const rp_sel_name[] = { + [TYPEC_SRC_RP_SEL_80UA] = "Rp-def-80uA", + [TYPEC_SRC_RP_SEL_180UA] = "Rp-1.5-180uA", + [TYPEC_SRC_RP_SEL_330UA] = "Rp-3.0-330uA", +}; + +static const char *rp_sel_to_name(int rp_sel) +{ + if (rp_sel > TYPEC_SRC_RP_SEL_330UA) + return rp_unknown; + + return rp_sel_name[rp_sel]; +} + +#define misc_to_cc(msic) !!(misc & CC_ORIENTATION) ? "cc1" : "cc2" +#define misc_to_vconn(msic) !!(misc & CC_ORIENTATION) ? "cc2" : "cc1" + +static void qcom_pmic_typec_port_cc_debounce(struct work_struct *work) +{ + struct pmic_typec_port *pmic_typec_port = + container_of(work, struct pmic_typec_port, cc_debounce_dwork.work); + unsigned long flags; + + spin_lock_irqsave(&pmic_typec_port->lock, flags); + pmic_typec_port->debouncing_cc = false; + spin_unlock_irqrestore(&pmic_typec_port->lock, flags); + + dev_dbg(pmic_typec_port->dev, "Debounce cc complete\n"); +} + +static irqreturn_t pmic_typec_port_isr(int irq, void *dev_id) +{ + struct pmic_typec_port_irq_data *irq_data = dev_id; + struct pmic_typec_port *pmic_typec_port = irq_data->pmic_typec_port; + u32 misc_stat; + bool vbus_change = false; + bool cc_change = false; + unsigned long flags; + int ret; + + spin_lock_irqsave(&pmic_typec_port->lock, flags); + + ret = regmap_read(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_MISC_STATUS_REG, + &misc_stat); + if (ret) + goto done; + + switch (irq_data->virq) { + case PMIC_TYPEC_VBUS_IRQ: + vbus_change = true; + break; + case PMIC_TYPEC_CC_STATE_IRQ: + case PMIC_TYPEC_ATTACH_DETACH_IRQ: + if (!pmic_typec_port->debouncing_cc) + cc_change = true; + break; + } + +done: + spin_unlock_irqrestore(&pmic_typec_port->lock, flags); + + if (vbus_change) + tcpm_vbus_change(pmic_typec_port->tcpm_port); + + if (cc_change) + tcpm_cc_change(pmic_typec_port->tcpm_port); + + return IRQ_HANDLED; +} + +int qcom_pmic_typec_port_get_vbus(struct pmic_typec_port *pmic_typec_port) +{ + struct device *dev = pmic_typec_port->dev; + unsigned int misc; + int ret; + + ret = regmap_read(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_MISC_STATUS_REG, + &misc); + if (ret) + misc = 0; + + dev_dbg(dev, "get_vbus: 0x%08x detect %d\n", misc, !!(misc & TYPEC_VBUS_DETECT)); + + return !!(misc & TYPEC_VBUS_DETECT); +} + +int qcom_pmic_typec_port_set_vbus(struct pmic_typec_port *pmic_typec_port, bool on) +{ + u32 sm_stat; + u32 val; + int ret; + + if (on) { + ret = regulator_enable(pmic_typec_port->vdd_vbus); + if (ret) + return ret; + + val = TYPEC_SM_VBUS_VSAFE5V; + } else { + ret = regulator_disable(pmic_typec_port->vdd_vbus); + if (ret) + return ret; + + val = TYPEC_SM_VBUS_VSAFE0V; + } + + /* Poll waiting for transition to required vSafe5V or vSafe0V */ + ret = regmap_read_poll_timeout(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_SM_STATUS_REG, + sm_stat, sm_stat & val, + 100, 250000); + if (ret) + dev_warn(pmic_typec_port->dev, "vbus vsafe%dv fail\n", on ? 5 : 0); + + return 0; +} + +int qcom_pmic_typec_port_get_cc(struct pmic_typec_port *pmic_typec_port, + enum typec_cc_status *cc1, + enum typec_cc_status *cc2) +{ + struct device *dev = pmic_typec_port->dev; + unsigned int misc, val; + bool attached; + int ret = 0; + + ret = regmap_read(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_MISC_STATUS_REG, &misc); + if (ret) + goto done; + + attached = !!(misc & CC_ATTACHED); + + if (pmic_typec_port->debouncing_cc) { + ret = -EBUSY; + goto done; + } + + *cc1 = TYPEC_CC_OPEN; + *cc2 = TYPEC_CC_OPEN; + + if (!attached) + goto done; + + if (misc & SNK_SRC_MODE) { + ret = regmap_read(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_SRC_STATUS_REG, + &val); + if (ret) + goto done; + switch (val & DETECTED_SRC_TYPE_MASK) { + case SRC_RD_OPEN: + val = TYPEC_CC_RD; + break; + case SRC_RD_RA_VCONN: + val = TYPEC_CC_RD; + *cc1 = TYPEC_CC_RA; + *cc2 = TYPEC_CC_RA; + break; + default: + dev_warn(dev, "unexpected src status %.2x\n", val); + val = TYPEC_CC_RD; + break; + } + } else { + ret = regmap_read(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_SNK_STATUS_REG, + &val); + if (ret) + goto done; + switch (val & DETECTED_SNK_TYPE_MASK) { + case SNK_RP_STD: + val = TYPEC_CC_RP_DEF; + break; + case SNK_RP_1P5: + val = TYPEC_CC_RP_1_5; + break; + case SNK_RP_3P0: + val = TYPEC_CC_RP_3_0; + break; + default: + dev_warn(dev, "unexpected snk status %.2x\n", val); + val = TYPEC_CC_RP_DEF; + break; + } + val = TYPEC_CC_RP_DEF; + } + + if (misc & CC_ORIENTATION) + *cc2 = val; + else + *cc1 = val; + +done: + dev_dbg(dev, "get_cc: misc 0x%08x cc1 0x%08x %s cc2 0x%08x %s attached %d cc=%s\n", + misc, *cc1, cc_to_name(*cc1), *cc2, cc_to_name(*cc2), attached, + misc_to_cc(misc)); + + return ret; +} + +static void qcom_pmic_set_cc_debounce(struct pmic_typec_port *pmic_typec_port) +{ + pmic_typec_port->debouncing_cc = true; + schedule_delayed_work(&pmic_typec_port->cc_debounce_dwork, + msecs_to_jiffies(2)); +} + +int qcom_pmic_typec_port_set_cc(struct pmic_typec_port *pmic_typec_port, + enum typec_cc_status cc) +{ + struct device *dev = pmic_typec_port->dev; + unsigned int mode, currsrc; + unsigned int misc; + unsigned long flags; + int ret; + + spin_lock_irqsave(&pmic_typec_port->lock, flags); + + ret = regmap_read(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_MISC_STATUS_REG, + &misc); + if (ret) + goto done; + + mode = EN_SRC_ONLY; + + switch (cc) { + case TYPEC_CC_OPEN: + currsrc = TYPEC_SRC_RP_SEL_80UA; + break; + case TYPEC_CC_RP_DEF: + currsrc = TYPEC_SRC_RP_SEL_80UA; + break; + case TYPEC_CC_RP_1_5: + currsrc = TYPEC_SRC_RP_SEL_180UA; + break; + case TYPEC_CC_RP_3_0: + currsrc = TYPEC_SRC_RP_SEL_330UA; + break; + case TYPEC_CC_RD: + currsrc = TYPEC_SRC_RP_SEL_80UA; + mode = EN_SNK_ONLY; + break; + default: + dev_warn(dev, "unexpected set_cc %d\n", cc); + ret = -EINVAL; + goto done; + } + + if (mode == EN_SRC_ONLY) { + ret = regmap_write(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_CURRSRC_CFG_REG, + currsrc); + if (ret) + goto done; + } + + pmic_typec_port->cc = cc; + qcom_pmic_set_cc_debounce(pmic_typec_port); + ret = 0; + +done: + spin_unlock_irqrestore(&pmic_typec_port->lock, flags); + + dev_dbg(dev, "set_cc: currsrc=%x %s mode %s debounce %d attached %d cc=%s\n", + currsrc, rp_sel_to_name(currsrc), + mode == EN_SRC_ONLY ? "EN_SRC_ONLY" : "EN_SNK_ONLY", + pmic_typec_port->debouncing_cc, !!(misc & CC_ATTACHED), + misc_to_cc(misc)); + + return ret; +} + +int qcom_pmic_typec_port_set_vconn(struct pmic_typec_port *pmic_typec_port, bool on) +{ + struct device *dev = pmic_typec_port->dev; + unsigned int orientation, misc, mask, value; + unsigned long flags; + int ret; + + spin_lock_irqsave(&pmic_typec_port->lock, flags); + + ret = regmap_read(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_MISC_STATUS_REG, &misc); + if (ret) + goto done; + + /* Set VCONN on the inversion of the active CC channel */ + orientation = (misc & CC_ORIENTATION) ? 0 : VCONN_EN_ORIENTATION; + if (on) { + mask = VCONN_EN_ORIENTATION | VCONN_EN_VALUE; + value = orientation | VCONN_EN_VALUE | VCONN_EN_SRC; + } else { + mask = VCONN_EN_VALUE; + value = 0; + } + + ret = regmap_update_bits(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_VCONN_CONTROL_REG, + mask, value); +done: + spin_unlock_irqrestore(&pmic_typec_port->lock, flags); + + dev_dbg(dev, "set_vconn: orientation %d control 0x%08x state %s cc %s vconn %s\n", + orientation, value, on ? "on" : "off", misc_to_vconn(misc), misc_to_cc(misc)); + + return ret; +} + +int qcom_pmic_typec_port_start_toggling(struct pmic_typec_port *pmic_typec_port, + enum typec_port_type port_type, + enum typec_cc_status cc) +{ + struct device *dev = pmic_typec_port->dev; + unsigned int misc; + u8 mode = 0; + unsigned long flags; + int ret; + + switch (port_type) { + case TYPEC_PORT_SRC: + mode = EN_SRC_ONLY; + break; + case TYPEC_PORT_SNK: + mode = EN_SNK_ONLY; + break; + case TYPEC_PORT_DRP: + mode = EN_TRY_SNK; + break; + } + + spin_lock_irqsave(&pmic_typec_port->lock, flags); + + ret = regmap_read(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_MISC_STATUS_REG, &misc); + if (ret) + goto done; + + dev_dbg(dev, "start_toggling: misc 0x%08x attached %d port_type %d current cc %d new %d\n", + misc, !!(misc & CC_ATTACHED), port_type, pmic_typec_port->cc, cc); + + qcom_pmic_set_cc_debounce(pmic_typec_port); + + /* force it to toggle at least once */ + ret = regmap_write(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_MODE_CFG_REG, + TYPEC_DISABLE_CMD); + if (ret) + goto done; + + ret = regmap_write(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_MODE_CFG_REG, + mode); +done: + spin_unlock_irqrestore(&pmic_typec_port->lock, flags); + + return ret; +} + +#define TYPEC_INTR_EN_CFG_1_MASK \ + (TYPEC_LEGACY_CABLE_INT_EN | \ + TYPEC_NONCOMPLIANT_LEGACY_CABLE_INT_EN | \ + TYPEC_TRYSOURCE_DETECT_INT_EN | \ + TYPEC_TRYSINK_DETECT_INT_EN | \ + TYPEC_CCOUT_DETACH_INT_EN | \ + TYPEC_CCOUT_ATTACH_INT_EN | \ + TYPEC_VBUS_DEASSERT_INT_EN | \ + TYPEC_VBUS_ASSERT_INT_EN) + +#define TYPEC_INTR_EN_CFG_2_MASK \ + (TYPEC_STATE_MACHINE_CHANGE_INT_EN | TYPEC_VBUS_ERROR_INT_EN | \ + TYPEC_DEBOUNCE_DONE_INT_EN) + +int qcom_pmic_typec_port_start(struct pmic_typec_port *pmic_typec_port, + struct tcpm_port *tcpm_port) +{ + int i; + int mask; + int ret; + + /* Configure interrupt sources */ + ret = regmap_write(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_INTERRUPT_EN_CFG_1_REG, + TYPEC_INTR_EN_CFG_1_MASK); + if (ret) + goto done; + + ret = regmap_write(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_INTERRUPT_EN_CFG_2_REG, + TYPEC_INTR_EN_CFG_2_MASK); + if (ret) + goto done; + + /* start in TRY_SNK mode */ + ret = regmap_write(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_MODE_CFG_REG, EN_TRY_SNK); + if (ret) + goto done; + + /* Configure VCONN for software control */ + ret = regmap_update_bits(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_VCONN_CONTROL_REG, + VCONN_EN_SRC | VCONN_EN_VALUE, VCONN_EN_SRC); + if (ret) + goto done; + + /* Set CC threshold to 1.6 Volts | tPDdebounce = 10-20ms */ + mask = SEL_SRC_UPPER_REF | USE_TPD_FOR_EXITING_ATTACHSRC; + ret = regmap_update_bits(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_EXIT_STATE_CFG_REG, + mask, mask); + if (ret) + goto done; + + pmic_typec_port->tcpm_port = tcpm_port; + + for (i = 0; i < pmic_typec_port->nr_irqs; i++) + enable_irq(pmic_typec_port->irq_data[i].irq); + +done: + return ret; +} + +void qcom_pmic_typec_port_stop(struct pmic_typec_port *pmic_typec_port) +{ + int i; + + for (i = 0; i < pmic_typec_port->nr_irqs; i++) + disable_irq(pmic_typec_port->irq_data[i].irq); +} + +struct pmic_typec_port *qcom_pmic_typec_port_alloc(struct device *dev) +{ + return devm_kzalloc(dev, sizeof(struct pmic_typec_port), GFP_KERNEL); +} + +int qcom_pmic_typec_port_probe(struct platform_device *pdev, + struct pmic_typec_port *pmic_typec_port, + struct pmic_typec_port_resources *res, + struct regmap *regmap, + u32 base) +{ + struct device *dev = &pdev->dev; + struct pmic_typec_port_irq_data *irq_data; + int i, ret, irq; + + if (!res->nr_irqs || res->nr_irqs > PMIC_TYPEC_MAX_IRQS) + return -EINVAL; + + irq_data = devm_kzalloc(dev, sizeof(*irq_data) * res->nr_irqs, + GFP_KERNEL); + if (!irq_data) + return -ENOMEM; + + pmic_typec_port->vdd_vbus = devm_regulator_get(dev, "vdd-vbus"); + if (IS_ERR(pmic_typec_port->vdd_vbus)) + return PTR_ERR(pmic_typec_port->vdd_vbus); + + pmic_typec_port->dev = dev; + pmic_typec_port->base = base; + pmic_typec_port->regmap = regmap; + pmic_typec_port->nr_irqs = res->nr_irqs; + pmic_typec_port->irq_data = irq_data; + spin_lock_init(&pmic_typec_port->lock); + INIT_DELAYED_WORK(&pmic_typec_port->cc_debounce_dwork, + qcom_pmic_typec_port_cc_debounce); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + for (i = 0; i < res->nr_irqs; i++, irq_data++) { + irq = platform_get_irq_byname(pdev, + res->irq_params[i].irq_name); + if (irq < 0) + return irq; + + irq_data->pmic_typec_port = pmic_typec_port; + irq_data->irq = irq; + irq_data->virq = res->irq_params[i].virq; + ret = devm_request_threaded_irq(dev, irq, NULL, pmic_typec_port_isr, + IRQF_ONESHOT | IRQF_NO_AUTOEN, + res->irq_params[i].irq_name, + irq_data); + if (ret) + return ret; + } + + return 0; +} diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.h b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.h new file mode 100644 index 000000000000..d4d358c680b6 --- /dev/null +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.h @@ -0,0 +1,195 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved. + * Copyright (c) 2023, Linaro Ltd. All rights reserved. + */ +#ifndef __QCOM_PMIC_TYPEC_H__ +#define __QCOM_PMIC_TYPEC_H__ + +#include <linux/platform_device.h> +#include <linux/usb/tcpm.h> + +#define TYPEC_SNK_STATUS_REG 0x06 +#define DETECTED_SNK_TYPE_MASK GENMASK(6, 0) +#define SNK_DAM_MASK GENMASK(6, 4) +#define SNK_DAM_500MA BIT(6) +#define SNK_DAM_1500MA BIT(5) +#define SNK_DAM_3000MA BIT(4) +#define SNK_RP_STD BIT(3) +#define SNK_RP_1P5 BIT(2) +#define SNK_RP_3P0 BIT(1) +#define SNK_RP_SHORT BIT(0) + +#define TYPEC_SRC_STATUS_REG 0x08 +#define DETECTED_SRC_TYPE_MASK GENMASK(4, 0) +#define SRC_HIGH_BATT BIT(5) +#define SRC_DEBUG_ACCESS BIT(4) +#define SRC_RD_OPEN BIT(3) +#define SRC_RD_RA_VCONN BIT(2) +#define SRC_RA_OPEN BIT(1) +#define AUDIO_ACCESS_RA_RA BIT(0) + +#define TYPEC_STATE_MACHINE_STATUS_REG 0x09 +#define TYPEC_ATTACH_DETACH_STATE BIT(5) + +#define TYPEC_SM_STATUS_REG 0x0A +#define TYPEC_SM_VBUS_VSAFE5V BIT(5) +#define TYPEC_SM_VBUS_VSAFE0V BIT(6) +#define TYPEC_SM_USBIN_LT_LV BIT(7) + +#define TYPEC_MISC_STATUS_REG 0x0B +#define TYPEC_WATER_DETECTION_STATUS BIT(7) +#define SNK_SRC_MODE BIT(6) +#define TYPEC_VBUS_DETECT BIT(5) +#define TYPEC_VBUS_ERROR_STATUS BIT(4) +#define TYPEC_DEBOUNCE_DONE BIT(3) +#define CC_ORIENTATION BIT(1) +#define CC_ATTACHED BIT(0) + +#define LEGACY_CABLE_STATUS_REG 0x0D +#define TYPEC_LEGACY_CABLE_STATUS BIT(1) +#define TYPEC_NONCOMP_LEGACY_CABLE_STATUS BIT(0) + +#define TYPEC_U_USB_STATUS_REG 0x0F +#define U_USB_GROUND_NOVBUS BIT(6) +#define U_USB_GROUND BIT(4) +#define U_USB_FMB1 BIT(3) +#define U_USB_FLOAT1 BIT(2) +#define U_USB_FMB2 BIT(1) +#define U_USB_FLOAT2 BIT(0) + +#define TYPEC_MODE_CFG_REG 0x44 +#define TYPEC_TRY_MODE_MASK GENMASK(4, 3) +#define EN_TRY_SNK BIT(4) +#define EN_TRY_SRC BIT(3) +#define TYPEC_POWER_ROLE_CMD_MASK GENMASK(2, 0) +#define EN_SRC_ONLY BIT(2) +#define EN_SNK_ONLY BIT(1) +#define TYPEC_DISABLE_CMD BIT(0) + +#define TYPEC_VCONN_CONTROL_REG 0x46 +#define VCONN_EN_ORIENTATION BIT(2) +#define VCONN_EN_VALUE BIT(1) +#define VCONN_EN_SRC BIT(0) + +#define TYPEC_CCOUT_CONTROL_REG 0x48 +#define TYPEC_CCOUT_BUFFER_EN BIT(2) +#define TYPEC_CCOUT_VALUE BIT(1) +#define TYPEC_CCOUT_SRC BIT(0) + +#define DEBUG_ACCESS_SRC_CFG_REG 0x4C +#define EN_UNORIENTED_DEBUG_ACCESS_SRC BIT(0) + +#define TYPE_C_CRUDE_SENSOR_CFG_REG 0x4e +#define EN_SRC_CRUDE_SENSOR BIT(1) +#define EN_SNK_CRUDE_SENSOR BIT(0) + +#define TYPEC_EXIT_STATE_CFG_REG 0x50 +#define BYPASS_VSAFE0V_DURING_ROLE_SWAP BIT(3) +#define SEL_SRC_UPPER_REF BIT(2) +#define USE_TPD_FOR_EXITING_ATTACHSRC BIT(1) +#define EXIT_SNK_BASED_ON_CC BIT(0) + +#define TYPEC_CURRSRC_CFG_REG 0x52 +#define TYPEC_SRC_RP_SEL_330UA BIT(1) +#define TYPEC_SRC_RP_SEL_180UA BIT(0) +#define TYPEC_SRC_RP_SEL_80UA 0 +#define TYPEC_SRC_RP_SEL_MASK GENMASK(1, 0) + +#define TYPEC_INTERRUPT_EN_CFG_1_REG 0x5E +#define TYPEC_LEGACY_CABLE_INT_EN BIT(7) +#define TYPEC_NONCOMPLIANT_LEGACY_CABLE_INT_EN BIT(6) +#define TYPEC_TRYSOURCE_DETECT_INT_EN BIT(5) +#define TYPEC_TRYSINK_DETECT_INT_EN BIT(4) +#define TYPEC_CCOUT_DETACH_INT_EN BIT(3) +#define TYPEC_CCOUT_ATTACH_INT_EN BIT(2) +#define TYPEC_VBUS_DEASSERT_INT_EN BIT(1) +#define TYPEC_VBUS_ASSERT_INT_EN BIT(0) + +#define TYPEC_INTERRUPT_EN_CFG_2_REG 0x60 +#define TYPEC_SRC_BATT_HPWR_INT_EN BIT(6) +#define MICRO_USB_STATE_CHANGE_INT_EN BIT(5) +#define TYPEC_STATE_MACHINE_CHANGE_INT_EN BIT(4) +#define TYPEC_DEBUG_ACCESS_DETECT_INT_EN BIT(3) +#define TYPEC_WATER_DETECTION_INT_EN BIT(2) +#define TYPEC_VBUS_ERROR_INT_EN BIT(1) +#define TYPEC_DEBOUNCE_DONE_INT_EN BIT(0) + +#define TYPEC_DEBOUNCE_OPTION_REG 0x62 +#define REDUCE_TCCDEBOUNCE_TO_2MS BIT(2) + +#define TYPE_C_SBU_CFG_REG 0x6A +#define SEL_SBU1_ISRC_VAL 0x04 +#define SEL_SBU2_ISRC_VAL 0x01 + +#define TYPEC_U_USB_CFG_REG 0x70 +#define EN_MICRO_USB_FACTORY_MODE BIT(1) +#define EN_MICRO_USB_MODE BIT(0) + +#define TYPEC_PMI632_U_USB_WATER_PROTECTION_CFG_REG 0x72 + +#define TYPEC_U_USB_WATER_PROTECTION_CFG_REG 0x73 +#define EN_MICRO_USB_WATER_PROTECTION BIT(4) +#define MICRO_USB_DETECTION_ON_TIME_CFG_MASK GENMASK(3, 2) +#define MICRO_USB_DETECTION_PERIOD_CFG_MASK GENMASK(1, 0) + +#define TYPEC_PMI632_MICRO_USB_MODE_REG 0x73 +#define MICRO_USB_MODE_ONLY BIT(0) + +/* Interrupt numbers */ +#define PMIC_TYPEC_OR_RID_IRQ 0x0 +#define PMIC_TYPEC_VPD_IRQ 0x1 +#define PMIC_TYPEC_CC_STATE_IRQ 0x2 +#define PMIC_TYPEC_VCONN_OC_IRQ 0x3 +#define PMIC_TYPEC_VBUS_IRQ 0x4 +#define PMIC_TYPEC_ATTACH_DETACH_IRQ 0x5 +#define PMIC_TYPEC_LEGACY_CABLE_IRQ 0x6 +#define PMIC_TYPEC_TRY_SNK_SRC_IRQ 0x7 + +/* Resources */ +#define PMIC_TYPEC_MAX_IRQS 0x08 + +struct pmic_typec_port_irq_params { + int virq; + char *irq_name; +}; + +struct pmic_typec_port_resources { + unsigned int nr_irqs; + struct pmic_typec_port_irq_params irq_params[PMIC_TYPEC_MAX_IRQS]; +}; + +/* API */ +struct pmic_typec; + +struct pmic_typec_port *qcom_pmic_typec_port_alloc(struct device *dev); + +int qcom_pmic_typec_port_probe(struct platform_device *pdev, + struct pmic_typec_port *pmic_typec_port, + struct pmic_typec_port_resources *res, + struct regmap *regmap, + u32 base); + +int qcom_pmic_typec_port_start(struct pmic_typec_port *pmic_typec_port, + struct tcpm_port *tcpm_port); + +void qcom_pmic_typec_port_stop(struct pmic_typec_port *pmic_typec_port); + +int qcom_pmic_typec_port_get_cc(struct pmic_typec_port *pmic_typec_port, + enum typec_cc_status *cc1, + enum typec_cc_status *cc2); + +int qcom_pmic_typec_port_set_cc(struct pmic_typec_port *pmic_typec_port, + enum typec_cc_status cc); + +int qcom_pmic_typec_port_get_vbus(struct pmic_typec_port *pmic_typec_port); + +int qcom_pmic_typec_port_set_vconn(struct pmic_typec_port *pmic_typec_port, bool on); + +int qcom_pmic_typec_port_start_toggling(struct pmic_typec_port *pmic_typec_port, + enum typec_port_type port_type, + enum typec_cc_status cc); + +int qcom_pmic_typec_port_set_vbus(struct pmic_typec_port *pmic_typec_port, bool on); + +#endif /* __QCOM_PMIC_TYPE_C_PORT_H__ */ diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index 8da23240afbe..fc708c289a73 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -895,7 +895,7 @@ static struct i2c_driver tcpci_i2c_driver = { .name = "tcpci", .of_match_table = of_match_ptr(tcpci_of_match), }, - .probe_new = tcpci_probe, + .probe = tcpci_probe, .remove = tcpci_remove, .id_table = tcpci_id, }; diff --git a/drivers/usb/typec/tcpm/tcpci_maxim_core.c b/drivers/usb/typec/tcpm/tcpci_maxim_core.c index f32cda2a5e3a..9454b12a073c 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim_core.c +++ b/drivers/usb/typec/tcpm/tcpci_maxim_core.c @@ -508,7 +508,7 @@ static struct i2c_driver max_tcpci_i2c_driver = { .name = "maxtcpc", .of_match_table = of_match_ptr(max_tcpci_of_match), }, - .probe_new = max_tcpci_probe, + .probe = max_tcpci_probe, .remove = max_tcpci_remove, .id_table = max_tcpci_id, }; diff --git a/drivers/usb/typec/tcpm/tcpci_mt6360.c b/drivers/usb/typec/tcpm/tcpci_mt6360.c index 6fa8fd5c8041..02b7fd302265 100644 --- a/drivers/usb/typec/tcpm/tcpci_mt6360.c +++ b/drivers/usb/typec/tcpm/tcpci_mt6360.c @@ -178,13 +178,12 @@ static int mt6360_tcpc_probe(struct platform_device *pdev) return 0; } -static int mt6360_tcpc_remove(struct platform_device *pdev) +static void mt6360_tcpc_remove(struct platform_device *pdev) { struct mt6360_tcpc_info *mti = platform_get_drvdata(pdev); disable_irq(mti->irq); tcpci_unregister_port(mti->tcpci); - return 0; } static int __maybe_unused mt6360_tcpc_suspend(struct device *dev) @@ -222,7 +221,7 @@ static struct platform_driver mt6360_tcpc_driver = { .of_match_table = mt6360_tcpc_of_id, }, .probe = mt6360_tcpc_probe, - .remove = mt6360_tcpc_remove, + .remove_new = mt6360_tcpc_remove, }; module_platform_driver(mt6360_tcpc_driver); diff --git a/drivers/usb/typec/tcpm/tcpci_mt6370.c b/drivers/usb/typec/tcpm/tcpci_mt6370.c index c5bb201a5163..2a079464b398 100644 --- a/drivers/usb/typec/tcpm/tcpci_mt6370.c +++ b/drivers/usb/typec/tcpm/tcpci_mt6370.c @@ -178,12 +178,10 @@ static int mt6370_tcpc_probe(struct platform_device *pdev) return 0; } -static int mt6370_tcpc_remove(struct platform_device *pdev) +static void mt6370_tcpc_remove(struct platform_device *pdev) { dev_pm_clear_wake_irq(&pdev->dev); device_init_wakeup(&pdev->dev, false); - - return 0; } static const struct of_device_id mt6370_tcpc_devid_table[] = { @@ -198,7 +196,7 @@ static struct platform_driver mt6370_tcpc_driver = { .of_match_table = mt6370_tcpc_devid_table, }, .probe = mt6370_tcpc_probe, - .remove = mt6370_tcpc_remove, + .remove_new = mt6370_tcpc_remove, }; module_platform_driver(mt6370_tcpc_driver); diff --git a/drivers/usb/typec/tcpm/tcpci_rt1711h.c b/drivers/usb/typec/tcpm/tcpci_rt1711h.c index a0e9e3fe8564..17ebc5fb684f 100644 --- a/drivers/usb/typec/tcpm/tcpci_rt1711h.c +++ b/drivers/usb/typec/tcpm/tcpci_rt1711h.c @@ -412,7 +412,7 @@ static struct i2c_driver rt1711h_i2c_driver = { .name = "rt1711h", .of_match_table = of_match_ptr(rt1711h_of_match), }, - .probe_new = rt1711h_probe, + .probe = rt1711h_probe, .remove = rt1711h_remove, .id_table = rt1711h_id, }; diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 3c6b0c8e2d3a..829d75ebab42 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -4885,7 +4885,8 @@ static void run_state_machine(struct tcpm_port *port) break; case PORT_RESET: tcpm_reset_port(port); - tcpm_set_cc(port, TYPEC_CC_OPEN); + tcpm_set_cc(port, tcpm_default_state(port) == SNK_UNATTACHED ? + TYPEC_CC_RD : tcpm_rp_cc(port)); tcpm_set_state(port, PORT_RESET_WAIT_OFF, PD_T_ERROR_RECOVERY); break; @@ -6339,6 +6340,27 @@ static int tcpm_psy_get_current_now(struct tcpm_port *port, return 0; } +static int tcpm_psy_get_input_power_limit(struct tcpm_port *port, + union power_supply_propval *val) +{ + unsigned int src_mv, src_ma, max_src_uw = 0; + unsigned int i, tmp; + + for (i = 0; i < port->nr_source_caps; i++) { + u32 pdo = port->source_caps[i]; + + if (pdo_type(pdo) == PDO_TYPE_FIXED) { + src_mv = pdo_fixed_voltage(pdo); + src_ma = pdo_max_current(pdo); + tmp = src_mv * src_ma; + max_src_uw = tmp > max_src_uw ? tmp : max_src_uw; + } + } + + val->intval = max_src_uw; + return 0; +} + static int tcpm_psy_get_prop(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -6368,6 +6390,9 @@ static int tcpm_psy_get_prop(struct power_supply *psy, case POWER_SUPPLY_PROP_CURRENT_NOW: ret = tcpm_psy_get_current_now(port, val); break; + case POWER_SUPPLY_PROP_INPUT_POWER_LIMIT: + tcpm_psy_get_input_power_limit(port, val); + break; default: ret = -EINVAL; break; diff --git a/drivers/usb/typec/tcpm/wcove.c b/drivers/usb/typec/tcpm/wcove.c index 20917d85d6f4..87d4abde0ea2 100644 --- a/drivers/usb/typec/tcpm/wcove.c +++ b/drivers/usb/typec/tcpm/wcove.c @@ -671,7 +671,7 @@ static int wcove_typec_probe(struct platform_device *pdev) return 0; } -static int wcove_typec_remove(struct platform_device *pdev) +static void wcove_typec_remove(struct platform_device *pdev) { struct wcove_typec *wcove = platform_get_drvdata(pdev); unsigned int val; @@ -684,8 +684,6 @@ static int wcove_typec_remove(struct platform_device *pdev) tcpm_unregister_port(wcove->tcpm); fwnode_remove_software_node(wcove->tcpc.fwnode); - - return 0; } static struct platform_driver wcove_typec_driver = { @@ -693,7 +691,7 @@ static struct platform_driver wcove_typec_driver = { .name = "bxt_wcove_usbc", }, .probe = wcove_typec_probe, - .remove = wcove_typec_remove, + .remove_new = wcove_typec_remove, }; module_platform_driver(wcove_typec_driver); diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 603dbd44deba..37b56ce75f39 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -950,7 +950,7 @@ static struct i2c_driver tps6598x_i2c_driver = { .pm = &tps6598x_pm_ops, .of_match_table = tps6598x_of_match, }, - .probe_new = tps6598x_probe, + .probe = tps6598x_probe, .remove = tps6598x_remove, .id_table = tps6598x_id, }; diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index b664ecbb798b..9b6d6b14431f 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -812,6 +812,23 @@ static void ucsi_partner_change(struct ucsi_connector *con) break; } + if (con->status.flags & UCSI_CONSTAT_CONNECTED) { + switch (UCSI_CONSTAT_PARTNER_TYPE(con->status.flags)) { + case UCSI_CONSTAT_PARTNER_TYPE_DEBUG: + typec_set_mode(con->port, TYPEC_MODE_DEBUG); + break; + case UCSI_CONSTAT_PARTNER_TYPE_AUDIO: + typec_set_mode(con->port, TYPEC_MODE_AUDIO); + break; + default: + if (UCSI_CONSTAT_PARTNER_FLAGS(con->status.flags) == + UCSI_CONSTAT_PARTNER_FLAG_USB) + typec_set_mode(con->port, TYPEC_STATE_USB); + } + } else { + typec_set_mode(con->port, TYPEC_STATE_SAFE); + } + /* Only notify USB controller if partner supports USB data */ if (!(UCSI_CONSTAT_PARTNER_FLAGS(con->status.flags) & UCSI_CONSTAT_PARTNER_FLAG_USB)) u_role = USB_ROLE_NONE; diff --git a/drivers/usb/typec/ucsi/ucsi_acpi.c b/drivers/usb/typec/ucsi/ucsi_acpi.c index 217355f1f9b9..6bbf490ac401 100644 --- a/drivers/usb/typec/ucsi/ucsi_acpi.c +++ b/drivers/usb/typec/ucsi/ucsi_acpi.c @@ -212,7 +212,7 @@ static int ucsi_acpi_probe(struct platform_device *pdev) return 0; } -static int ucsi_acpi_remove(struct platform_device *pdev) +static void ucsi_acpi_remove(struct platform_device *pdev) { struct ucsi_acpi *ua = platform_get_drvdata(pdev); @@ -221,8 +221,6 @@ static int ucsi_acpi_remove(struct platform_device *pdev) acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), ACPI_DEVICE_NOTIFY, ucsi_acpi_notify); - - return 0; } static int ucsi_acpi_resume(struct device *dev) @@ -247,7 +245,7 @@ static struct platform_driver ucsi_acpi_platform_driver = { .acpi_match_table = ACPI_PTR(ucsi_acpi_match), }, .probe = ucsi_acpi_probe, - .remove = ucsi_acpi_remove, + .remove_new = ucsi_acpi_remove, }; module_platform_driver(ucsi_acpi_platform_driver); diff --git a/drivers/usb/typec/ucsi/ucsi_ccg.c b/drivers/usb/typec/ucsi/ucsi_ccg.c index e0ed465bd518..607061a37eca 100644 --- a/drivers/usb/typec/ucsi/ucsi_ccg.c +++ b/drivers/usb/typec/ucsi/ucsi_ccg.c @@ -1495,7 +1495,7 @@ static struct i2c_driver ucsi_ccg_driver = { .acpi_match_table = amd_i2c_ucsi_match, .of_match_table = ucsi_ccg_of_match_table, }, - .probe_new = ucsi_ccg_probe, + .probe = ucsi_ccg_probe, .remove = ucsi_ccg_remove, .id_table = ucsi_ccg_device_id, }; diff --git a/drivers/usb/typec/ucsi/ucsi_glink.c b/drivers/usb/typec/ucsi/ucsi_glink.c index b454a5159896..1fe9cb5b6bd9 100644 --- a/drivers/usb/typec/ucsi/ucsi_glink.c +++ b/drivers/usb/typec/ucsi/ucsi_glink.c @@ -245,7 +245,7 @@ static void pmic_glink_ucsi_callback(const void *data, size_t len, void *priv) struct pmic_glink_ucsi *ucsi = priv; const struct pmic_glink_hdr *hdr = data; - switch (hdr->opcode) { + switch (le32_to_cpu(hdr->opcode)) { case UC_UCSI_READ_BUF_REQ: pmic_glink_ucsi_read_ack(ucsi, data, len); break; diff --git a/drivers/usb/typec/ucsi/ucsi_stm32g0.c b/drivers/usb/typec/ucsi/ucsi_stm32g0.c index 93fead0096b7..93d7806681cf 100644 --- a/drivers/usb/typec/ucsi/ucsi_stm32g0.c +++ b/drivers/usb/typec/ucsi/ucsi_stm32g0.c @@ -763,7 +763,7 @@ static struct i2c_driver ucsi_stm32g0_i2c_driver = { .of_match_table = of_match_ptr(ucsi_stm32g0_typec_of_match), .pm = pm_sleep_ptr(&ucsi_stm32g0_pm_ops), }, - .probe_new = ucsi_stm32g0_probe, + .probe = ucsi_stm32g0_probe, .remove = ucsi_stm32g0_remove, .id_table = ucsi_stm32g0_typec_i2c_devid }; diff --git a/drivers/usb/typec/wusb3801.c b/drivers/usb/typec/wusb3801.c index a43a18d4b02e..6062875fb04a 100644 --- a/drivers/usb/typec/wusb3801.c +++ b/drivers/usb/typec/wusb3801.c @@ -420,7 +420,7 @@ static const struct of_device_id wusb3801_of_match[] = { MODULE_DEVICE_TABLE(of, wusb3801_of_match); static struct i2c_driver wusb3801_driver = { - .probe_new = wusb3801_probe, + .probe = wusb3801_probe, .remove = wusb3801_remove, .driver = { .name = "wusb3801", diff --git a/drivers/usb/usbip/stub_main.c b/drivers/usb/usbip/stub_main.c index e8c3131a8543..0a6624d37929 100644 --- a/drivers/usb/usbip/stub_main.c +++ b/drivers/usb/usbip/stub_main.c @@ -167,15 +167,13 @@ static ssize_t match_busid_show(struct device_driver *drv, char *buf) static ssize_t match_busid_store(struct device_driver *dev, const char *buf, size_t count) { - int len; char busid[BUSID_SIZE]; if (count < 5) return -EINVAL; /* busid needs to include \0 termination */ - len = strlcpy(busid, buf + 4, BUSID_SIZE); - if (sizeof(busid) <= len) + if (strscpy(busid, buf + 4, BUSID_SIZE) < 0) return -EINVAL; if (!strncmp(buf, "add ", 4)) { diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c index 233265550fc6..37d1fc34e8a5 100644 --- a/drivers/usb/usbip/vhci_hcd.c +++ b/drivers/usb/usbip/vhci_hcd.c @@ -1393,7 +1393,7 @@ put_usb2_hcd: return ret; } -static int vhci_hcd_remove(struct platform_device *pdev) +static void vhci_hcd_remove(struct platform_device *pdev) { struct vhci *vhci = *((void **)dev_get_platdata(&pdev->dev)); @@ -1410,8 +1410,6 @@ static int vhci_hcd_remove(struct platform_device *pdev) vhci->vhci_hcd_hs = NULL; vhci->vhci_hcd_ss = NULL; - - return 0; } #ifdef CONFIG_PM @@ -1485,7 +1483,7 @@ static int vhci_hcd_resume(struct platform_device *pdev) static struct platform_driver vhci_driver = { .probe = vhci_hcd_probe, - .remove = vhci_hcd_remove, + .remove_new = vhci_hcd_remove, .suspend = vhci_hcd_suspend, .resume = vhci_hcd_resume, .driver = { diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index 90cd08ab2f5d..02333f47c994 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -172,6 +172,20 @@ int tb_register_property_dir(const char *key, struct tb_property_dir *dir); 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_RX: Dual lane asymmetric Gen 4 link with 3 receivers + */ +enum tb_link_width { + TB_LINK_WIDTH_SINGLE = BIT(0), + TB_LINK_WIDTH_DUAL = BIT(1), + TB_LINK_WIDTH_ASYM_TX = BIT(2), + TB_LINK_WIDTH_ASYM_RX = BIT(3), +}; + +/** * struct tb_xdomain - Cross-domain (XDomain) connection * @dev: XDomain device * @tb: Pointer to the domain @@ -186,7 +200,7 @@ void tb_unregister_property_dir(const char *key, struct tb_property_dir *dir); * @vendor_name: Name of the vendor (or %NULL if not known) * @device_name: Name of the device (or %NULL if not known) * @link_speed: Speed of the link in Gb/s - * @link_width: Width of the link (1 or 2) + * @link_width: Width of the downstream facing link * @link_usb4: Downstream link is USB4 * @is_unplugged: The XDomain is unplugged * @needs_uuid: If the XDomain does not have @remote_uuid it will be @@ -234,7 +248,7 @@ struct tb_xdomain { const char *vendor_name; const char *device_name; unsigned int link_speed; - unsigned int link_width; + enum tb_link_width link_width; bool link_usb4; bool is_unplugged; bool needs_uuid; diff --git a/include/linux/ulpi/driver.h b/include/linux/ulpi/driver.h index c7a1810373e3..a8cb617a3028 100644 --- a/include/linux/ulpi/driver.h +++ b/include/linux/ulpi/driver.h @@ -15,9 +15,9 @@ struct ulpi_ops; * @dev: device interface */ struct ulpi { + struct device dev; struct ulpi_device_id id; const struct ulpi_ops *ops; - struct device dev; }; #define to_ulpi_dev(d) container_of(d, struct ulpi, dev) diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index 0c7eff91adf4..4e9623e8492b 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -267,7 +267,7 @@ struct hc_driver { int (*pci_suspend)(struct usb_hcd *hcd, bool do_wakeup); /* called after entering D0 (etc), before resuming the hub */ - int (*pci_resume)(struct usb_hcd *hcd, bool hibernated); + int (*pci_resume)(struct usb_hcd *hcd, pm_message_t state); /* called just before hibernate final D3 state, allows host to poweroff parts */ int (*pci_poweroff_late)(struct usb_hcd *hcd, bool do_wakeup); diff --git a/include/linux/usb/serial.h b/include/linux/usb/serial.h index 7eeb5f9c4f0d..1a0a4dc87980 100644 --- a/include/linux/usb/serial.h +++ b/include/linux/usb/serial.h @@ -278,7 +278,7 @@ struct usb_serial_driver { int (*set_serial)(struct tty_struct *tty, struct serial_struct *ss); void (*set_termios)(struct tty_struct *tty, struct usb_serial_port *port, const struct ktermios *old); - void (*break_ctl)(struct tty_struct *tty, int break_state); + int (*break_ctl)(struct tty_struct *tty, int break_state); unsigned int (*chars_in_buffer)(struct tty_struct *tty); void (*wait_until_sent)(struct tty_struct *tty, long timeout); bool (*tx_empty)(struct usb_serial_port *port); diff --git a/include/linux/usb/typec_mux.h b/include/linux/usb/typec_mux.h index 9292f0e07846..2489a7857d8e 100644 --- a/include/linux/usb/typec_mux.h +++ b/include/linux/usb/typec_mux.h @@ -60,8 +60,7 @@ struct typec_mux_desc { #if IS_ENABLED(CONFIG_TYPEC) -struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode, - const struct typec_altmode_desc *desc); +struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode); void typec_mux_put(struct typec_mux *mux); int typec_mux_set(struct typec_mux *mux, struct typec_mux_state *state); @@ -74,8 +73,7 @@ void *typec_mux_get_drvdata(struct typec_mux_dev *mux); #else -static inline struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode, - const struct typec_altmode_desc *desc) +static inline struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode) { return NULL; } @@ -102,10 +100,9 @@ static inline void *typec_mux_get_drvdata(struct typec_mux_dev *mux) #endif /* CONFIG_TYPEC */ -static inline struct typec_mux * -typec_mux_get(struct device *dev, const struct typec_altmode_desc *desc) +static inline struct typec_mux *typec_mux_get(struct device *dev) { - return fwnode_typec_mux_get(dev_fwnode(dev), desc); + return fwnode_typec_mux_get(dev_fwnode(dev)); } #endif /* __USB_TYPEC_MUX */ diff --git a/include/uapi/linux/usb/ch9.h b/include/uapi/linux/usb/ch9.h index b17e3a21b15f..82ec6af71a1d 100644 --- a/include/uapi/linux/usb/ch9.h +++ b/include/uapi/linux/usb/ch9.h @@ -376,7 +376,10 @@ struct usb_string_descriptor { __u8 bLength; __u8 bDescriptorType; - __le16 wData[1]; /* UTF-16LE encoded */ + union { + __le16 legacy_padding; + __DECLARE_FLEX_ARRAY(__le16, wData); /* UTF-16LE encoded */ + }; } __attribute__ ((packed)); /* note that "string" zero is special, it holds language codes that diff --git a/tools/usb/usbip/configure.ac b/tools/usb/usbip/configure.ac index 607d05c5ccfd..8debf934f8b7 100644 --- a/tools/usb/usbip/configure.ac +++ b/tools/usb/usbip/configure.ac @@ -94,11 +94,11 @@ AC_SUBST([USBIDS_DIR]) AC_MSG_CHECKING([whether to use fortify]) AC_ARG_WITH([fortify], [AS_HELP_STRING([--with-fortify], - [use _FORTIFY_SROUCE option when compiling)])], + [use _FORTIFY_SOURCE=2 option when compiling)])], dnl [ACTION-IF-GIVEN] [if test "$withval" = "yes"; then AC_MSG_RESULT([yes]) - CFLAGS="$CFLAGS -D_FORTIFY_SOURCE -O" + CFLAGS="$CFLAGS -D_FORTIFY_SOURCE=2 -O" else AC_MSG_RESULT([no]) CFLAGS="$CFLAGS -U_FORTIFY_SOURCE" diff --git a/tools/usb/usbip/src/usbip_attach.c b/tools/usb/usbip/src/usbip_attach.c index b4aeb9f1f493..531a415538f9 100644 --- a/tools/usb/usbip/src/usbip_attach.c +++ b/tools/usb/usbip/src/usbip_attach.c @@ -86,7 +86,7 @@ static int import_device(int sockfd, struct usbip_usb_device *udev) rc = usbip_vhci_driver_open(); if (rc < 0) { - err("open vhci_driver"); + err("open vhci_driver (is vhci_hcd loaded?)"); goto err_out; } diff --git a/tools/usb/usbip/src/usbip_detach.c b/tools/usb/usbip/src/usbip_detach.c index aec993159036..b29101986b5a 100644 --- a/tools/usb/usbip/src/usbip_detach.c +++ b/tools/usb/usbip/src/usbip_detach.c @@ -50,7 +50,7 @@ static int detach_port(char *port) ret = usbip_vhci_driver_open(); if (ret < 0) { - err("open vhci_driver"); + err("open vhci_driver (is vhci_hcd loaded?)"); return -1; } diff --git a/tools/usb/usbip/src/usbip_port.c b/tools/usb/usbip/src/usbip_port.c index 4d14387df13d..21a20e378419 100644 --- a/tools/usb/usbip/src/usbip_port.c +++ b/tools/usb/usbip/src/usbip_port.c @@ -18,7 +18,7 @@ static int list_imported_devices(void) ret = usbip_vhci_driver_open(); if (ret < 0) { - err("open vhci_driver"); + err("open vhci_driver (is vhci_hcd loaded?)"); goto err_names_free; } |