diff options
212 files changed, 11167 insertions, 1742 deletions
diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec index d7647b258c3c..b834671522d6 100644 --- a/Documentation/ABI/testing/sysfs-class-typec +++ b/Documentation/ABI/testing/sysfs-class-typec @@ -20,13 +20,13 @@ Date: April 2017 Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> Description: The supported power roles. This attribute can be used to request - power role swap on the port when the port supports USB Power - Delivery. Swapping is supported as synchronous operation, so - write(2) to the attribute will not return until the operation - has finished. The attribute is notified about role changes so - that poll(2) on the attribute wakes up. Change on the role will - also generate uevent KOBJ_CHANGE. The current role is show in - brackets, for example "[source] sink" when in source mode. + power role swap on the port. Swapping is supported as + synchronous operation, so write(2) to the attribute will not + return until the operation has finished. The attribute is + notified about role changes so that poll(2) on the attribute + wakes up. Change on the role will also generate uevent + KOBJ_CHANGE. The current role is show in brackets, for example + "[source] sink" when in source mode. Valid values: source, sink @@ -108,6 +108,15 @@ Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> Description: Revision number of the supported USB Type-C specification. +What: /sys/class/typec/<port>/orientation +Date: February 2020 +Contact: Badhri Jagan Sridharan <badhri@google.com> +Description: + Indicates the active orientation of the Type-C connector. + Valid values: + - "normal": CC1 orientation + - "reverse": CC2 orientation + - "unknown": Orientation cannot be determined. USB Type-C partner devices (eg. /sys/class/typec/port0-partner/) diff --git a/Documentation/devicetree/bindings/phy/amlogic,meson-g12a-usb2-phy.yaml b/Documentation/devicetree/bindings/phy/amlogic,meson-g12a-usb2-phy.yaml index 57d8603076bd..9e32cb43fb21 100644 --- a/Documentation/devicetree/bindings/phy/amlogic,meson-g12a-usb2-phy.yaml +++ b/Documentation/devicetree/bindings/phy/amlogic,meson-g12a-usb2-phy.yaml @@ -14,6 +14,7 @@ properties: compatible: enum: - amlogic,meson-g12a-usb2-phy + - amlogic,meson-a1-usb2-phy reg: maxItems: 1 @@ -49,6 +50,19 @@ required: - reset-names - "#phy-cells" +if: + properties: + compatible: + enum: + - amlogic,meson-a1-usb-ctrl + +then: + properties: + power-domains: + maxItems: 1 + required: + - power-domains + examples: - | phy@36000 { diff --git a/Documentation/devicetree/bindings/phy/phy-cadence-dp.txt b/Documentation/devicetree/bindings/phy/phy-cadence-dp.txt deleted file mode 100644 index 7f49fd54ebc1..000000000000 --- a/Documentation/devicetree/bindings/phy/phy-cadence-dp.txt +++ /dev/null @@ -1,30 +0,0 @@ -Cadence MHDP DisplayPort SD0801 PHY binding -=========================================== - -This binding describes the Cadence SD0801 PHY hardware included with -the Cadence MHDP DisplayPort controller. - -------------------------------------------------------------------------------- -Required properties (controller (parent) node): -- compatible : Should be "cdns,dp-phy" -- reg : Defines the following sets of registers in the parent - mhdp device: - - Offset of the DPTX PHY configuration registers - - Offset of the SD0801 PHY configuration registers -- #phy-cells : from the generic PHY bindings, must be 0. - -Optional properties: -- num_lanes : Number of DisplayPort lanes to use (1, 2 or 4) -- max_bit_rate : Maximum DisplayPort link bit rate to use, in Mbps (2160, - 2430, 2700, 3240, 4320, 5400 or 8100) -------------------------------------------------------------------------------- - -Example: - dp_phy: phy@f0fb030a00 { - compatible = "cdns,dp-phy"; - reg = <0xf0 0xfb030a00 0x0 0x00000040>, - <0xf0 0xfb500000 0x0 0x00100000>; - num_lanes = <4>; - max_bit_rate = <8100>; - #phy-cells = <0>; - }; diff --git a/Documentation/devicetree/bindings/phy/phy-cadence-torrent.yaml b/Documentation/devicetree/bindings/phy/phy-cadence-torrent.yaml new file mode 100644 index 000000000000..c779a3c7d87a --- /dev/null +++ b/Documentation/devicetree/bindings/phy/phy-cadence-torrent.yaml @@ -0,0 +1,143 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: "http://devicetree.org/schemas/phy/phy-cadence-torrent.yaml#" +$schema: "http://devicetree.org/meta-schemas/core.yaml#" + +title: Cadence Torrent SD0801 PHY binding for DisplayPort + +description: + This binding describes the Cadence SD0801 PHY (also known as Torrent PHY) + hardware included with the Cadence MHDP DisplayPort controller. + +maintainers: + - Swapnil Jakhade <sjakhade@cadence.com> + - Yuti Amonkar <yamonkar@cadence.com> + +properties: + compatible: + enum: + - cdns,torrent-phy + - ti,j721e-serdes-10g + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + clocks: + maxItems: 1 + description: + PHY reference clock. Must contain an entry in clock-names. + + clock-names: + const: refclk + + reg: + minItems: 1 + maxItems: 2 + items: + - description: Offset of the Torrent PHY configuration registers. + - description: Offset of the DPTX PHY configuration registers. + + reg-names: + minItems: 1 + maxItems: 2 + items: + - const: torrent_phy + - const: dptx_phy + + resets: + maxItems: 1 + description: + Torrent PHY reset. + See Documentation/devicetree/bindings/reset/reset.txt + +patternProperties: + '^phy@[0-7]+$': + type: object + description: + Each group of PHY lanes with a single master lane should be represented as a sub-node. + properties: + reg: + description: + The master lane number. This is the lowest numbered lane in the lane group. + + resets: + minItems: 1 + maxItems: 4 + description: + Contains list of resets, one per lane, to get all the link lanes out of reset. + + "#phy-cells": + const: 0 + + cdns,phy-type: + description: + Specifies the type of PHY for which the group of PHY lanes is used. + Refer include/dt-bindings/phy/phy.h. Constants from the header should be used. + allOf: + - $ref: /schemas/types.yaml#/definitions/uint32 + - enum: [1, 2, 3, 4, 5, 6] + + cdns,num-lanes: + description: + Number of DisplayPort lanes. + allOf: + - $ref: /schemas/types.yaml#/definitions/uint32 + - enum: [1, 2, 4] + default: 4 + + cdns,max-bit-rate: + description: + Maximum DisplayPort link bit rate to use, in Mbps + allOf: + - $ref: /schemas/types.yaml#/definitions/uint32 + - enum: [2160, 2430, 2700, 3240, 4320, 5400, 8100] + default: 8100 + + required: + - reg + - resets + - "#phy-cells" + - cdns,phy-type + + additionalProperties: false + +required: + - compatible + - "#address-cells" + - "#size-cells" + - clocks + - clock-names + - reg + - reg-names + - resets + +additionalProperties: false + +examples: + - | + #include <dt-bindings/phy/phy.h> + torrent_phy: torrent-phy@f0fb500000 { + compatible = "cdns,torrent-phy"; + reg = <0xf0 0xfb500000 0x0 0x00100000>, + <0xf0 0xfb030a00 0x0 0x00000040>; + reg-names = "torrent_phy", "dptx_phy"; + resets = <&phyrst 0>; + clocks = <&ref_clk>; + clock-names = "refclk"; + #address-cells = <1>; + #size-cells = <0>; + torrent_phy_dp: phy@0 { + reg = <0>; + resets = <&phyrst 1>, <&phyrst 2>, + <&phyrst 3>, <&phyrst 4>; + #phy-cells = <0>; + cdns,phy-type = <PHY_TYPE_DP>; + cdns,num-lanes = <4>; + cdns,max-bit-rate = <8100>; + }; + }; +... diff --git a/Documentation/devicetree/bindings/phy/phy-mtk-tphy.txt b/Documentation/devicetree/bindings/phy/phy-mtk-tphy.txt index a5f7a4f0dbc1..dd75b676b71d 100644 --- a/Documentation/devicetree/bindings/phy/phy-mtk-tphy.txt +++ b/Documentation/devicetree/bindings/phy/phy-mtk-tphy.txt @@ -13,10 +13,16 @@ Required properties (controller (parent) node): "mediatek,mt8173-u3phy"; make use of "mediatek,generic-tphy-v1" on mt2701 instead and "mediatek,generic-tphy-v2" on mt2712 instead. - - clocks : (deprecated, use port's clocks instead) a list of phandle + - clock-specifier pairs, one for each entry in clock-names - - clock-names : (deprecated, use port's one instead) must contain - "u3phya_ref": for reference clock of usb3.0 analog phy. + +- #address-cells: the number of cells used to represent physical + base addresses. +- #size-cells: the number of cells used to represent the size of an address. +- ranges: the address mapping relationship to the parent, defined with + - empty value: if optional 'reg' is used. + - non-empty value: if optional 'reg' is not used. should set + the child's base address to 0, the physical address + within parent's address space, and the length of + the address map. Required nodes : a sub-node is required for each port the controller provides. Address range information including the usual @@ -34,12 +40,6 @@ Optional properties (controller (parent) node): Required properties (port (child) node): - reg : address and length of the register set for the port. -- clocks : a list of phandle + clock-specifier pairs, one for each - entry in clock-names -- clock-names : must contain - "ref": 48M reference clock for HighSpeed analog phy; and 26M - reference clock for SuperSpeed analog phy, sometimes is - 24M, 25M or 27M, depended on platform. - #phy-cells : should be 1 (See second example) cell after port phandle is phy type from: - PHY_TYPE_USB2 @@ -48,10 +48,22 @@ Required properties (port (child) node): - PHY_TYPE_SATA Optional properties (PHY_TYPE_USB2 port (child) node): +- clocks : a list of phandle + clock-specifier pairs, one for each + entry in clock-names +- clock-names : may contain + "ref": 48M reference clock for HighSpeed (digital) phy; and 26M + reference clock for SuperSpeed (digital) phy, sometimes is + 24M, 25M or 27M, depended on platform. + "da_ref": the reference clock of analog phy, used if the clocks + of analog and digital phys are separated, otherwise uses + "ref" clock only if needed. + - mediatek,eye-src : u32, the value of slew rate calibrate - mediatek,eye-vrt : u32, the selection of VRT reference voltage - mediatek,eye-term : u32, the selection of HS_TX TERM reference voltage - mediatek,bc12 : bool, enable BC12 of u2phy if support it +- mediatek,discth : u32, the selection of disconnect threshold +- mediatek,intr : u32, the selection of internal R (resistance) Example: diff --git a/Documentation/devicetree/bindings/phy/qcom,qusb2-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,qusb2-phy.yaml new file mode 100644 index 000000000000..144ae29e7141 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/qcom,qusb2-phy.yaml @@ -0,0 +1,185 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) + +%YAML 1.2 +--- +$id: "http://devicetree.org/schemas/phy/qcom,qusb2-phy.yaml#" +$schema: "http://devicetree.org/meta-schemas/core.yaml#" + +title: Qualcomm QUSB2 phy controller + +maintainers: + - Manu Gautam <mgautam@codeaurora.org> + +description: + QUSB2 controller supports LS/FS/HS usb connectivity on Qualcomm chipsets. + +properties: + compatible: + oneOf: + - items: + - enum: + - qcom,msm8996-qusb2-phy + - qcom,msm8998-qusb2-phy + - items: + - enum: + - qcom,sc7180-qusb2-phy + - qcom,sdm845-qusb2-phy + - const: qcom,qusb2-v2-phy + reg: + maxItems: 1 + + "#phy-cells": + const: 0 + + clocks: + minItems: 2 + maxItems: 3 + items: + - description: phy config clock + - description: 19.2 MHz ref clk + - description: phy interface clock (Optional) + + clock-names: + minItems: 2 + maxItems: 3 + items: + - const: cfg_ahb + - const: ref + - const: iface + + vdda-pll-supply: + description: + Phandle to 1.8V regulator supply to PHY refclk pll block. + + vdda-phy-dpdm-supply: + description: + Phandle to 3.1V regulator supply to Dp/Dm port signals. + + resets: + maxItems: 1 + description: + Phandle to reset to phy block. + + nvmem-cells: + maxItems: 1 + description: + Phandle to nvmem cell that contains 'HS Tx trim' + tuning parameter value for qusb2 phy. + + qcom,tcsr-syscon: + description: + Phandle to TCSR syscon register region. + $ref: /schemas/types.yaml#/definitions/phandle + +if: + properties: + compatible: + contains: + const: qcom,qusb2-v2-phy +then: + properties: + qcom,imp-res-offset-value: + description: + It is a 6 bit value that specifies offset to be + added to PHY refgen RESCODE via IMP_CTRL1 register. It is a PHY + tuning parameter that may vary for different boards of same SOC. + allOf: + - $ref: /schemas/types.yaml#/definitions/uint32 + - minimum: 0 + maximum: 63 + default: 0 + + qcom,bias-ctrl-value: + description: + It is a 6 bit value that specifies bias-ctrl-value. It is a PHY + tuning parameter that may vary for different boards of same SOC. + allOf: + - $ref: /schemas/types.yaml#/definitions/uint32 + - minimum: 0 + maximum: 63 + default: 0 + + qcom,charge-ctrl-value: + description: + It is a 2 bit value that specifies charge-ctrl-value. It is a PHY + tuning parameter that may vary for different boards of same SOC. + allOf: + - $ref: /schemas/types.yaml#/definitions/uint32 + - minimum: 0 + maximum: 3 + default: 0 + + qcom,hstx-trim-value: + description: + It is a 4 bit value that specifies tuning for HSTX + output current. + Possible range is - 15mA to 24mA (stepsize of 600 uA). + See dt-bindings/phy/phy-qcom-qusb2.h for applicable values. + allOf: + - $ref: /schemas/types.yaml#/definitions/uint32 + - minimum: 0 + maximum: 15 + default: 3 + + qcom,preemphasis-level: + description: + It is a 2 bit value that specifies pre-emphasis level. + Possible range is 0 to 15% (stepsize of 5%). + See dt-bindings/phy/phy-qcom-qusb2.h for applicable values. + allOf: + - $ref: /schemas/types.yaml#/definitions/uint32 + - minimum: 0 + maximum: 3 + default: 2 + + qcom,preemphasis-width: + description: + It is a 1 bit value that specifies how long the HSTX + pre-emphasis (specified using qcom,preemphasis-level) must be in + effect. Duration could be half-bit of full-bit. + See dt-bindings/phy/phy-qcom-qusb2.h for applicable values. + allOf: + - $ref: /schemas/types.yaml#/definitions/uint32 + - minimum: 0 + maximum: 1 + default: 0 + + qcom,hsdisc-trim-value: + description: + It is a 2 bit value tuning parameter that control disconnect + threshold and may vary for different boards of same SOC. + allOf: + - $ref: /schemas/types.yaml#/definitions/uint32 + - minimum: 0 + maximum: 3 + default: 0 + +required: + - compatible + - reg + - "#phy-cells" + - clocks + - clock-names + - vdda-pll-supply + - vdda-phy-dpdm-supply + - resets + + +examples: + - | + #include <dt-bindings/clock/qcom,gcc-msm8996.h> + hsusb_phy: phy@7411000 { + compatible = "qcom,msm8996-qusb2-phy"; + reg = <0x7411000 0x180>; + #phy-cells = <0>; + + clocks = <&gcc GCC_USB_PHY_CFG_AHB2PHY_CLK>, + <&gcc GCC_RX1_USB2_CLKREF_CLK>; + clock-names = "cfg_ahb", "ref"; + + vdda-pll-supply = <&pm8994_l12>; + vdda-phy-dpdm-supply = <&pm8994_l24>; + + resets = <&gcc GCC_QUSB2PHY_PRIM_BCR>; + nvmem-cells = <&qusb2p_hstx_trim>; + }; diff --git a/Documentation/devicetree/bindings/phy/qcom,usb-hs-28nm.yaml b/Documentation/devicetree/bindings/phy/qcom,usb-hs-28nm.yaml new file mode 100644 index 000000000000..ca6a0836b53c --- /dev/null +++ b/Documentation/devicetree/bindings/phy/qcom,usb-hs-28nm.yaml @@ -0,0 +1,90 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: "http://devicetree.org/schemas/phy/qcom,usb-hs-28nm.yaml#" +$schema: "http://devicetree.org/meta-schemas/core.yaml#" + +title: Qualcomm Synopsys DesignWare Core 28nm High-Speed PHY + +maintainers: + - Bryan O'Donoghue <bryan.odonoghue@linaro.org> + +description: | + Qualcomm Low-Speed, Full-Speed, Hi-Speed 28nm USB PHY + +properties: + compatible: + enum: + - qcom,usb-hs-28nm-femtophy + + reg: + maxItems: 1 + + "#phy-cells": + const: 0 + + clocks: + items: + - description: rpmcc ref clock + - description: PHY AHB clock + - description: Rentention clock + + clock-names: + items: + - const: ref + - const: ahb + - const: sleep + + resets: + items: + - description: PHY core reset + - description: POR reset + + reset-names: + items: + - const: phy + - const: por + + vdd-supply: + description: phandle to the regulator VDD supply node. + + vdda1p8-supply: + description: phandle to the regulator 1.8V supply node. + + vdda3p3-supply: + description: phandle to the regulator 3.3V supply node. + +required: + - compatible + - reg + - "#phy-cells" + - clocks + - clock-names + - resets + - reset-names + - vdd-supply + - vdda1p8-supply + - vdda3p3-supply + +additionalProperties: false + +examples: + - | + #include <dt-bindings/clock/qcom,gcc-qcs404.h> + #include <dt-bindings/clock/qcom,rpmcc.h> + usb2_phy_prim: phy@7a000 { + compatible = "qcom,usb-hs-28nm-femtophy"; + reg = <0x0007a000 0x200>; + #phy-cells = <0>; + clocks = <&rpmcc RPM_SMD_LN_BB_CLK>, + <&gcc GCC_USB_HS_PHY_CFG_AHB_CLK>, + <&gcc GCC_USB2A_PHY_SLEEP_CLK>; + clock-names = "ref", "ahb", "sleep"; + resets = <&gcc GCC_USB_HS_PHY_CFG_AHB_BCR>, + <&gcc GCC_USB2A_PHY_BCR>; + reset-names = "phy", "por"; + vdd-supply = <&vreg_l4_1p2>; + vdda1p8-supply = <&vreg_l5_1p8>; + vdda3p3-supply = <&vreg_l12_3p3>; + }; +... diff --git a/Documentation/devicetree/bindings/phy/qcom,usb-ss.yaml b/Documentation/devicetree/bindings/phy/qcom,usb-ss.yaml new file mode 100644 index 000000000000..bd1388d62ce0 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/qcom,usb-ss.yaml @@ -0,0 +1,83 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: "http://devicetree.org/schemas/phy/qcom,usb-ss.yaml#" +$schema: "http://devicetree.org/meta-schemas/core.yaml#" + +title: Qualcomm Synopsys 1.0.0 SuperSpeed USB PHY + +maintainers: + - Bryan O'Donoghue <bryan.odonoghue@linaro.org> + +description: | + Qualcomm Synopsys 1.0.0 SuperSpeed USB PHY + +properties: + compatible: + enum: + - qcom,usb-ss-28nm-phy + + reg: + maxItems: 1 + + "#phy-cells": + const: 0 + + clocks: + items: + - description: rpmcc clock + - description: PHY AHB clock + - description: SuperSpeed pipe clock + + clock-names: + items: + - const: ref + - const: ahb + - const: pipe + + vdd-supply: + description: phandle to the regulator VDD supply node. + + vdda1p8-supply: + description: phandle to the regulator 1.8V supply node. + + resets: + items: + - description: COM reset + - description: PHY reset line + + reset-names: + items: + - const: com + - const: phy + +required: + - compatible + - reg + - "#phy-cells" + - clocks + - clock-names + - vdd-supply + - vdda1p8-supply + +additionalProperties: false + +examples: + - | + #include <dt-bindings/clock/qcom,gcc-qcs404.h> + #include <dt-bindings/clock/qcom,rpmcc.h> + usb3_phy: usb3-phy@78000 { + compatible = "qcom,usb-ss-28nm-phy"; + reg = <0x78000 0x400>; + #phy-cells = <0>; + clocks = <&rpmcc RPM_SMD_LN_BB_CLK>, + <&gcc GCC_USB_HS_PHY_CFG_AHB_CLK>, + <&gcc GCC_USB3_PHY_PIPE_CLK>; + clock-names = "ref", "ahb", "pipe"; + resets = <&gcc GCC_USB3_PHY_BCR>, + <&gcc GCC_USB3PHY_PHY_BCR>; + reset-names = "com", "phy"; + vdd-supply = <&vreg_l3_1p05>; + vdda1p8-supply = <&vreg_l5_1p8>; + }; +... diff --git a/Documentation/devicetree/bindings/phy/qcom-dwc3-usb-phy.txt b/Documentation/devicetree/bindings/phy/qcom-dwc3-usb-phy.txt deleted file mode 100644 index a1697c27aecd..000000000000 --- a/Documentation/devicetree/bindings/phy/qcom-dwc3-usb-phy.txt +++ /dev/null @@ -1,37 +0,0 @@ -Qualcomm DWC3 HS AND SS PHY CONTROLLER --------------------------------------- - -DWC3 PHY nodes are defined to describe on-chip Synopsis Physical layer -controllers. Each DWC3 PHY controller should have its own node. - -Required properties: -- compatible: should contain one of the following: - - "qcom,dwc3-hs-usb-phy" for High Speed Synopsis PHY controller - - "qcom,dwc3-ss-usb-phy" for Super Speed Synopsis PHY controller -- reg: offset and length of the DWC3 PHY controller register set -- #phy-cells: must be zero -- clocks: a list of phandles and clock-specifier pairs, one for each entry in - clock-names. -- clock-names: Should contain "ref" for the PHY reference clock - -Optional clocks: - "xo" External reference clock - -Example: - phy@100f8800 { - compatible = "qcom,dwc3-hs-usb-phy"; - reg = <0x100f8800 0x30>; - clocks = <&gcc USB30_0_UTMI_CLK>; - clock-names = "ref"; - #phy-cells = <0>; - - }; - - phy@100f8830 { - compatible = "qcom,dwc3-ss-usb-phy"; - reg = <0x100f8830 0x30>; - clocks = <&gcc USB30_0_MASTER_CLK>; - clock-names = "ref"; - #phy-cells = <0>; - - }; diff --git a/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt b/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt index eac9ad3cbbc8..54d6f8d43508 100644 --- a/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt +++ b/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt @@ -8,10 +8,13 @@ Required properties: - compatible: compatible list, contains: "qcom,ipq8074-qmp-pcie-phy" for PCIe phy on IPQ8074 "qcom,msm8996-qmp-pcie-phy" for 14nm PCIe phy on msm8996, + "qcom,msm8996-qmp-ufs-phy" for 14nm UFS phy on msm8996, "qcom,msm8996-qmp-usb3-phy" for 14nm USB3 phy on msm8996, "qcom,msm8998-qmp-usb3-phy" for USB3 QMP V3 phy on msm8998, "qcom,msm8998-qmp-ufs-phy" for UFS QMP phy on msm8998, "qcom,msm8998-qmp-pcie-phy" for PCIe QMP phy on msm8998, + "qcom,sdm845-qhp-pcie-phy" for QHP PCIe phy on sdm845, + "qcom,sdm845-qmp-pcie-phy" for QMP PCIe phy on sdm845, "qcom,sdm845-qmp-usb3-phy" for USB3 QMP V3 phy on sdm845, "qcom,sdm845-qmp-usb3-uni-phy" for USB3 QMP V3 UNI phy on sdm845, "qcom,sdm845-qmp-ufs-phy" for UFS QMP phy on sdm845, @@ -44,6 +47,8 @@ Required properties: For "qcom,ipq8074-qmp-pcie-phy": no clocks are listed. For "qcom,msm8996-qmp-pcie-phy" must contain: "aux", "cfg_ahb", "ref". + For "qcom,msm8996-qmp-ufs-phy" must contain: + "ref". For "qcom,msm8996-qmp-usb3-phy" must contain: "aux", "cfg_ahb", "ref". For "qcom,msm8998-qmp-usb3-phy" must contain: @@ -52,6 +57,10 @@ Required properties: "ref", "ref_aux". For "qcom,msm8998-qmp-pcie-phy" must contain: "aux", "cfg_ahb", "ref". + For "qcom,sdm845-qhp-pcie-phy" must contain: + "aux", "cfg_ahb", "ref", "refgen". + For "qcom,sdm845-qmp-pcie-phy" must contain: + "aux", "cfg_ahb", "ref", "refgen". For "qcom,sdm845-qmp-usb3-phy" must contain: "aux", "cfg_ahb", "ref", "com_aux". For "qcom,sdm845-qmp-usb3-uni-phy" must contain: @@ -72,6 +81,8 @@ Required properties: "phy", "common". For "qcom,msm8996-qmp-pcie-phy" must contain: "phy", "common", "cfg". + For "qcom,msm8996-qmp-ufs-phy": must contain: + "ufsphy". For "qcom,msm8996-qmp-usb3-phy" must contain "phy", "common". For "qcom,msm8998-qmp-usb3-phy" must contain @@ -80,6 +91,10 @@ Required properties: "ufsphy". For "qcom,msm8998-qmp-pcie-phy" must contain: "phy", "common". + For "qcom,sdm845-qhp-pcie-phy" must contain: + "phy". + For "qcom,sdm845-qmp-pcie-phy" must contain: + "phy". For "qcom,sdm845-qmp-usb3-phy" must contain: "phy", "common". For "qcom,sdm845-qmp-usb3-uni-phy" must contain: diff --git a/Documentation/devicetree/bindings/phy/qcom-qusb2-phy.txt b/Documentation/devicetree/bindings/phy/qcom-qusb2-phy.txt deleted file mode 100644 index fe29f9e0af6d..000000000000 --- a/Documentation/devicetree/bindings/phy/qcom-qusb2-phy.txt +++ /dev/null @@ -1,68 +0,0 @@ -Qualcomm QUSB2 phy controller -============================= - -QUSB2 controller supports LS/FS/HS usb connectivity on Qualcomm chipsets. - -Required properties: - - compatible: compatible list, contains - "qcom,msm8996-qusb2-phy" for 14nm PHY on msm8996, - "qcom,msm8998-qusb2-phy" for 10nm PHY on msm8998, - "qcom,sdm845-qusb2-phy" for 10nm PHY on sdm845. - - - reg: offset and length of the PHY register set. - - #phy-cells: must be 0. - - - clocks: a list of phandles and clock-specifier pairs, - one for each entry in clock-names. - - clock-names: must be "cfg_ahb" for phy config clock, - "ref" for 19.2 MHz ref clk, - "iface" for phy interface clock (Optional). - - - vdda-pll-supply: Phandle to 1.8V regulator supply to PHY refclk pll block. - - vdda-phy-dpdm-supply: Phandle to 3.1V regulator supply to Dp/Dm port signals. - - - resets: Phandle to reset to phy block. - -Optional properties: - - nvmem-cells: Phandle to nvmem cell that contains 'HS Tx trim' - tuning parameter value for qusb2 phy. - - - qcom,tcsr-syscon: Phandle to TCSR syscon register region. - - qcom,imp-res-offset-value: It is a 6 bit value that specifies offset to be - added to PHY refgen RESCODE via IMP_CTRL1 register. It is a PHY - tuning parameter that may vary for different boards of same SOC. - This property is applicable to only QUSB2 v2 PHY (sdm845). - - qcom,hstx-trim-value: It is a 4 bit value that specifies tuning for HSTX - output current. - Possible range is - 15mA to 24mA (stepsize of 600 uA). - See dt-bindings/phy/phy-qcom-qusb2.h for applicable values. - This property is applicable to only QUSB2 v2 PHY (sdm845). - Default value is 22.2mA for sdm845. - - qcom,preemphasis-level: It is a 2 bit value that specifies pre-emphasis level. - Possible range is 0 to 15% (stepsize of 5%). - See dt-bindings/phy/phy-qcom-qusb2.h for applicable values. - This property is applicable to only QUSB2 v2 PHY (sdm845). - Default value is 10% for sdm845. -- qcom,preemphasis-width: It is a 1 bit value that specifies how long the HSTX - pre-emphasis (specified using qcom,preemphasis-level) must be in - effect. Duration could be half-bit of full-bit. - See dt-bindings/phy/phy-qcom-qusb2.h for applicable values. - This property is applicable to only QUSB2 v2 PHY (sdm845). - Default value is full-bit width for sdm845. - -Example: - hsusb_phy: phy@7411000 { - compatible = "qcom,msm8996-qusb2-phy"; - reg = <0x7411000 0x180>; - #phy-cells = <0>; - - clocks = <&gcc GCC_USB_PHY_CFG_AHB2PHY_CLK>, - <&gcc GCC_RX1_USB2_CLKREF_CLK>, - clock-names = "cfg_ahb", "ref"; - - vdda-pll-supply = <&pm8994_l12>; - vdda-phy-dpdm-supply = <&pm8994_l24>; - - resets = <&gcc GCC_QUSB2PHY_PRIM_BCR>; - nvmem-cells = <&qusb2p_hstx_trim>; - }; diff --git a/Documentation/devicetree/bindings/phy/ti-phy-gmii-sel.txt b/Documentation/devicetree/bindings/phy/ti-phy-gmii-sel.txt index 50ce9ae0f7a5..83b78c1c0644 100644 --- a/Documentation/devicetree/bindings/phy/ti-phy-gmii-sel.txt +++ b/Documentation/devicetree/bindings/phy/ti-phy-gmii-sel.txt @@ -40,6 +40,7 @@ Required properties: "ti,dra7xx-phy-gmii-sel" for dra7xx/am57xx platform "ti,am43xx-phy-gmii-sel" for am43xx platform "ti,dm814-phy-gmii-sel" for dm814x platform + "ti,am654-phy-gmii-sel" for AM654x/J721E platform - reg : Address and length of the register set for the device - #phy-cells : must be 2. cell 1 - CPSW port number (starting from 1) diff --git a/Documentation/devicetree/bindings/phy/uniphier-pcie-phy.txt b/Documentation/devicetree/bindings/phy/uniphier-pcie-phy.txt index 1889d3b89d68..3cee372c5742 100644 --- a/Documentation/devicetree/bindings/phy/uniphier-pcie-phy.txt +++ b/Documentation/devicetree/bindings/phy/uniphier-pcie-phy.txt @@ -5,14 +5,19 @@ PCIe controller implemented on Socionext UniPhier SoCs. Required properties: - compatible: Should contain one of the following: + "socionext,uniphier-pro5-pcie-phy" - for Pro5 PHY "socionext,uniphier-ld20-pcie-phy" - for LD20 PHY "socionext,uniphier-pxs3-pcie-phy" - for PXs3 PHY - reg: Specifies offset and length of the register set for the device. - #phy-cells: Must be zero. -- clocks: A phandle to the clock gate for PCIe glue layer including - this phy. -- resets: A phandle to the reset line for PCIe glue layer including - this phy. +- clocks: A list of phandles to the clock gate for PCIe glue layer + including this phy. +- clock-names: For Pro5 only, should contain the following: + "gio", "link" - for Pro5 SoC +- resets: A list of phandles to the reset line for PCIe glue layer + including this phy. +- reset-names: For Pro5 only, should contain the following: + "gio", "link" - for Pro5 SoC Optional properties: - socionext,syscon: A phandle to system control to set configurations diff --git a/Documentation/devicetree/bindings/phy/uniphier-usb3-hsphy.txt b/Documentation/devicetree/bindings/phy/uniphier-usb3-hsphy.txt index e8d8086a7ae9..093d4f08705f 100644 --- a/Documentation/devicetree/bindings/phy/uniphier-usb3-hsphy.txt +++ b/Documentation/devicetree/bindings/phy/uniphier-usb3-hsphy.txt @@ -7,7 +7,7 @@ this describes about High-Speed PHY. Required properties: - compatible: Should contain one of the following: - "socionext,uniphier-pro4-usb3-hsphy" - for Pro4 SoC + "socionext,uniphier-pro5-usb3-hsphy" - for Pro5 SoC "socionext,uniphier-pxs2-usb3-hsphy" - for PXs2 SoC "socionext,uniphier-ld20-usb3-hsphy" - for LD20 SoC "socionext,uniphier-pxs3-usb3-hsphy" - for PXs3 SoC @@ -16,13 +16,13 @@ Required properties: - clocks: A list of phandles to the clock gate for USB3 glue layer. According to the clock-names, appropriate clocks are required. - clock-names: Should contain the following: - "gio", "link" - for Pro4 SoC + "gio", "link" - for Pro5 SoC "phy", "phy-ext", "link" - for PXs3 SoC, "phy-ext" is optional. "phy", "link" - for others - resets: A list of phandles to the reset control for USB3 glue layer. According to the reset-names, appropriate resets are required. - reset-names: Should contain the following: - "gio", "link" - for Pro4 SoC + "gio", "link" - for Pro5 SoC "phy", "link" - for others Optional properties: diff --git a/Documentation/devicetree/bindings/phy/uniphier-usb3-ssphy.txt b/Documentation/devicetree/bindings/phy/uniphier-usb3-ssphy.txt index 490b815445e8..9df2bc2f5999 100644 --- a/Documentation/devicetree/bindings/phy/uniphier-usb3-ssphy.txt +++ b/Documentation/devicetree/bindings/phy/uniphier-usb3-ssphy.txt @@ -8,6 +8,7 @@ this describes about Super-Speed PHY. Required properties: - compatible: Should contain one of the following: "socionext,uniphier-pro4-usb3-ssphy" - for Pro4 SoC + "socionext,uniphier-pro5-usb3-ssphy" - for Pro5 SoC "socionext,uniphier-pxs2-usb3-ssphy" - for PXs2 SoC "socionext,uniphier-ld20-usb3-ssphy" - for LD20 SoC "socionext,uniphier-pxs3-usb3-ssphy" - for PXs3 SoC @@ -16,13 +17,13 @@ Required properties: - clocks: A list of phandles to the clock gate for USB3 glue layer. According to the clock-names, appropriate clocks are required. - clock-names: - "gio", "link" - for Pro4 SoC + "gio", "link" - for Pro4 and Pro5 SoC "phy", "phy-ext", "link" - for PXs3 SoC, "phy-ext" is optional. "phy", "link" - for others - resets: A list of phandles to the reset control for USB3 glue layer. According to the reset-names, appropriate resets are required. - reset-names: - "gio", "link" - for Pro4 SoC + "gio", "link" - for Pro4 and Pro5 SoC "phy", "link" - for others Optional properties: diff --git a/Documentation/devicetree/bindings/usb/amlogic,meson-g12a-usb-ctrl.yaml b/Documentation/devicetree/bindings/usb/amlogic,meson-g12a-usb-ctrl.yaml index 267fce165994..b0e5e0fe9386 100644 --- a/Documentation/devicetree/bindings/usb/amlogic,meson-g12a-usb-ctrl.yaml +++ b/Documentation/devicetree/bindings/usb/amlogic,meson-g12a-usb-ctrl.yaml @@ -22,10 +22,14 @@ description: | The DWC3 Glue controls the PHY routing and power, an interrupt line is connected to the Glue to serve as OTG ID change detection. + The Amlogic A1 embeds a DWC3 USB IP Core configured for USB2 in + host-only mode. + properties: compatible: enum: - amlogic,meson-g12a-usb-ctrl + - amlogic,meson-a1-usb-ctrl ranges: true @@ -84,6 +88,25 @@ required: - phys - dr_mode +allOf: + - if: + properties: + compatible: + enum: + - amlogic,meson-a1-usb-ctrl + + then: + properties: + clocks: + minItems: 3 + clock-names: + items: + - const: usb_ctrl + - const: usb_bus + - const: xtal_usb_ctrl + required: + - clock-names + examples: - | usb: usb@ffe09000 { diff --git a/Documentation/devicetree/bindings/usb/aspeed,usb-vhub.yaml b/Documentation/devicetree/bindings/usb/aspeed,usb-vhub.yaml new file mode 100644 index 000000000000..06399ba0d9e4 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/aspeed,usb-vhub.yaml @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (c) 2020 Facebook Inc. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/aspeed,usb-vhub.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ASPEED USB 2.0 Virtual Hub Controller + +maintainers: + - Benjamin Herrenschmidt <benh@kernel.crashing.org> + +description: |+ + The ASPEED USB 2.0 Virtual Hub Controller implements 1 set of USB Hub + register and several sets of Device and Endpoint registers to support + the Virtual Hub's downstream USB devices. + + Supported number of devices and endpoints vary depending on hardware + revisions. AST2400 and AST2500 Virtual Hub supports 5 downstream devices + and 15 generic endpoints, while AST2600 Virtual Hub supports 7 downstream + devices and 21 generic endpoints. + +properties: + compatible: + enum: + - aspeed,ast2400-usb-vhub + - aspeed,ast2500-usb-vhub + - aspeed,ast2600-usb-vhub + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + interrupts: + maxItems: 1 + + aspeed,vhub-downstream-ports: + description: Number of downstream ports supported by the Virtual Hub + allOf: + - $ref: /schemas/types.yaml#/definitions/uint32 + - default: 5 + minimum: 1 + maximum: 7 + + aspeed,vhub-generic-endpoints: + description: Number of generic endpoints supported by the Virtual Hub + allOf: + - $ref: /schemas/types.yaml#/definitions/uint32 + - default: 15 + minimum: 1 + maximum: 21 + +required: + - compatible + - reg + - clocks + - interrupts + - aspeed,vhub-downstream-ports + - aspeed,vhub-generic-endpoints + +additionalProperties: false + +examples: + - | + #include <dt-bindings/clock/aspeed-clock.h> + vhub: usb-vhub@1e6a0000 { + compatible = "aspeed,ast2500-usb-vhub"; + reg = <0x1e6a0000 0x300>; + interrupts = <5>; + clocks = <&syscon ASPEED_CLK_GATE_USBPORT1CLK>; + aspeed,vhub-downstream-ports = <5>; + aspeed,vhub-generic-endpoints = <15>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usb2ad_default>; + }; diff --git a/Documentation/devicetree/bindings/usb/dwc2.yaml b/Documentation/devicetree/bindings/usb/dwc2.yaml index 71cf7ba32237..6baf00e7d0a9 100644 --- a/Documentation/devicetree/bindings/usb/dwc2.yaml +++ b/Documentation/devicetree/bindings/usb/dwc2.yaml @@ -18,27 +18,15 @@ properties: - const: rockchip,rk3066-usb - const: snps,dwc2 - items: - - const: rockchip,px30-usb - - const: rockchip,rk3066-usb - - const: snps,dwc2 - - items: - - const: rockchip,rk3036-usb - - const: rockchip,rk3066-usb - - const: snps,dwc2 - - items: - - const: rockchip,rv1108-usb - - const: rockchip,rk3066-usb - - const: snps,dwc2 - - items: - - const: rockchip,rk3188-usb - - const: rockchip,rk3066-usb - - const: snps,dwc2 - - items: - - const: rockchip,rk3228-usb - - const: rockchip,rk3066-usb - - const: snps,dwc2 - - items: - - const: rockchip,rk3288-usb + - enum: + - rockchip,px30-usb + - rockchip,rk3036-usb + - rockchip,rk3188-usb + - rockchip,rk3228-usb + - rockchip,rk3288-usb + - rockchip,rk3328-usb + - rockchip,rk3368-usb + - rockchip,rv1108-usb - const: rockchip,rk3066-usb - const: snps,dwc2 - const: lantiq,arx100-usb diff --git a/Documentation/devicetree/bindings/usb/dwc3.txt b/Documentation/devicetree/bindings/usb/dwc3.txt index 66780a47ad85..9946ff9ba735 100644 --- a/Documentation/devicetree/bindings/usb/dwc3.txt +++ b/Documentation/devicetree/bindings/usb/dwc3.txt @@ -7,7 +7,8 @@ Required properties: - compatible: must be "snps,dwc3" - reg : Address and length of the register set for the device - interrupts: Interrupts used by the dwc3 controller. - - clock-names: should contain "ref", "bus_early", "suspend" + - clock-names: list of clock names. Ideally should be "ref", + "bus_early", "suspend" but may be less or more. - clocks: list of phandle and clock specifier pairs corresponding to entries in the clock-names property. @@ -36,7 +37,7 @@ Optional properties: - phys: from the *Generic PHY* bindings - phy-names: from the *Generic PHY* bindings; supported names are "usb2-phy" or "usb3-phy". - - resets: a single pair of phandle and reset specifier + - resets: set of phandle and reset specifier pairs - snps,usb2-lpm-disable: indicate if we don't want to enable USB2 HW LPM - snps,usb3_lpm_capable: determines if platform is USB3 LPM capable - snps,dis-start-transfer-quirk: when set, disable isoc START TRANSFER command @@ -75,6 +76,8 @@ Optional properties: from P0 to P1/P2/P3 without delay. - snps,dis-tx-ipgap-linecheck-quirk: when set, disable u2mac linestate check during HS transmit. + - snps,parkmode-disable-ss-quirk: when set, all SuperSpeed bus instances in + park mode are disabled. - snps,dis_metastability_quirk: when set, disable metastability workaround. CAUTION: use only if you are absolutely sure of it. - snps,is-utmi-l1-suspend: true when DWC3 asserts output signal diff --git a/Documentation/devicetree/bindings/usb/generic.txt b/Documentation/devicetree/bindings/usb/generic.txt index e6790d2a4da9..67c51759a642 100644 --- a/Documentation/devicetree/bindings/usb/generic.txt +++ b/Documentation/devicetree/bindings/usb/generic.txt @@ -35,6 +35,12 @@ Optional properties: the USB data role (USB host or USB device) for a given USB connector, such as Type-C, Type-B(micro). see connector/usb-connector.txt. + - role-switch-default-mode: indicating if usb-role-switch is enabled, the + device default operation mode of controller while usb + role is USB_ROLE_NONE. Valid arguments are "host" and + "peripheral". Defaults to "peripheral" if not + specified. + This is an attribute to a USB controller such as: diff --git a/Documentation/devicetree/bindings/usb/ingenic,jz4740-musb.txt b/Documentation/devicetree/bindings/usb/ingenic,jz4740-musb.txt deleted file mode 100644 index 16808721f3ff..000000000000 --- a/Documentation/devicetree/bindings/usb/ingenic,jz4740-musb.txt +++ /dev/null @@ -1,32 +0,0 @@ -Ingenic JZ4740 MUSB driver - -Required properties: - -- compatible: Must be "ingenic,jz4740-musb" -- reg: Address range of the UDC register set -- interrupts: IRQ number related to the UDC hardware -- interrupt-names: must be "mc" -- clocks: phandle to the "udc" clock -- clock-names: must be "udc" -- phys: phandle to the USB PHY - -Example: - -usb_phy: usb-phy@0 { - compatible = "usb-nop-xceiv"; - #phy-cells = <0>; -}; - -udc: usb@13040000 { - compatible = "ingenic,jz4740-musb"; - reg = <0x13040000 0x10000>; - - interrupt-parent = <&intc>; - interrupts = <24>; - interrupt-names = "mc"; - - clocks = <&cgu JZ4740_CLK_UDC>; - clock-names = "udc"; - - phys = <&usb_phy>; -}; diff --git a/Documentation/devicetree/bindings/usb/ingenic,jz4770-phy.yaml b/Documentation/devicetree/bindings/usb/ingenic,jz4770-phy.yaml new file mode 100644 index 000000000000..a81b0b1a2226 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/ingenic,jz4770-phy.yaml @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/ingenic,jz4770-phy.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Ingenic JZ4770 USB PHY devicetree bindings + +maintainers: + - Paul Cercueil <paul@crapouillou.net> + +properties: + $nodename: + pattern: '^usb-phy@.*' + + compatible: + enum: + - ingenic,jz4770-phy + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + vcc-supply: + description: VCC power supply + + '#phy-cells': + const: 0 + +required: + - compatible + - reg + - clocks + - vcc-supply + - '#phy-cells' + +additionalProperties: false + +examples: + - | + #include <dt-bindings/clock/jz4770-cgu.h> + otg_phy: usb-phy@3c { + compatible = "ingenic,jz4770-phy"; + reg = <0x3c 0x10>; + + vcc-supply = <&vcc>; + clocks = <&cgu JZ4770_CLK_OTG_PHY>; + + #phy-cells = <0>; + }; diff --git a/Documentation/devicetree/bindings/usb/ingenic,musb.yaml b/Documentation/devicetree/bindings/usb/ingenic,musb.yaml new file mode 100644 index 000000000000..1d6877875077 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/ingenic,musb.yaml @@ -0,0 +1,76 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/ingenic,musb.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Ingenic JZ47xx USB IP DT bindings + +maintainers: + - Paul Cercueil <paul@crapouillou.net> + +properties: + $nodename: + pattern: '^usb@.*' + + compatible: + oneOf: + - enum: + - ingenic,jz4770-musb + - ingenic,jz4740-musb + - items: + - const: ingenic,jz4725b-musb + - const: ingenic,jz4740-musb + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + items: + - const: udc + + interrupts: + maxItems: 1 + + interrupt-names: + items: + - const: mc + + phys: + description: PHY specifier for the USB PHY + +required: + - compatible + - reg + - clocks + - clock-names + - interrupts + - interrupt-names + - phys + +additionalProperties: false + +examples: + - | + #include <dt-bindings/clock/jz4740-cgu.h> + usb_phy: usb-phy@0 { + compatible = "usb-nop-xceiv"; + #phy-cells = <0>; + }; + + udc: usb@13040000 { + compatible = "ingenic,jz4740-musb"; + reg = <0x13040000 0x10000>; + + interrupt-parent = <&intc>; + interrupts = <24>; + interrupt-names = "mc"; + + clocks = <&cgu JZ4740_CLK_UDC>; + clock-names = "udc"; + + phys = <&usb_phy>; + }; diff --git a/Documentation/devicetree/bindings/usb/maxim,max3420-udc.yaml b/Documentation/devicetree/bindings/usb/maxim,max3420-udc.yaml new file mode 100644 index 000000000000..4241d38d5864 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/maxim,max3420-udc.yaml @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/maxim,max3420-udc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MAXIM MAX3420/1 USB Peripheral Controller + +maintainers: + - Jassi Brar <jaswinder.singh@linaro.org> + +description: | + The controller provices USB2.0 compliant FullSpeed peripheral + implementation over the SPI interface. + + Specifications about the part can be found at: + http://datasheets.maximintegrated.com/en/ds/MAX3420E.pdf + +properties: + compatible: + enum: + - maxim,max3420-udc + - maxim,max3421-udc + + reg: + maxItems: 1 + + interrupts: + items: + - description: usb irq from max3420 + - description: vbus detection irq + minItems: 1 + maxItems: 2 + + interrupt-names: + items: + - const: udc + - const: vbus + minItems: 1 + maxItems: 2 + + spi-max-frequency: + maximum: 26000000 + +required: + - compatible + - reg + - interrupts + - interrupt-names + +additionalProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + #include <dt-bindings/interrupt-controller/irq.h> + spi0 { + #address-cells = <1>; + #size-cells = <0>; + + udc@0 { + compatible = "maxim,max3420-udc"; + reg = <0>; + interrupt-parent = <&gpio>; + interrupts = <0 IRQ_TYPE_EDGE_FALLING>, <10 IRQ_TYPE_EDGE_BOTH>; + interrupt-names = "udc", "vbus"; + spi-max-frequency = <12500000>; + }; + }; diff --git a/Documentation/driver-api/usb/typec_bus.rst b/Documentation/driver-api/usb/typec_bus.rst index f47a69bff498..03dfa9c018b7 100644 --- a/Documentation/driver-api/usb/typec_bus.rst +++ b/Documentation/driver-api/usb/typec_bus.rst @@ -53,9 +53,7 @@ in need to reconfigure the pins on the connector, the alternate mode driver needs to notify the bus using :c:func:`typec_altmode_notify()`. The driver passes the negotiated SVID specific pin configuration value to the function as parameter. The bus driver will then configure the mux behind the connector using -that value as the state value for the mux, and also call blocking notification -chain to notify the external drivers about the state of the connector that need -to know it. +that value as the state value for the mux. NOTE: The SVID specific pin configuration values must always start from ``TYPEC_STATE_MODAL``. USB Type-C specification defines two default states for @@ -80,19 +78,6 @@ Helper macro ``TYPEC_MODAL_STATE()`` can also be used:: #define ALTMODEX_CONF_A = TYPEC_MODAL_STATE(0); #define ALTMODEX_CONF_B = TYPEC_MODAL_STATE(1); -Notification chain -~~~~~~~~~~~~~~~~~~ - -The drivers for the components that the alternate modes are designed for need to -get details regarding the results of the negotiation with the partner, and the -pin configuration of the connector. In case of DisplayPort alternate mode for -example, the GPU drivers will need to know those details. In case of -Thunderbolt alternate mode, the thunderbolt drivers will need to know them, and -so on. - -The notification chain is designed for this purpose. The drivers can register -notifiers with :c:func:`typec_altmode_register_notifier()`. - Cable plug alternate modes ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -129,8 +114,3 @@ Cable Plug operations .. kernel-doc:: drivers/usb/typec/bus.c :functions: typec_altmode_get_plug typec_altmode_put_plug - -Notifications -~~~~~~~~~~~~~ -.. kernel-doc:: drivers/usb/typec/class.c - :functions: typec_altmode_register_notifier typec_altmode_unregister_notifier diff --git a/Documentation/usb/index.rst b/Documentation/usb/index.rst index 36b6ebd9a9d9..b656c9be23ed 100644 --- a/Documentation/usb/index.rst +++ b/Documentation/usb/index.rst @@ -22,6 +22,7 @@ USB support misc_usbsevseg mtouchusb ohci + raw-gadget usbip_protocol usbmon usb-serial diff --git a/Documentation/usb/raw-gadget.rst b/Documentation/usb/raw-gadget.rst new file mode 100644 index 000000000000..9e78cb858f86 --- /dev/null +++ b/Documentation/usb/raw-gadget.rst @@ -0,0 +1,61 @@ +============== +USB Raw Gadget +============== + +USB Raw Gadget is a kernel module that provides a userspace interface for +the USB Gadget subsystem. Essentially it allows to emulate USB devices +from userspace. Enabled with CONFIG_USB_RAW_GADGET. Raw Gadget is +currently a strictly debugging feature and shouldn't be used in +production, use GadgetFS instead. + +Comparison to GadgetFS +~~~~~~~~~~~~~~~~~~~~~~ + +Raw Gadget is similar to GadgetFS, but provides a more low-level and +direct access to the USB Gadget layer for the userspace. The key +differences are: + +1. Every USB request is passed to the userspace to get a response, while + GadgetFS responds to some USB requests internally based on the provided + descriptors. However note, that the UDC driver might respond to some + requests on its own and never forward them to the Gadget layer. + +2. GadgetFS performs some sanity checks on the provided USB descriptors, + while Raw Gadget allows you to provide arbitrary data as responses to + USB requests. + +3. Raw Gadget provides a way to select a UDC device/driver to bind to, + while GadgetFS currently binds to the first available UDC. + +4. Raw Gadget uses predictable endpoint names (handles) across different + UDCs (as long as UDCs have enough endpoints of each required transfer + type). + +5. Raw Gadget has ioctl-based interface instead of a filesystem-based one. + +Userspace interface +~~~~~~~~~~~~~~~~~~~ + +To create a Raw Gadget instance open /dev/raw-gadget. Multiple raw-gadget +instances (bound to different UDCs) can be used at the same time. The +interaction with the opened file happens through the ioctl() calls, see +comments in include/uapi/linux/usb/raw_gadget.h for details. + +The typical usage of Raw Gadget looks like: + +1. Open Raw Gadget instance via /dev/raw-gadget. +2. Initialize the instance via USB_RAW_IOCTL_INIT. +3. Launch the instance with USB_RAW_IOCTL_RUN. +4. In a loop issue USB_RAW_IOCTL_EVENT_FETCH calls to receive events from + Raw Gadget and react to those depending on what kind of USB device + needs to be emulated. + +Potential future improvements +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Implement ioctl's for setting/clearing halt status on endpoints. + +- Reporting more events (suspend, resume, etc.) through + USB_RAW_IOCTL_EVENT_FETCH. + +- Support O_NONBLOCK I/O. diff --git a/MAINTAINERS b/MAINTAINERS index 1b37feaf18e6..91c7db807208 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17218,6 +17218,12 @@ S: Maintained F: Documentation/usb/acm.rst F: drivers/usb/class/cdc-acm.* +USB APPLE MFI FASTCHARGE DRIVER +M: Bastien Nocera <hadess@hadess.net> +L: linux-usb@vger.kernel.org +S: Maintained +F: drivers/usb/misc/apple-mfi-fastcharge.c + USB AR5523 WIRELESS DRIVER M: Pontus Fuchs <pontus.fuchs@gmail.com> L: linux-wireless@vger.kernel.org diff --git a/arch/arm/boot/dts/aspeed-g4.dtsi b/arch/arm/boot/dts/aspeed-g4.dtsi index 807a0fc20670..8e04303e8514 100644 --- a/arch/arm/boot/dts/aspeed-g4.dtsi +++ b/arch/arm/boot/dts/aspeed-g4.dtsi @@ -164,6 +164,8 @@ reg = <0x1e6a0000 0x300>; interrupts = <5>; clocks = <&syscon ASPEED_CLK_GATE_USBPORT1CLK>; + aspeed,vhub-downstream-ports = <5>; + aspeed,vhub-generic-endpoints = <15>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_usb2d_default>; status = "disabled"; diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi index ebec0fa8baa7..f12ec04d3cbc 100644 --- a/arch/arm/boot/dts/aspeed-g5.dtsi +++ b/arch/arm/boot/dts/aspeed-g5.dtsi @@ -195,6 +195,8 @@ reg = <0x1e6a0000 0x300>; interrupts = <5>; clocks = <&syscon ASPEED_CLK_GATE_USBPORT1CLK>; + aspeed,vhub-downstream-ports = <5>; + aspeed,vhub-generic-endpoints = <15>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_usb2ad_default>; status = "disabled"; diff --git a/arch/arm/boot/dts/aspeed-g6-pinctrl.dtsi b/arch/arm/boot/dts/aspeed-g6-pinctrl.dtsi index 045ce66ca876..7028e21bdd98 100644 --- a/arch/arm/boot/dts/aspeed-g6-pinctrl.dtsi +++ b/arch/arm/boot/dts/aspeed-g6-pinctrl.dtsi @@ -1112,6 +1112,31 @@ groups = "UART9"; }; + pinctrl_usb2ah_default: usb2ah_default { + function = "USB2AH"; + groups = "USBA"; + }; + + pinctrl_usb2ad_default: usb2ad_default { + function = "USB2AD"; + groups = "USBA"; + }; + + pinctrl_usb2bh_default: usb2bh_default { + function = "USB2BH"; + groups = "USBB"; + }; + + pinctrl_usb2bd_default: usb2bd_default { + function = "USB2BD"; + groups = "USBB"; + }; + + pinctrl_usb11bhid_default: usb11bhid_default { + function = "USB11BHID"; + groups = "USBB"; + }; + pinctrl_vb_default: vb_default { function = "VB"; groups = "VB"; diff --git a/arch/arm/boot/dts/aspeed-g6.dtsi b/arch/arm/boot/dts/aspeed-g6.dtsi index 796976d275e1..0a29b3b57a9d 100644 --- a/arch/arm/boot/dts/aspeed-g6.dtsi +++ b/arch/arm/boot/dts/aspeed-g6.dtsi @@ -245,6 +245,51 @@ status = "disabled"; }; + ehci0: usb@1e6a1000 { + compatible = "aspeed,ast2600-ehci", "generic-ehci"; + reg = <0x1e6a1000 0x100>; + interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&syscon ASPEED_CLK_GATE_USBPORT1CLK>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usb2ah_default>; + status = "disabled"; + }; + + ehci1: usb@1e6a3000 { + compatible = "aspeed,ast2600-ehci", "generic-ehci"; + reg = <0x1e6a3000 0x100>; + interrupts = <GIC_SPI 9 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&syscon ASPEED_CLK_GATE_USBPORT2CLK>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usb2bh_default>; + status = "disabled"; + }; + + uhci: usb@1e6b0000 { + compatible = "aspeed,ast2600-uhci", "generic-uhci"; + reg = <0x1e6b0000 0x100>; + interrupts = <GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH>; + #ports = <2>; + clocks = <&syscon ASPEED_CLK_GATE_USBUHCICLK>; + status = "disabled"; + /* + * No default pinmux, it will follow EHCI, use an + * explicit pinmux override if EHCI is not enabled. + */ + }; + + vhub: usb-vhub@1e6a0000 { + compatible = "aspeed,ast2600-usb-vhub"; + reg = <0x1e6a0000 0x350>; + interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&syscon ASPEED_CLK_GATE_USBPORT1CLK>; + aspeed,vhub-downstream-ports = <7>; + aspeed,vhub-generic-endpoints = <21>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usb2ad_default>; + status = "disabled"; + }; + apb { compatible = "simple-bus"; #address-cells = <1>; diff --git a/drivers/base/platform.c b/drivers/base/platform.c index b5ce7b085795..5255550b7c34 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -63,6 +63,28 @@ EXPORT_SYMBOL_GPL(platform_get_resource); #ifdef CONFIG_HAS_IOMEM /** + * devm_platform_get_and_ioremap_resource - call devm_ioremap_resource() for a + * platform device and get resource + * + * @pdev: platform device to use both for memory resource lookup as well as + * resource management + * @index: resource index + * @res: optional output parameter to store a pointer to the obtained resource. + */ +void __iomem * +devm_platform_get_and_ioremap_resource(struct platform_device *pdev, + unsigned int index, struct resource **res) +{ + struct resource *r; + + r = platform_get_resource(pdev, IORESOURCE_MEM, index); + if (res) + *res = r; + return devm_ioremap_resource(&pdev->dev, r); +} +EXPORT_SYMBOL_GPL(devm_platform_get_and_ioremap_resource); + +/** * devm_platform_ioremap_resource - call devm_ioremap_resource() for a platform * device * @@ -73,10 +95,7 @@ EXPORT_SYMBOL_GPL(platform_get_resource); void __iomem *devm_platform_ioremap_resource(struct platform_device *pdev, unsigned int index) { - struct resource *res; - - res = platform_get_resource(pdev, IORESOURCE_MEM, index); - return devm_ioremap_resource(&pdev->dev, res); + return devm_platform_get_and_ioremap_resource(pdev, index, NULL); } EXPORT_SYMBOL_GPL(devm_platform_ioremap_resource); diff --git a/drivers/phy/amlogic/phy-meson-g12a-usb2.c b/drivers/phy/amlogic/phy-meson-g12a-usb2.c index 9065ffc85eb4..b26e30e1afaf 100644 --- a/drivers/phy/amlogic/phy-meson-g12a-usb2.c +++ b/drivers/phy/amlogic/phy-meson-g12a-usb2.c @@ -66,7 +66,7 @@ #define PHY_CTRL_R14 0x38 #define PHY_CTRL_R14_I_RDP_EN BIT(0) #define PHY_CTRL_R14_I_RPU_SW1_EN BIT(1) - #define PHY_CTRL_R14_I_RPU_SW2_EN GENMASK(2, 3) + #define PHY_CTRL_R14_I_RPU_SW2_EN GENMASK(3, 2) #define PHY_CTRL_R14_PG_RSTN BIT(4) #define PHY_CTRL_R14_I_C2L_DATA_16_8 BIT(5) #define PHY_CTRL_R14_I_C2L_ASSERT_SINGLE_EN_ZERO BIT(6) @@ -146,11 +146,17 @@ #define RESET_COMPLETE_TIME 1000 #define PLL_RESET_COMPLETE_TIME 100 +enum meson_soc_id { + MESON_SOC_G12A = 0, + MESON_SOC_A1, +}; + struct phy_meson_g12a_usb2_priv { struct device *dev; struct regmap *regmap; struct clk *clk; struct reset_control *reset; + int soc_id; }; static const struct regmap_config phy_meson_g12a_usb2_regmap_conf = { @@ -164,6 +170,7 @@ static int phy_meson_g12a_usb2_init(struct phy *phy) { struct phy_meson_g12a_usb2_priv *priv = phy_get_drvdata(phy); int ret; + unsigned int value; ret = reset_control_reset(priv->reset); if (ret) @@ -192,18 +199,22 @@ static int phy_meson_g12a_usb2_init(struct phy *phy) FIELD_PREP(PHY_CTRL_R17_MPLL_FILTER_PVT2, 2) | FIELD_PREP(PHY_CTRL_R17_MPLL_FILTER_PVT1, 9)); - regmap_write(priv->regmap, PHY_CTRL_R18, - FIELD_PREP(PHY_CTRL_R18_MPLL_LKW_SEL, 1) | - FIELD_PREP(PHY_CTRL_R18_MPLL_LK_W, 9) | - FIELD_PREP(PHY_CTRL_R18_MPLL_LK_S, 0x27) | - FIELD_PREP(PHY_CTRL_R18_MPLL_PFD_GAIN, 1) | - FIELD_PREP(PHY_CTRL_R18_MPLL_ROU, 7) | - FIELD_PREP(PHY_CTRL_R18_MPLL_DATA_SEL, 3) | - FIELD_PREP(PHY_CTRL_R18_MPLL_BIAS_ADJ, 1) | - FIELD_PREP(PHY_CTRL_R18_MPLL_BB_MODE, 0) | - FIELD_PREP(PHY_CTRL_R18_MPLL_ALPHA, 3) | - FIELD_PREP(PHY_CTRL_R18_MPLL_ADJ_LDO, 1) | - PHY_CTRL_R18_MPLL_ACG_RANGE); + value = FIELD_PREP(PHY_CTRL_R18_MPLL_LKW_SEL, 1) | + FIELD_PREP(PHY_CTRL_R18_MPLL_LK_W, 9) | + FIELD_PREP(PHY_CTRL_R18_MPLL_LK_S, 0x27) | + FIELD_PREP(PHY_CTRL_R18_MPLL_PFD_GAIN, 1) | + FIELD_PREP(PHY_CTRL_R18_MPLL_ROU, 7) | + FIELD_PREP(PHY_CTRL_R18_MPLL_DATA_SEL, 3) | + FIELD_PREP(PHY_CTRL_R18_MPLL_BIAS_ADJ, 1) | + FIELD_PREP(PHY_CTRL_R18_MPLL_BB_MODE, 0) | + FIELD_PREP(PHY_CTRL_R18_MPLL_ALPHA, 3) | + FIELD_PREP(PHY_CTRL_R18_MPLL_ADJ_LDO, 1) | + PHY_CTRL_R18_MPLL_ACG_RANGE; + + if (priv->soc_id == MESON_SOC_A1) + value |= PHY_CTRL_R18_MPLL_DCO_CLK_SEL; + + regmap_write(priv->regmap, PHY_CTRL_R18, value); udelay(PLL_RESET_COMPLETE_TIME); @@ -227,13 +238,24 @@ static int phy_meson_g12a_usb2_init(struct phy *phy) FIELD_PREP(PHY_CTRL_R20_USB2_BGR_VREF_4_0, 0) | FIELD_PREP(PHY_CTRL_R20_USB2_BGR_DBG_1_0, 0)); - regmap_write(priv->regmap, PHY_CTRL_R4, - FIELD_PREP(PHY_CTRL_R4_CALIB_CODE_7_0, 0xf) | - FIELD_PREP(PHY_CTRL_R4_CALIB_CODE_15_8, 0xf) | - FIELD_PREP(PHY_CTRL_R4_CALIB_CODE_23_16, 0xf) | - PHY_CTRL_R4_TEST_BYPASS_MODE_EN | - FIELD_PREP(PHY_CTRL_R4_I_C2L_BIAS_TRIM_1_0, 0) | - FIELD_PREP(PHY_CTRL_R4_I_C2L_BIAS_TRIM_3_2, 0)); + if (priv->soc_id == MESON_SOC_G12A) + regmap_write(priv->regmap, PHY_CTRL_R4, + FIELD_PREP(PHY_CTRL_R4_CALIB_CODE_7_0, 0xf) | + FIELD_PREP(PHY_CTRL_R4_CALIB_CODE_15_8, 0xf) | + FIELD_PREP(PHY_CTRL_R4_CALIB_CODE_23_16, 0xf) | + PHY_CTRL_R4_TEST_BYPASS_MODE_EN | + FIELD_PREP(PHY_CTRL_R4_I_C2L_BIAS_TRIM_1_0, 0) | + FIELD_PREP(PHY_CTRL_R4_I_C2L_BIAS_TRIM_3_2, 0)); + else if (priv->soc_id == MESON_SOC_A1) { + regmap_write(priv->regmap, PHY_CTRL_R21, + PHY_CTRL_R21_USB2_CAL_ACK_EN | + PHY_CTRL_R21_USB2_TX_STRG_PD | + FIELD_PREP(PHY_CTRL_R21_USB2_OTG_ACA_TRIM_1_0, 2)); + + /* Analog Settings */ + regmap_write(priv->regmap, PHY_CTRL_R13, + FIELD_PREP(PHY_CTRL_R13_MIN_COUNT_FOR_SYNC_DET, 7)); + } /* Tuning Disconnect Threshold */ regmap_write(priv->regmap, PHY_CTRL_R3, @@ -241,11 +263,13 @@ static int phy_meson_g12a_usb2_init(struct phy *phy) FIELD_PREP(PHY_CTRL_R3_HSDIC_REF, 1) | FIELD_PREP(PHY_CTRL_R3_DISC_THRESH, 3)); - /* Analog Settings */ - regmap_write(priv->regmap, PHY_CTRL_R14, 0); - regmap_write(priv->regmap, PHY_CTRL_R13, - PHY_CTRL_R13_UPDATE_PMA_SIGNALS | - FIELD_PREP(PHY_CTRL_R13_MIN_COUNT_FOR_SYNC_DET, 7)); + if (priv->soc_id == MESON_SOC_G12A) { + /* Analog Settings */ + regmap_write(priv->regmap, PHY_CTRL_R14, 0); + regmap_write(priv->regmap, PHY_CTRL_R13, + PHY_CTRL_R13_UPDATE_PMA_SIGNALS | + FIELD_PREP(PHY_CTRL_R13_MIN_COUNT_FOR_SYNC_DET, 7)); + } return 0; } @@ -286,6 +310,8 @@ static int phy_meson_g12a_usb2_probe(struct platform_device *pdev) if (IS_ERR(base)) return PTR_ERR(base); + priv->soc_id = (enum meson_soc_id)of_device_get_match_data(&pdev->dev); + priv->regmap = devm_regmap_init_mmio(dev, base, &phy_meson_g12a_usb2_regmap_conf); if (IS_ERR(priv->regmap)) @@ -321,8 +347,15 @@ static int phy_meson_g12a_usb2_probe(struct platform_device *pdev) } static const struct of_device_id phy_meson_g12a_usb2_of_match[] = { - { .compatible = "amlogic,g12a-usb2-phy", }, - { }, + { + .compatible = "amlogic,g12a-usb2-phy", + .data = (void *)MESON_SOC_G12A, + }, + { + .compatible = "amlogic,a1-usb2-phy", + .data = (void *)MESON_SOC_A1, + }, + { /* Sentinel */ } }; MODULE_DEVICE_TABLE(of, phy_meson_g12a_usb2_of_match); diff --git a/drivers/phy/cadence/Kconfig b/drivers/phy/cadence/Kconfig index b2db916db64b..459545871608 100644 --- a/drivers/phy/cadence/Kconfig +++ b/drivers/phy/cadence/Kconfig @@ -3,13 +3,13 @@ # Phy drivers for Cadence PHYs # -config PHY_CADENCE_DP - tristate "Cadence MHDP DisplayPort PHY driver" +config PHY_CADENCE_TORRENT + tristate "Cadence Torrent PHY driver" depends on OF depends on HAS_IOMEM select GENERIC_PHY help - Support for Cadence MHDP DisplayPort PHY. + Support for Cadence Torrent PHY. config PHY_CADENCE_DPHY tristate "Cadence D-PHY Support" diff --git a/drivers/phy/cadence/Makefile b/drivers/phy/cadence/Makefile index 8f89560f1711..6a7ffc6ea599 100644 --- a/drivers/phy/cadence/Makefile +++ b/drivers/phy/cadence/Makefile @@ -1,4 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only -obj-$(CONFIG_PHY_CADENCE_DP) += phy-cadence-dp.o +obj-$(CONFIG_PHY_CADENCE_TORRENT) += phy-cadence-torrent.o obj-$(CONFIG_PHY_CADENCE_DPHY) += cdns-dphy.o obj-$(CONFIG_PHY_CADENCE_SIERRA) += phy-cadence-sierra.o diff --git a/drivers/phy/cadence/phy-cadence-dp.c b/drivers/phy/cadence/phy-cadence-dp.c deleted file mode 100644 index bc10cb264b7a..000000000000 --- a/drivers/phy/cadence/phy-cadence-dp.c +++ /dev/null @@ -1,541 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Cadence MHDP DisplayPort SD0801 PHY driver. - * - * Copyright 2018 Cadence Design Systems, Inc. - * - */ - -#include <linux/delay.h> -#include <linux/err.h> -#include <linux/io.h> -#include <linux/iopoll.h> -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/of.h> -#include <linux/of_address.h> -#include <linux/of_device.h> -#include <linux/phy/phy.h> -#include <linux/platform_device.h> - -#define DEFAULT_NUM_LANES 2 -#define MAX_NUM_LANES 4 -#define DEFAULT_MAX_BIT_RATE 8100 /* in Mbps */ - -#define POLL_TIMEOUT_US 2000 -#define LANE_MASK 0x7 - -/* - * register offsets from DPTX PHY register block base (i.e MHDP - * register base + 0x30a00) - */ -#define PHY_AUX_CONFIG 0x00 -#define PHY_AUX_CTRL 0x04 -#define PHY_RESET 0x20 -#define PHY_PMA_XCVR_PLLCLK_EN 0x24 -#define PHY_PMA_XCVR_PLLCLK_EN_ACK 0x28 -#define PHY_PMA_XCVR_POWER_STATE_REQ 0x2c -#define PHY_POWER_STATE_LN_0 0x0000 -#define PHY_POWER_STATE_LN_1 0x0008 -#define PHY_POWER_STATE_LN_2 0x0010 -#define PHY_POWER_STATE_LN_3 0x0018 -#define PHY_PMA_XCVR_POWER_STATE_ACK 0x30 -#define PHY_PMA_CMN_READY 0x34 -#define PHY_PMA_XCVR_TX_VMARGIN 0x38 -#define PHY_PMA_XCVR_TX_DEEMPH 0x3c - -/* - * register offsets from SD0801 PHY register block base (i.e MHDP - * register base + 0x500000) - */ -#define CMN_SSM_BANDGAP_TMR 0x00084 -#define CMN_SSM_BIAS_TMR 0x00088 -#define CMN_PLLSM0_PLLPRE_TMR 0x000a8 -#define CMN_PLLSM0_PLLLOCK_TMR 0x000b0 -#define CMN_PLLSM1_PLLPRE_TMR 0x000c8 -#define CMN_PLLSM1_PLLLOCK_TMR 0x000d0 -#define CMN_BGCAL_INIT_TMR 0x00190 -#define CMN_BGCAL_ITER_TMR 0x00194 -#define CMN_IBCAL_INIT_TMR 0x001d0 -#define CMN_PLL0_VCOCAL_INIT_TMR 0x00210 -#define CMN_PLL0_VCOCAL_ITER_TMR 0x00214 -#define CMN_PLL0_VCOCAL_REFTIM_START 0x00218 -#define CMN_PLL0_VCOCAL_PLLCNT_START 0x00220 -#define CMN_PLL0_INTDIV_M0 0x00240 -#define CMN_PLL0_FRACDIVL_M0 0x00244 -#define CMN_PLL0_FRACDIVH_M0 0x00248 -#define CMN_PLL0_HIGH_THR_M0 0x0024c -#define CMN_PLL0_DSM_DIAG_M0 0x00250 -#define CMN_PLL0_LOCK_PLLCNT_START 0x00278 -#define CMN_PLL1_VCOCAL_INIT_TMR 0x00310 -#define CMN_PLL1_VCOCAL_ITER_TMR 0x00314 -#define CMN_PLL1_DSM_DIAG_M0 0x00350 -#define CMN_TXPUCAL_INIT_TMR 0x00410 -#define CMN_TXPUCAL_ITER_TMR 0x00414 -#define CMN_TXPDCAL_INIT_TMR 0x00430 -#define CMN_TXPDCAL_ITER_TMR 0x00434 -#define CMN_RXCAL_INIT_TMR 0x00450 -#define CMN_RXCAL_ITER_TMR 0x00454 -#define CMN_SD_CAL_INIT_TMR 0x00490 -#define CMN_SD_CAL_ITER_TMR 0x00494 -#define CMN_SD_CAL_REFTIM_START 0x00498 -#define CMN_SD_CAL_PLLCNT_START 0x004a0 -#define CMN_PDIAG_PLL0_CTRL_M0 0x00680 -#define CMN_PDIAG_PLL0_CLK_SEL_M0 0x00684 -#define CMN_PDIAG_PLL0_CP_PADJ_M0 0x00690 -#define CMN_PDIAG_PLL0_CP_IADJ_M0 0x00694 -#define CMN_PDIAG_PLL0_FILT_PADJ_M0 0x00698 -#define CMN_PDIAG_PLL0_CP_PADJ_M1 0x006d0 -#define CMN_PDIAG_PLL0_CP_IADJ_M1 0x006d4 -#define CMN_PDIAG_PLL1_CLK_SEL_M0 0x00704 -#define XCVR_DIAG_PLLDRC_CTRL 0x10394 -#define XCVR_DIAG_HSCLK_SEL 0x10398 -#define XCVR_DIAG_HSCLK_DIV 0x1039c -#define TX_PSC_A0 0x10400 -#define TX_PSC_A1 0x10404 -#define TX_PSC_A2 0x10408 -#define TX_PSC_A3 0x1040c -#define RX_PSC_A0 0x20000 -#define RX_PSC_A1 0x20004 -#define RX_PSC_A2 0x20008 -#define RX_PSC_A3 0x2000c -#define PHY_PLL_CFG 0x30038 - -struct cdns_dp_phy { - void __iomem *base; /* DPTX registers base */ - void __iomem *sd_base; /* SD0801 registers base */ - u32 num_lanes; /* Number of lanes to use */ - u32 max_bit_rate; /* Maximum link bit rate to use (in Mbps) */ - struct device *dev; -}; - -static int cdns_dp_phy_init(struct phy *phy); -static void cdns_dp_phy_run(struct cdns_dp_phy *cdns_phy); -static void cdns_dp_phy_wait_pma_cmn_ready(struct cdns_dp_phy *cdns_phy); -static void cdns_dp_phy_pma_cfg(struct cdns_dp_phy *cdns_phy); -static void cdns_dp_phy_pma_cmn_cfg_25mhz(struct cdns_dp_phy *cdns_phy); -static void cdns_dp_phy_pma_lane_cfg(struct cdns_dp_phy *cdns_phy, - unsigned int lane); -static void cdns_dp_phy_pma_cmn_vco_cfg_25mhz(struct cdns_dp_phy *cdns_phy); -static void cdns_dp_phy_pma_cmn_rate(struct cdns_dp_phy *cdns_phy); -static void cdns_dp_phy_write_field(struct cdns_dp_phy *cdns_phy, - unsigned int offset, - unsigned char start_bit, - unsigned char num_bits, - unsigned int val); - -static const struct phy_ops cdns_dp_phy_ops = { - .init = cdns_dp_phy_init, - .owner = THIS_MODULE, -}; - -static int cdns_dp_phy_init(struct phy *phy) -{ - unsigned char lane_bits; - - struct cdns_dp_phy *cdns_phy = phy_get_drvdata(phy); - - writel(0x0003, cdns_phy->base + PHY_AUX_CTRL); /* enable AUX */ - - /* PHY PMA registers configuration function */ - cdns_dp_phy_pma_cfg(cdns_phy); - - /* - * Set lines power state to A0 - * Set lines pll clk enable to 0 - */ - - cdns_dp_phy_write_field(cdns_phy, PHY_PMA_XCVR_POWER_STATE_REQ, - PHY_POWER_STATE_LN_0, 6, 0x0000); - - if (cdns_phy->num_lanes >= 2) { - cdns_dp_phy_write_field(cdns_phy, - PHY_PMA_XCVR_POWER_STATE_REQ, - PHY_POWER_STATE_LN_1, 6, 0x0000); - - if (cdns_phy->num_lanes == 4) { - cdns_dp_phy_write_field(cdns_phy, - PHY_PMA_XCVR_POWER_STATE_REQ, - PHY_POWER_STATE_LN_2, 6, 0); - cdns_dp_phy_write_field(cdns_phy, - PHY_PMA_XCVR_POWER_STATE_REQ, - PHY_POWER_STATE_LN_3, 6, 0); - } - } - - cdns_dp_phy_write_field(cdns_phy, PHY_PMA_XCVR_PLLCLK_EN, - 0, 1, 0x0000); - - if (cdns_phy->num_lanes >= 2) { - cdns_dp_phy_write_field(cdns_phy, PHY_PMA_XCVR_PLLCLK_EN, - 1, 1, 0x0000); - if (cdns_phy->num_lanes == 4) { - cdns_dp_phy_write_field(cdns_phy, - PHY_PMA_XCVR_PLLCLK_EN, - 2, 1, 0x0000); - cdns_dp_phy_write_field(cdns_phy, - PHY_PMA_XCVR_PLLCLK_EN, - 3, 1, 0x0000); - } - } - - /* - * release phy_l0*_reset_n and pma_tx_elec_idle_ln_* based on - * used lanes - */ - lane_bits = (1 << cdns_phy->num_lanes) - 1; - writel(((0xF & ~lane_bits) << 4) | (0xF & lane_bits), - cdns_phy->base + PHY_RESET); - - /* release pma_xcvr_pllclk_en_ln_*, only for the master lane */ - writel(0x0001, cdns_phy->base + PHY_PMA_XCVR_PLLCLK_EN); - - /* PHY PMA registers configuration functions */ - cdns_dp_phy_pma_cmn_vco_cfg_25mhz(cdns_phy); - cdns_dp_phy_pma_cmn_rate(cdns_phy); - - /* take out of reset */ - cdns_dp_phy_write_field(cdns_phy, PHY_RESET, 8, 1, 1); - cdns_dp_phy_wait_pma_cmn_ready(cdns_phy); - cdns_dp_phy_run(cdns_phy); - - return 0; -} - -static void cdns_dp_phy_wait_pma_cmn_ready(struct cdns_dp_phy *cdns_phy) -{ - unsigned int reg; - int ret; - - ret = readl_poll_timeout(cdns_phy->base + PHY_PMA_CMN_READY, reg, - reg & 1, 0, 500); - if (ret == -ETIMEDOUT) - dev_err(cdns_phy->dev, - "timeout waiting for PMA common ready\n"); -} - -static void cdns_dp_phy_pma_cfg(struct cdns_dp_phy *cdns_phy) -{ - unsigned int i; - - /* PMA common configuration */ - cdns_dp_phy_pma_cmn_cfg_25mhz(cdns_phy); - - /* PMA lane configuration to deal with multi-link operation */ - for (i = 0; i < cdns_phy->num_lanes; i++) - cdns_dp_phy_pma_lane_cfg(cdns_phy, i); -} - -static void cdns_dp_phy_pma_cmn_cfg_25mhz(struct cdns_dp_phy *cdns_phy) -{ - /* refclock registers - assumes 25 MHz refclock */ - writel(0x0019, cdns_phy->sd_base + CMN_SSM_BIAS_TMR); - writel(0x0032, cdns_phy->sd_base + CMN_PLLSM0_PLLPRE_TMR); - writel(0x00D1, cdns_phy->sd_base + CMN_PLLSM0_PLLLOCK_TMR); - writel(0x0032, cdns_phy->sd_base + CMN_PLLSM1_PLLPRE_TMR); - writel(0x00D1, cdns_phy->sd_base + CMN_PLLSM1_PLLLOCK_TMR); - writel(0x007D, cdns_phy->sd_base + CMN_BGCAL_INIT_TMR); - writel(0x007D, cdns_phy->sd_base + CMN_BGCAL_ITER_TMR); - writel(0x0019, cdns_phy->sd_base + CMN_IBCAL_INIT_TMR); - writel(0x001E, cdns_phy->sd_base + CMN_TXPUCAL_INIT_TMR); - writel(0x0006, cdns_phy->sd_base + CMN_TXPUCAL_ITER_TMR); - writel(0x001E, cdns_phy->sd_base + CMN_TXPDCAL_INIT_TMR); - writel(0x0006, cdns_phy->sd_base + CMN_TXPDCAL_ITER_TMR); - writel(0x02EE, cdns_phy->sd_base + CMN_RXCAL_INIT_TMR); - writel(0x0006, cdns_phy->sd_base + CMN_RXCAL_ITER_TMR); - writel(0x0002, cdns_phy->sd_base + CMN_SD_CAL_INIT_TMR); - writel(0x0002, cdns_phy->sd_base + CMN_SD_CAL_ITER_TMR); - writel(0x000E, cdns_phy->sd_base + CMN_SD_CAL_REFTIM_START); - writel(0x012B, cdns_phy->sd_base + CMN_SD_CAL_PLLCNT_START); - /* PLL registers */ - writel(0x0409, cdns_phy->sd_base + CMN_PDIAG_PLL0_CP_PADJ_M0); - writel(0x1001, cdns_phy->sd_base + CMN_PDIAG_PLL0_CP_IADJ_M0); - writel(0x0F08, cdns_phy->sd_base + CMN_PDIAG_PLL0_FILT_PADJ_M0); - writel(0x0004, cdns_phy->sd_base + CMN_PLL0_DSM_DIAG_M0); - writel(0x00FA, cdns_phy->sd_base + CMN_PLL0_VCOCAL_INIT_TMR); - writel(0x0004, cdns_phy->sd_base + CMN_PLL0_VCOCAL_ITER_TMR); - writel(0x00FA, cdns_phy->sd_base + CMN_PLL1_VCOCAL_INIT_TMR); - writel(0x0004, cdns_phy->sd_base + CMN_PLL1_VCOCAL_ITER_TMR); - writel(0x0318, cdns_phy->sd_base + CMN_PLL0_VCOCAL_REFTIM_START); -} - -static void cdns_dp_phy_pma_cmn_vco_cfg_25mhz(struct cdns_dp_phy *cdns_phy) -{ - /* Assumes 25 MHz refclock */ - switch (cdns_phy->max_bit_rate) { - /* Setting VCO for 10.8GHz */ - case 2700: - case 5400: - writel(0x01B0, cdns_phy->sd_base + CMN_PLL0_INTDIV_M0); - writel(0x0000, cdns_phy->sd_base + CMN_PLL0_FRACDIVL_M0); - writel(0x0002, cdns_phy->sd_base + CMN_PLL0_FRACDIVH_M0); - writel(0x0120, cdns_phy->sd_base + CMN_PLL0_HIGH_THR_M0); - break; - /* Setting VCO for 9.72GHz */ - case 2430: - case 3240: - writel(0x0184, cdns_phy->sd_base + CMN_PLL0_INTDIV_M0); - writel(0xCCCD, cdns_phy->sd_base + CMN_PLL0_FRACDIVL_M0); - writel(0x0002, cdns_phy->sd_base + CMN_PLL0_FRACDIVH_M0); - writel(0x0104, cdns_phy->sd_base + CMN_PLL0_HIGH_THR_M0); - break; - /* Setting VCO for 8.64GHz */ - case 2160: - case 4320: - writel(0x0159, cdns_phy->sd_base + CMN_PLL0_INTDIV_M0); - writel(0x999A, cdns_phy->sd_base + CMN_PLL0_FRACDIVL_M0); - writel(0x0002, cdns_phy->sd_base + CMN_PLL0_FRACDIVH_M0); - writel(0x00E7, cdns_phy->sd_base + CMN_PLL0_HIGH_THR_M0); - break; - /* Setting VCO for 8.1GHz */ - case 8100: - writel(0x0144, cdns_phy->sd_base + CMN_PLL0_INTDIV_M0); - writel(0x0000, cdns_phy->sd_base + CMN_PLL0_FRACDIVL_M0); - writel(0x0002, cdns_phy->sd_base + CMN_PLL0_FRACDIVH_M0); - writel(0x00D8, cdns_phy->sd_base + CMN_PLL0_HIGH_THR_M0); - break; - } - - writel(0x0002, cdns_phy->sd_base + CMN_PDIAG_PLL0_CTRL_M0); - writel(0x0318, cdns_phy->sd_base + CMN_PLL0_VCOCAL_PLLCNT_START); -} - -static void cdns_dp_phy_pma_cmn_rate(struct cdns_dp_phy *cdns_phy) -{ - unsigned int clk_sel_val = 0; - unsigned int hsclk_div_val = 0; - unsigned int i; - - /* 16'h0000 for single DP link configuration */ - writel(0x0000, cdns_phy->sd_base + PHY_PLL_CFG); - - switch (cdns_phy->max_bit_rate) { - case 1620: - clk_sel_val = 0x0f01; - hsclk_div_val = 2; - break; - case 2160: - case 2430: - case 2700: - clk_sel_val = 0x0701; - hsclk_div_val = 1; - break; - case 3240: - clk_sel_val = 0x0b00; - hsclk_div_val = 2; - break; - case 4320: - case 5400: - clk_sel_val = 0x0301; - hsclk_div_val = 0; - break; - case 8100: - clk_sel_val = 0x0200; - hsclk_div_val = 0; - break; - } - - writel(clk_sel_val, cdns_phy->sd_base + CMN_PDIAG_PLL0_CLK_SEL_M0); - - /* PMA lane configuration to deal with multi-link operation */ - for (i = 0; i < cdns_phy->num_lanes; i++) { - writel(hsclk_div_val, - cdns_phy->sd_base + (XCVR_DIAG_HSCLK_DIV | (i<<11))); - } -} - -static void cdns_dp_phy_pma_lane_cfg(struct cdns_dp_phy *cdns_phy, - unsigned int lane) -{ - unsigned int lane_bits = (lane & LANE_MASK) << 11; - - /* Writing Tx/Rx Power State Controllers registers */ - writel(0x00FB, cdns_phy->sd_base + (TX_PSC_A0 | lane_bits)); - writel(0x04AA, cdns_phy->sd_base + (TX_PSC_A2 | lane_bits)); - writel(0x04AA, cdns_phy->sd_base + (TX_PSC_A3 | lane_bits)); - writel(0x0000, cdns_phy->sd_base + (RX_PSC_A0 | lane_bits)); - writel(0x0000, cdns_phy->sd_base + (RX_PSC_A2 | lane_bits)); - writel(0x0000, cdns_phy->sd_base + (RX_PSC_A3 | lane_bits)); - - writel(0x0001, cdns_phy->sd_base + (XCVR_DIAG_PLLDRC_CTRL | lane_bits)); - writel(0x0000, cdns_phy->sd_base + (XCVR_DIAG_HSCLK_SEL | lane_bits)); -} - -static void cdns_dp_phy_run(struct cdns_dp_phy *cdns_phy) -{ - unsigned int read_val; - u32 write_val1 = 0; - u32 write_val2 = 0; - u32 mask = 0; - int ret; - - /* - * waiting for ACK of pma_xcvr_pllclk_en_ln_*, only for the - * master lane - */ - ret = readl_poll_timeout(cdns_phy->base + PHY_PMA_XCVR_PLLCLK_EN_ACK, - read_val, read_val & 1, 0, POLL_TIMEOUT_US); - if (ret == -ETIMEDOUT) - dev_err(cdns_phy->dev, - "timeout waiting for link PLL clock enable ack\n"); - - ndelay(100); - - switch (cdns_phy->num_lanes) { - - case 1: /* lane 0 */ - write_val1 = 0x00000004; - write_val2 = 0x00000001; - mask = 0x0000003f; - break; - case 2: /* lane 0-1 */ - write_val1 = 0x00000404; - write_val2 = 0x00000101; - mask = 0x00003f3f; - break; - case 4: /* lane 0-3 */ - write_val1 = 0x04040404; - write_val2 = 0x01010101; - mask = 0x3f3f3f3f; - break; - } - - writel(write_val1, cdns_phy->base + PHY_PMA_XCVR_POWER_STATE_REQ); - - ret = readl_poll_timeout(cdns_phy->base + PHY_PMA_XCVR_POWER_STATE_ACK, - read_val, (read_val & mask) == write_val1, 0, - POLL_TIMEOUT_US); - if (ret == -ETIMEDOUT) - dev_err(cdns_phy->dev, - "timeout waiting for link power state ack\n"); - - writel(0, cdns_phy->base + PHY_PMA_XCVR_POWER_STATE_REQ); - ndelay(100); - - writel(write_val2, cdns_phy->base + PHY_PMA_XCVR_POWER_STATE_REQ); - - ret = readl_poll_timeout(cdns_phy->base + PHY_PMA_XCVR_POWER_STATE_ACK, - read_val, (read_val & mask) == write_val2, 0, - POLL_TIMEOUT_US); - if (ret == -ETIMEDOUT) - dev_err(cdns_phy->dev, - "timeout waiting for link power state ack\n"); - - writel(0, cdns_phy->base + PHY_PMA_XCVR_POWER_STATE_REQ); - ndelay(100); -} - -static void cdns_dp_phy_write_field(struct cdns_dp_phy *cdns_phy, - unsigned int offset, - unsigned char start_bit, - unsigned char num_bits, - unsigned int val) -{ - unsigned int read_val; - - read_val = readl(cdns_phy->base + offset); - writel(((val << start_bit) | (read_val & ~(((1 << num_bits) - 1) << - start_bit))), cdns_phy->base + offset); -} - -static int cdns_dp_phy_probe(struct platform_device *pdev) -{ - struct resource *regs; - struct cdns_dp_phy *cdns_phy; - struct device *dev = &pdev->dev; - struct phy_provider *phy_provider; - struct phy *phy; - int err; - - cdns_phy = devm_kzalloc(dev, sizeof(*cdns_phy), GFP_KERNEL); - if (!cdns_phy) - return -ENOMEM; - - cdns_phy->dev = &pdev->dev; - - phy = devm_phy_create(dev, NULL, &cdns_dp_phy_ops); - if (IS_ERR(phy)) { - dev_err(dev, "failed to create DisplayPort PHY\n"); - return PTR_ERR(phy); - } - - regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); - cdns_phy->base = devm_ioremap_resource(&pdev->dev, regs); - if (IS_ERR(cdns_phy->base)) - return PTR_ERR(cdns_phy->base); - - regs = platform_get_resource(pdev, IORESOURCE_MEM, 1); - cdns_phy->sd_base = devm_ioremap_resource(&pdev->dev, regs); - if (IS_ERR(cdns_phy->sd_base)) - return PTR_ERR(cdns_phy->sd_base); - - err = device_property_read_u32(dev, "num_lanes", - &(cdns_phy->num_lanes)); - if (err) - cdns_phy->num_lanes = DEFAULT_NUM_LANES; - - switch (cdns_phy->num_lanes) { - case 1: - case 2: - case 4: - /* valid number of lanes */ - break; - default: - dev_err(dev, "unsupported number of lanes: %d\n", - cdns_phy->num_lanes); - return -EINVAL; - } - - err = device_property_read_u32(dev, "max_bit_rate", - &(cdns_phy->max_bit_rate)); - if (err) - cdns_phy->max_bit_rate = DEFAULT_MAX_BIT_RATE; - - switch (cdns_phy->max_bit_rate) { - case 2160: - case 2430: - case 2700: - case 3240: - case 4320: - case 5400: - case 8100: - /* valid bit rate */ - break; - default: - dev_err(dev, "unsupported max bit rate: %dMbps\n", - cdns_phy->max_bit_rate); - return -EINVAL; - } - - phy_set_drvdata(phy, cdns_phy); - - phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - - dev_info(dev, "%d lanes, max bit rate %d.%03d Gbps\n", - cdns_phy->num_lanes, - cdns_phy->max_bit_rate / 1000, - cdns_phy->max_bit_rate % 1000); - - return PTR_ERR_OR_ZERO(phy_provider); -} - -static const struct of_device_id cdns_dp_phy_of_match[] = { - { - .compatible = "cdns,dp-phy" - }, - {} -}; -MODULE_DEVICE_TABLE(of, cdns_dp_phy_of_match); - -static struct platform_driver cdns_dp_phy_driver = { - .probe = cdns_dp_phy_probe, - .driver = { - .name = "cdns-dp-phy", - .of_match_table = cdns_dp_phy_of_match, - } -}; -module_platform_driver(cdns_dp_phy_driver); - -MODULE_AUTHOR("Cadence Design Systems, Inc."); -MODULE_DESCRIPTION("Cadence MHDP PHY driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/cadence/phy-cadence-torrent.c b/drivers/phy/cadence/phy-cadence-torrent.c new file mode 100644 index 000000000000..7116127358ee --- /dev/null +++ b/drivers/phy/cadence/phy-cadence-torrent.c @@ -0,0 +1,1944 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Cadence Torrent SD0801 PHY driver. + * + * Copyright 2018 Cadence Design Systems, Inc. + * + */ + +#include <dt-bindings/phy/phy.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/reset.h> +#include <linux/regmap.h> + +#define REF_CLK_19_2MHz 19200000 +#define REF_CLK_25MHz 25000000 + +#define DEFAULT_NUM_LANES 4 +#define MAX_NUM_LANES 4 +#define DEFAULT_MAX_BIT_RATE 8100 /* in Mbps */ + +#define POLL_TIMEOUT_US 5000 + +#define TORRENT_COMMON_CDB_OFFSET 0x0 + +#define TORRENT_TX_LANE_CDB_OFFSET(ln, block_offset, reg_offset) \ + ((0x4000 << (block_offset)) + \ + (((ln) << 9) << (reg_offset))) + +#define TORRENT_RX_LANE_CDB_OFFSET(ln, block_offset, reg_offset) \ + ((0x8000 << (block_offset)) + \ + (((ln) << 9) << (reg_offset))) + +#define TORRENT_PHY_PCS_COMMON_OFFSET(block_offset) \ + (0xC000 << (block_offset)) + +#define TORRENT_PHY_PMA_COMMON_OFFSET(block_offset) \ + (0xE000 << (block_offset)) + +#define TORRENT_DPTX_PHY_OFFSET 0x0 + +/* + * register offsets from DPTX PHY register block base (i.e MHDP + * register base + 0x30a00) + */ +#define PHY_AUX_CTRL 0x04 +#define PHY_RESET 0x20 +#define PMA_TX_ELEC_IDLE_MASK 0xF0U +#define PMA_TX_ELEC_IDLE_SHIFT 4 +#define PHY_L00_RESET_N_MASK 0x01U +#define PHY_PMA_XCVR_PLLCLK_EN 0x24 +#define PHY_PMA_XCVR_PLLCLK_EN_ACK 0x28 +#define PHY_PMA_XCVR_POWER_STATE_REQ 0x2c +#define PHY_POWER_STATE_LN_0 0x0000 +#define PHY_POWER_STATE_LN_1 0x0008 +#define PHY_POWER_STATE_LN_2 0x0010 +#define PHY_POWER_STATE_LN_3 0x0018 +#define PMA_XCVR_POWER_STATE_REQ_LN_MASK 0x3FU +#define PHY_PMA_XCVR_POWER_STATE_ACK 0x30 +#define PHY_PMA_CMN_READY 0x34 + +/* + * register offsets from SD0801 PHY register block base (i.e MHDP + * register base + 0x500000) + */ +#define CMN_SSM_BANDGAP_TMR 0x0021U +#define CMN_SSM_BIAS_TMR 0x0022U +#define CMN_PLLSM0_PLLPRE_TMR 0x002AU +#define CMN_PLLSM0_PLLLOCK_TMR 0x002CU +#define CMN_PLLSM1_PLLPRE_TMR 0x0032U +#define CMN_PLLSM1_PLLLOCK_TMR 0x0034U +#define CMN_BGCAL_INIT_TMR 0x0064U +#define CMN_BGCAL_ITER_TMR 0x0065U +#define CMN_IBCAL_INIT_TMR 0x0074U +#define CMN_PLL0_VCOCAL_TCTRL 0x0082U +#define CMN_PLL0_VCOCAL_INIT_TMR 0x0084U +#define CMN_PLL0_VCOCAL_ITER_TMR 0x0085U +#define CMN_PLL0_VCOCAL_REFTIM_START 0x0086U +#define CMN_PLL0_VCOCAL_PLLCNT_START 0x0088U +#define CMN_PLL0_INTDIV_M0 0x0090U +#define CMN_PLL0_FRACDIVL_M0 0x0091U +#define CMN_PLL0_FRACDIVH_M0 0x0092U +#define CMN_PLL0_HIGH_THR_M0 0x0093U +#define CMN_PLL0_DSM_DIAG_M0 0x0094U +#define CMN_PLL0_SS_CTRL1_M0 0x0098U +#define CMN_PLL0_SS_CTRL2_M0 0x0099U +#define CMN_PLL0_SS_CTRL3_M0 0x009AU +#define CMN_PLL0_SS_CTRL4_M0 0x009BU +#define CMN_PLL0_LOCK_REFCNT_START 0x009CU +#define CMN_PLL0_LOCK_PLLCNT_START 0x009EU +#define CMN_PLL0_LOCK_PLLCNT_THR 0x009FU +#define CMN_PLL1_VCOCAL_TCTRL 0x00C2U +#define CMN_PLL1_VCOCAL_INIT_TMR 0x00C4U +#define CMN_PLL1_VCOCAL_ITER_TMR 0x00C5U +#define CMN_PLL1_VCOCAL_REFTIM_START 0x00C6U +#define CMN_PLL1_VCOCAL_PLLCNT_START 0x00C8U +#define CMN_PLL1_INTDIV_M0 0x00D0U +#define CMN_PLL1_FRACDIVL_M0 0x00D1U +#define CMN_PLL1_FRACDIVH_M0 0x00D2U +#define CMN_PLL1_HIGH_THR_M0 0x00D3U +#define CMN_PLL1_DSM_DIAG_M0 0x00D4U +#define CMN_PLL1_SS_CTRL1_M0 0x00D8U +#define CMN_PLL1_SS_CTRL2_M0 0x00D9U +#define CMN_PLL1_SS_CTRL3_M0 0x00DAU +#define CMN_PLL1_SS_CTRL4_M0 0x00DBU +#define CMN_PLL1_LOCK_REFCNT_START 0x00DCU +#define CMN_PLL1_LOCK_PLLCNT_START 0x00DEU +#define CMN_PLL1_LOCK_PLLCNT_THR 0x00DFU +#define CMN_TXPUCAL_INIT_TMR 0x0104U +#define CMN_TXPUCAL_ITER_TMR 0x0105U +#define CMN_TXPDCAL_INIT_TMR 0x010CU +#define CMN_TXPDCAL_ITER_TMR 0x010DU +#define CMN_RXCAL_INIT_TMR 0x0114U +#define CMN_RXCAL_ITER_TMR 0x0115U +#define CMN_SD_CAL_INIT_TMR 0x0124U +#define CMN_SD_CAL_ITER_TMR 0x0125U +#define CMN_SD_CAL_REFTIM_START 0x0126U +#define CMN_SD_CAL_PLLCNT_START 0x0128U +#define CMN_PDIAG_PLL0_CTRL_M0 0x01A0U +#define CMN_PDIAG_PLL0_CLK_SEL_M0 0x01A1U +#define CMN_PDIAG_PLL0_CP_PADJ_M0 0x01A4U +#define CMN_PDIAG_PLL0_CP_IADJ_M0 0x01A5U +#define CMN_PDIAG_PLL0_FILT_PADJ_M0 0x01A6U +#define CMN_PDIAG_PLL0_CP_PADJ_M1 0x01B4U +#define CMN_PDIAG_PLL0_CP_IADJ_M1 0x01B5U +#define CMN_PDIAG_PLL1_CTRL_M0 0x01C0U +#define CMN_PDIAG_PLL1_CLK_SEL_M0 0x01C1U +#define CMN_PDIAG_PLL1_CP_PADJ_M0 0x01C4U +#define CMN_PDIAG_PLL1_CP_IADJ_M0 0x01C5U +#define CMN_PDIAG_PLL1_FILT_PADJ_M0 0x01C6U + +/* PMA TX Lane registers */ +#define TX_TXCC_CTRL 0x0040U +#define TX_TXCC_CPOST_MULT_00 0x004CU +#define TX_TXCC_MGNFS_MULT_000 0x0050U +#define DRV_DIAG_TX_DRV 0x00C6U +#define XCVR_DIAG_PLLDRC_CTRL 0x00E5U +#define XCVR_DIAG_HSCLK_SEL 0x00E6U +#define XCVR_DIAG_HSCLK_DIV 0x00E7U +#define XCVR_DIAG_BIDI_CTRL 0x00EAU +#define TX_PSC_A0 0x0100U +#define TX_PSC_A2 0x0102U +#define TX_PSC_A3 0x0103U +#define TX_RCVDET_ST_TMR 0x0123U +#define TX_DIAG_ACYA 0x01E7U +#define TX_DIAG_ACYA_HBDC_MASK 0x0001U + +/* PMA RX Lane registers */ +#define RX_PSC_A0 0x0000U +#define RX_PSC_A2 0x0002U +#define RX_PSC_A3 0x0003U +#define RX_PSC_CAL 0x0006U +#define RX_REE_GCSM1_CTRL 0x0108U +#define RX_REE_GCSM2_CTRL 0x0110U +#define RX_REE_PERGCSM_CTRL 0x0118U + +/* PHY PCS common registers */ +#define PHY_PLL_CFG 0x000EU + +/* PHY PMA common registers */ +#define PHY_PMA_CMN_CTRL2 0x0001U +#define PHY_PMA_PLL_RAW_CTRL 0x0003U + +static const struct reg_field phy_pll_cfg = + REG_FIELD(PHY_PLL_CFG, 0, 1); + +static const struct reg_field phy_pma_cmn_ctrl_2 = + REG_FIELD(PHY_PMA_CMN_CTRL2, 0, 7); + +static const struct reg_field phy_pma_pll_raw_ctrl = + REG_FIELD(PHY_PMA_PLL_RAW_CTRL, 0, 1); + +static const struct reg_field phy_reset_ctrl = + REG_FIELD(PHY_RESET, 8, 8); + +static const struct of_device_id cdns_torrent_phy_of_match[]; + +struct cdns_torrent_inst { + struct phy *phy; + u32 mlane; + u32 phy_type; + u32 num_lanes; + struct reset_control *lnk_rst; +}; + +struct cdns_torrent_phy { + void __iomem *base; /* DPTX registers base */ + void __iomem *sd_base; /* SD0801 registers base */ + u32 max_bit_rate; /* Maximum link bit rate to use (in Mbps) */ + struct reset_control *phy_rst; + struct device *dev; + struct clk *clk; + unsigned long ref_clk_rate; + struct cdns_torrent_inst phys[MAX_NUM_LANES]; + int nsubnodes; + struct regmap *regmap; + struct regmap *regmap_common_cdb; + struct regmap *regmap_phy_pcs_common_cdb; + struct regmap *regmap_phy_pma_common_cdb; + struct regmap *regmap_tx_lane_cdb[MAX_NUM_LANES]; + struct regmap *regmap_rx_lane_cdb[MAX_NUM_LANES]; + struct regmap *regmap_dptx_phy_reg; + struct regmap_field *phy_pll_cfg; + struct regmap_field *phy_pma_cmn_ctrl_2; + struct regmap_field *phy_pma_pll_raw_ctrl; + struct regmap_field *phy_reset_ctrl; +}; + +enum phy_powerstate { + POWERSTATE_A0 = 0, + /* Powerstate A1 is unused */ + POWERSTATE_A2 = 2, + POWERSTATE_A3 = 3, +}; + +static int cdns_torrent_dp_init(struct phy *phy); +static int cdns_torrent_dp_exit(struct phy *phy); +static int cdns_torrent_dp_run(struct cdns_torrent_phy *cdns_phy, + u32 num_lanes); +static +int cdns_torrent_dp_wait_pma_cmn_ready(struct cdns_torrent_phy *cdns_phy); +static void cdns_torrent_dp_pma_cfg(struct cdns_torrent_phy *cdns_phy, + struct cdns_torrent_inst *inst); +static +void cdns_torrent_dp_pma_cmn_cfg_19_2mhz(struct cdns_torrent_phy *cdns_phy); +static +void cdns_torrent_dp_pma_cmn_vco_cfg_19_2mhz(struct cdns_torrent_phy *cdns_phy, + u32 rate, bool ssc); +static +void cdns_torrent_dp_pma_cmn_cfg_25mhz(struct cdns_torrent_phy *cdns_phy); +static +void cdns_torrent_dp_pma_cmn_vco_cfg_25mhz(struct cdns_torrent_phy *cdns_phy, + u32 rate, bool ssc); +static void cdns_torrent_dp_pma_lane_cfg(struct cdns_torrent_phy *cdns_phy, + unsigned int lane); +static void cdns_torrent_dp_pma_cmn_rate(struct cdns_torrent_phy *cdns_phy, + u32 rate, u32 num_lanes); +static int cdns_torrent_dp_configure(struct phy *phy, + union phy_configure_opts *opts); +static int cdns_torrent_dp_set_power_state(struct cdns_torrent_phy *cdns_phy, + u32 num_lanes, + enum phy_powerstate powerstate); +static int cdns_torrent_phy_on(struct phy *phy); +static int cdns_torrent_phy_off(struct phy *phy); + +static const struct phy_ops cdns_torrent_phy_ops = { + .init = cdns_torrent_dp_init, + .exit = cdns_torrent_dp_exit, + .configure = cdns_torrent_dp_configure, + .power_on = cdns_torrent_phy_on, + .power_off = cdns_torrent_phy_off, + .owner = THIS_MODULE, +}; + +struct cdns_torrent_data { + u8 block_offset_shift; + u8 reg_offset_shift; +}; + +struct cdns_regmap_cdb_context { + struct device *dev; + void __iomem *base; + u8 reg_offset_shift; +}; + +static int cdns_regmap_write(void *context, unsigned int reg, unsigned int val) +{ + struct cdns_regmap_cdb_context *ctx = context; + u32 offset = reg << ctx->reg_offset_shift; + + writew(val, ctx->base + offset); + + return 0; +} + +static int cdns_regmap_read(void *context, unsigned int reg, unsigned int *val) +{ + struct cdns_regmap_cdb_context *ctx = context; + u32 offset = reg << ctx->reg_offset_shift; + + *val = readw(ctx->base + offset); + return 0; +} + +static int cdns_regmap_dptx_write(void *context, unsigned int reg, + unsigned int val) +{ + struct cdns_regmap_cdb_context *ctx = context; + u32 offset = reg; + + writel(val, ctx->base + offset); + + return 0; +} + +static int cdns_regmap_dptx_read(void *context, unsigned int reg, + unsigned int *val) +{ + struct cdns_regmap_cdb_context *ctx = context; + u32 offset = reg; + + *val = readl(ctx->base + offset); + return 0; +} + +#define TORRENT_TX_LANE_CDB_REGMAP_CONF(n) \ +{ \ + .name = "torrent_tx_lane" n "_cdb", \ + .reg_stride = 1, \ + .fast_io = true, \ + .reg_write = cdns_regmap_write, \ + .reg_read = cdns_regmap_read, \ +} + +#define TORRENT_RX_LANE_CDB_REGMAP_CONF(n) \ +{ \ + .name = "torrent_rx_lane" n "_cdb", \ + .reg_stride = 1, \ + .fast_io = true, \ + .reg_write = cdns_regmap_write, \ + .reg_read = cdns_regmap_read, \ +} + +static struct regmap_config cdns_torrent_tx_lane_cdb_config[] = { + TORRENT_TX_LANE_CDB_REGMAP_CONF("0"), + TORRENT_TX_LANE_CDB_REGMAP_CONF("1"), + TORRENT_TX_LANE_CDB_REGMAP_CONF("2"), + TORRENT_TX_LANE_CDB_REGMAP_CONF("3"), +}; + +static struct regmap_config cdns_torrent_rx_lane_cdb_config[] = { + TORRENT_RX_LANE_CDB_REGMAP_CONF("0"), + TORRENT_RX_LANE_CDB_REGMAP_CONF("1"), + TORRENT_RX_LANE_CDB_REGMAP_CONF("2"), + TORRENT_RX_LANE_CDB_REGMAP_CONF("3"), +}; + +static struct regmap_config cdns_torrent_common_cdb_config = { + .name = "torrent_common_cdb", + .reg_stride = 1, + .fast_io = true, + .reg_write = cdns_regmap_write, + .reg_read = cdns_regmap_read, +}; + +static struct regmap_config cdns_torrent_phy_pcs_cmn_cdb_config = { + .name = "torrent_phy_pcs_cmn_cdb", + .reg_stride = 1, + .fast_io = true, + .reg_write = cdns_regmap_write, + .reg_read = cdns_regmap_read, +}; + +static struct regmap_config cdns_torrent_phy_pma_cmn_cdb_config = { + .name = "torrent_phy_pma_cmn_cdb", + .reg_stride = 1, + .fast_io = true, + .reg_write = cdns_regmap_write, + .reg_read = cdns_regmap_read, +}; + +static struct regmap_config cdns_torrent_dptx_phy_config = { + .name = "torrent_dptx_phy", + .reg_stride = 1, + .fast_io = true, + .reg_write = cdns_regmap_dptx_write, + .reg_read = cdns_regmap_dptx_read, +}; + +/* PHY mmr access functions */ + +static void cdns_torrent_phy_write(struct regmap *regmap, u32 offset, u32 val) +{ + regmap_write(regmap, offset, val); +} + +static u32 cdns_torrent_phy_read(struct regmap *regmap, u32 offset) +{ + unsigned int val; + + regmap_read(regmap, offset, &val); + return val; +} + +/* DPTX mmr access functions */ + +static void cdns_torrent_dp_write(struct regmap *regmap, u32 offset, u32 val) +{ + regmap_write(regmap, offset, val); +} + +static u32 cdns_torrent_dp_read(struct regmap *regmap, u32 offset) +{ + u32 val; + + regmap_read(regmap, offset, &val); + return val; +} + +/* + * Structure used to store values of PHY registers for voltage-related + * coefficients, for particular voltage swing and pre-emphasis level. Values + * are shared across all physical lanes. + */ +struct coefficients { + /* Value of DRV_DIAG_TX_DRV register to use */ + u16 diag_tx_drv; + /* Value of TX_TXCC_MGNFS_MULT_000 register to use */ + u16 mgnfs_mult; + /* Value of TX_TXCC_CPOST_MULT_00 register to use */ + u16 cpost_mult; +}; + +/* + * Array consists of values of voltage-related registers for sd0801 PHY. A value + * of 0xFFFF is a placeholder for invalid combination, and will never be used. + */ +static const struct coefficients vltg_coeff[4][4] = { + /* voltage swing 0, pre-emphasis 0->3 */ + { {.diag_tx_drv = 0x0003, .mgnfs_mult = 0x002A, + .cpost_mult = 0x0000}, + {.diag_tx_drv = 0x0003, .mgnfs_mult = 0x001F, + .cpost_mult = 0x0014}, + {.diag_tx_drv = 0x0003, .mgnfs_mult = 0x0012, + .cpost_mult = 0x0020}, + {.diag_tx_drv = 0x0003, .mgnfs_mult = 0x0000, + .cpost_mult = 0x002A} + }, + + /* voltage swing 1, pre-emphasis 0->3 */ + { {.diag_tx_drv = 0x0003, .mgnfs_mult = 0x001F, + .cpost_mult = 0x0000}, + {.diag_tx_drv = 0x0003, .mgnfs_mult = 0x0013, + .cpost_mult = 0x0012}, + {.diag_tx_drv = 0x0003, .mgnfs_mult = 0x0000, + .cpost_mult = 0x001F}, + {.diag_tx_drv = 0xFFFF, .mgnfs_mult = 0xFFFF, + .cpost_mult = 0xFFFF} + }, + + /* voltage swing 2, pre-emphasis 0->3 */ + { {.diag_tx_drv = 0x0003, .mgnfs_mult = 0x0013, + .cpost_mult = 0x0000}, + {.diag_tx_drv = 0x0003, .mgnfs_mult = 0x0000, + .cpost_mult = 0x0013}, + {.diag_tx_drv = 0xFFFF, .mgnfs_mult = 0xFFFF, + .cpost_mult = 0xFFFF}, + {.diag_tx_drv = 0xFFFF, .mgnfs_mult = 0xFFFF, + .cpost_mult = 0xFFFF} + }, + + /* voltage swing 3, pre-emphasis 0->3 */ + { {.diag_tx_drv = 0x0003, .mgnfs_mult = 0x0000, + .cpost_mult = 0x0000}, + {.diag_tx_drv = 0xFFFF, .mgnfs_mult = 0xFFFF, + .cpost_mult = 0xFFFF}, + {.diag_tx_drv = 0xFFFF, .mgnfs_mult = 0xFFFF, + .cpost_mult = 0xFFFF}, + {.diag_tx_drv = 0xFFFF, .mgnfs_mult = 0xFFFF, + .cpost_mult = 0xFFFF} + } +}; + +/* + * Enable or disable PLL for selected lanes. + */ +static int cdns_torrent_dp_set_pll_en(struct cdns_torrent_phy *cdns_phy, + struct phy_configure_opts_dp *dp, + bool enable) +{ + u32 rd_val; + u32 ret; + struct regmap *regmap = cdns_phy->regmap_dptx_phy_reg; + + /* + * Used to determine, which bits to check for or enable in + * PHY_PMA_XCVR_PLLCLK_EN register. + */ + u32 pll_bits; + /* Used to enable or disable lanes. */ + u32 pll_val; + + /* Select values of registers and mask, depending on enabled lane + * count. + */ + switch (dp->lanes) { + /* lane 0 */ + case (1): + pll_bits = 0x00000001; + break; + /* lanes 0-1 */ + case (2): + pll_bits = 0x00000003; + break; + /* lanes 0-3, all */ + default: + pll_bits = 0x0000000F; + break; + } + + if (enable) + pll_val = pll_bits; + else + pll_val = 0x00000000; + + cdns_torrent_dp_write(regmap, PHY_PMA_XCVR_PLLCLK_EN, pll_val); + + /* Wait for acknowledgment from PHY. */ + ret = regmap_read_poll_timeout(regmap, + PHY_PMA_XCVR_PLLCLK_EN_ACK, + rd_val, + (rd_val & pll_bits) == pll_val, + 0, POLL_TIMEOUT_US); + ndelay(100); + return ret; +} + +/* + * Perform register operations related to setting link rate, once powerstate is + * set and PLL disable request was processed. + */ +static int cdns_torrent_dp_configure_rate(struct cdns_torrent_phy *cdns_phy, + struct phy_configure_opts_dp *dp) +{ + u32 ret; + u32 read_val; + + /* Disable the cmn_pll0_en before re-programming the new data rate. */ + regmap_field_write(cdns_phy->phy_pma_pll_raw_ctrl, 0x0); + + /* + * Wait for PLL ready de-assertion. + * For PLL0 - PHY_PMA_CMN_CTRL2[2] == 1 + */ + ret = regmap_field_read_poll_timeout(cdns_phy->phy_pma_cmn_ctrl_2, + read_val, + ((read_val >> 2) & 0x01) != 0, + 0, POLL_TIMEOUT_US); + if (ret) + return ret; + ndelay(200); + + /* DP Rate Change - VCO Output settings. */ + if (cdns_phy->ref_clk_rate == REF_CLK_19_2MHz) { + /* PMA common configuration 19.2MHz */ + cdns_torrent_dp_pma_cmn_vco_cfg_19_2mhz(cdns_phy, dp->link_rate, + dp->ssc); + cdns_torrent_dp_pma_cmn_cfg_19_2mhz(cdns_phy); + } else if (cdns_phy->ref_clk_rate == REF_CLK_25MHz) { + /* PMA common configuration 25MHz */ + cdns_torrent_dp_pma_cmn_vco_cfg_25mhz(cdns_phy, dp->link_rate, + dp->ssc); + cdns_torrent_dp_pma_cmn_cfg_25mhz(cdns_phy); + } + cdns_torrent_dp_pma_cmn_rate(cdns_phy, dp->link_rate, dp->lanes); + + /* Enable the cmn_pll0_en. */ + regmap_field_write(cdns_phy->phy_pma_pll_raw_ctrl, 0x3); + + /* + * Wait for PLL ready assertion. + * For PLL0 - PHY_PMA_CMN_CTRL2[0] == 1 + */ + ret = regmap_field_read_poll_timeout(cdns_phy->phy_pma_cmn_ctrl_2, + read_val, + (read_val & 0x01) != 0, + 0, POLL_TIMEOUT_US); + return ret; +} + +/* + * Verify, that parameters to configure PHY with are correct. + */ +static int cdns_torrent_dp_verify_config(struct cdns_torrent_inst *inst, + struct phy_configure_opts_dp *dp) +{ + u8 i; + + /* If changing link rate was required, verify it's supported. */ + if (dp->set_rate) { + switch (dp->link_rate) { + case 1620: + case 2160: + case 2430: + case 2700: + case 3240: + case 4320: + case 5400: + case 8100: + /* valid bit rate */ + break; + default: + return -EINVAL; + } + } + + /* Verify lane count. */ + switch (dp->lanes) { + case 1: + case 2: + case 4: + /* valid lane count. */ + break; + default: + return -EINVAL; + } + + /* Check against actual number of PHY's lanes. */ + if (dp->lanes > inst->num_lanes) + return -EINVAL; + + /* + * If changing voltages is required, check swing and pre-emphasis + * levels, per-lane. + */ + if (dp->set_voltages) { + /* Lane count verified previously. */ + for (i = 0; i < dp->lanes; i++) { + if (dp->voltage[i] > 3 || dp->pre[i] > 3) + return -EINVAL; + + /* Sum of voltage swing and pre-emphasis levels cannot + * exceed 3. + */ + if (dp->voltage[i] + dp->pre[i] > 3) + return -EINVAL; + } + } + + return 0; +} + +/* Set power state A0 and PLL clock enable to 0 on enabled lanes. */ +static void cdns_torrent_dp_set_a0_pll(struct cdns_torrent_phy *cdns_phy, + u32 num_lanes) +{ + struct regmap *regmap = cdns_phy->regmap_dptx_phy_reg; + u32 pwr_state = cdns_torrent_dp_read(regmap, + PHY_PMA_XCVR_POWER_STATE_REQ); + u32 pll_clk_en = cdns_torrent_dp_read(regmap, + PHY_PMA_XCVR_PLLCLK_EN); + + /* Lane 0 is always enabled. */ + pwr_state &= ~(PMA_XCVR_POWER_STATE_REQ_LN_MASK << + PHY_POWER_STATE_LN_0); + pll_clk_en &= ~0x01U; + + if (num_lanes > 1) { + /* lane 1 */ + pwr_state &= ~(PMA_XCVR_POWER_STATE_REQ_LN_MASK << + PHY_POWER_STATE_LN_1); + pll_clk_en &= ~(0x01U << 1); + } + + if (num_lanes > 2) { + /* lanes 2 and 3 */ + pwr_state &= ~(PMA_XCVR_POWER_STATE_REQ_LN_MASK << + PHY_POWER_STATE_LN_2); + pwr_state &= ~(PMA_XCVR_POWER_STATE_REQ_LN_MASK << + PHY_POWER_STATE_LN_3); + pll_clk_en &= ~(0x01U << 2); + pll_clk_en &= ~(0x01U << 3); + } + + cdns_torrent_dp_write(regmap, PHY_PMA_XCVR_POWER_STATE_REQ, pwr_state); + cdns_torrent_dp_write(regmap, PHY_PMA_XCVR_PLLCLK_EN, pll_clk_en); +} + +/* Configure lane count as required. */ +static int cdns_torrent_dp_set_lanes(struct cdns_torrent_phy *cdns_phy, + struct phy_configure_opts_dp *dp) +{ + u32 value; + u32 ret; + struct regmap *regmap = cdns_phy->regmap_dptx_phy_reg; + u8 lane_mask = (1 << dp->lanes) - 1; + + value = cdns_torrent_dp_read(regmap, PHY_RESET); + /* clear pma_tx_elec_idle_ln_* bits. */ + value &= ~PMA_TX_ELEC_IDLE_MASK; + /* Assert pma_tx_elec_idle_ln_* for disabled lanes. */ + value |= ((~lane_mask) << PMA_TX_ELEC_IDLE_SHIFT) & + PMA_TX_ELEC_IDLE_MASK; + cdns_torrent_dp_write(regmap, PHY_RESET, value); + + /* reset the link by asserting phy_l00_reset_n low */ + cdns_torrent_dp_write(regmap, PHY_RESET, + value & (~PHY_L00_RESET_N_MASK)); + + /* + * Assert lane reset on unused lanes and lane 0 so they remain in reset + * and powered down when re-enabling the link + */ + value = (value & 0x0000FFF0) | (0x0000000E & lane_mask); + cdns_torrent_dp_write(regmap, PHY_RESET, value); + + cdns_torrent_dp_set_a0_pll(cdns_phy, dp->lanes); + + /* release phy_l0*_reset_n based on used laneCount */ + value = (value & 0x0000FFF0) | (0x0000000F & lane_mask); + cdns_torrent_dp_write(regmap, PHY_RESET, value); + + /* Wait, until PHY gets ready after releasing PHY reset signal. */ + ret = cdns_torrent_dp_wait_pma_cmn_ready(cdns_phy); + if (ret) + return ret; + + ndelay(100); + + /* release pma_xcvr_pllclk_en_ln_*, only for the master lane */ + cdns_torrent_dp_write(regmap, PHY_PMA_XCVR_PLLCLK_EN, 0x0001); + + ret = cdns_torrent_dp_run(cdns_phy, dp->lanes); + + return ret; +} + +/* Configure link rate as required. */ +static int cdns_torrent_dp_set_rate(struct cdns_torrent_phy *cdns_phy, + struct phy_configure_opts_dp *dp) +{ + u32 ret; + + ret = cdns_torrent_dp_set_power_state(cdns_phy, dp->lanes, + POWERSTATE_A3); + if (ret) + return ret; + ret = cdns_torrent_dp_set_pll_en(cdns_phy, dp, false); + if (ret) + return ret; + ndelay(200); + + ret = cdns_torrent_dp_configure_rate(cdns_phy, dp); + if (ret) + return ret; + ndelay(200); + + ret = cdns_torrent_dp_set_pll_en(cdns_phy, dp, true); + if (ret) + return ret; + ret = cdns_torrent_dp_set_power_state(cdns_phy, dp->lanes, + POWERSTATE_A2); + if (ret) + return ret; + ret = cdns_torrent_dp_set_power_state(cdns_phy, dp->lanes, + POWERSTATE_A0); + if (ret) + return ret; + ndelay(900); + + return ret; +} + +/* Configure voltage swing and pre-emphasis for all enabled lanes. */ +static void cdns_torrent_dp_set_voltages(struct cdns_torrent_phy *cdns_phy, + struct phy_configure_opts_dp *dp) +{ + u8 lane; + u16 val; + + for (lane = 0; lane < dp->lanes; lane++) { + val = cdns_torrent_phy_read(cdns_phy->regmap_tx_lane_cdb[lane], + TX_DIAG_ACYA); + /* + * Write 1 to register bit TX_DIAG_ACYA[0] to freeze the + * current state of the analog TX driver. + */ + val |= TX_DIAG_ACYA_HBDC_MASK; + cdns_torrent_phy_write(cdns_phy->regmap_tx_lane_cdb[lane], + TX_DIAG_ACYA, val); + + cdns_torrent_phy_write(cdns_phy->regmap_tx_lane_cdb[lane], + TX_TXCC_CTRL, 0x08A4); + val = vltg_coeff[dp->voltage[lane]][dp->pre[lane]].diag_tx_drv; + cdns_torrent_phy_write(cdns_phy->regmap_tx_lane_cdb[lane], + DRV_DIAG_TX_DRV, val); + val = vltg_coeff[dp->voltage[lane]][dp->pre[lane]].mgnfs_mult; + cdns_torrent_phy_write(cdns_phy->regmap_tx_lane_cdb[lane], + TX_TXCC_MGNFS_MULT_000, + val); + val = vltg_coeff[dp->voltage[lane]][dp->pre[lane]].cpost_mult; + cdns_torrent_phy_write(cdns_phy->regmap_tx_lane_cdb[lane], + TX_TXCC_CPOST_MULT_00, + val); + + val = cdns_torrent_phy_read(cdns_phy->regmap_tx_lane_cdb[lane], + TX_DIAG_ACYA); + /* + * Write 0 to register bit TX_DIAG_ACYA[0] to allow the state of + * analog TX driver to reflect the new programmed one. + */ + val &= ~TX_DIAG_ACYA_HBDC_MASK; + cdns_torrent_phy_write(cdns_phy->regmap_tx_lane_cdb[lane], + TX_DIAG_ACYA, val); + } +}; + +static int cdns_torrent_dp_configure(struct phy *phy, + union phy_configure_opts *opts) +{ + struct cdns_torrent_inst *inst = phy_get_drvdata(phy); + struct cdns_torrent_phy *cdns_phy = dev_get_drvdata(phy->dev.parent); + int ret; + + ret = cdns_torrent_dp_verify_config(inst, &opts->dp); + if (ret) { + dev_err(&phy->dev, "invalid params for phy configure\n"); + return ret; + } + + if (opts->dp.set_lanes) { + ret = cdns_torrent_dp_set_lanes(cdns_phy, &opts->dp); + if (ret) { + dev_err(&phy->dev, "cdns_torrent_dp_set_lanes failed\n"); + return ret; + } + } + + if (opts->dp.set_rate) { + ret = cdns_torrent_dp_set_rate(cdns_phy, &opts->dp); + if (ret) { + dev_err(&phy->dev, "cdns_torrent_dp_set_rate failed\n"); + return ret; + } + } + + if (opts->dp.set_voltages) + cdns_torrent_dp_set_voltages(cdns_phy, &opts->dp); + + return ret; +} + +static int cdns_torrent_dp_init(struct phy *phy) +{ + unsigned char lane_bits; + int ret; + struct cdns_torrent_inst *inst = phy_get_drvdata(phy); + struct cdns_torrent_phy *cdns_phy = dev_get_drvdata(phy->dev.parent); + struct regmap *regmap = cdns_phy->regmap_dptx_phy_reg; + + ret = clk_prepare_enable(cdns_phy->clk); + if (ret) { + dev_err(cdns_phy->dev, "Failed to prepare ref clock\n"); + return ret; + } + + cdns_phy->ref_clk_rate = clk_get_rate(cdns_phy->clk); + if (!(cdns_phy->ref_clk_rate)) { + dev_err(cdns_phy->dev, "Failed to get ref clock rate\n"); + clk_disable_unprepare(cdns_phy->clk); + return -EINVAL; + } + + switch (cdns_phy->ref_clk_rate) { + case REF_CLK_19_2MHz: + case REF_CLK_25MHz: + /* Valid Ref Clock Rate */ + break; + default: + dev_err(cdns_phy->dev, "Unsupported Ref Clock Rate\n"); + return -EINVAL; + } + + cdns_torrent_dp_write(regmap, PHY_AUX_CTRL, 0x0003); /* enable AUX */ + + /* PHY PMA registers configuration function */ + cdns_torrent_dp_pma_cfg(cdns_phy, inst); + + /* + * Set lines power state to A0 + * Set lines pll clk enable to 0 + */ + cdns_torrent_dp_set_a0_pll(cdns_phy, inst->num_lanes); + + /* + * release phy_l0*_reset_n and pma_tx_elec_idle_ln_* based on + * used lanes + */ + lane_bits = (1 << inst->num_lanes) - 1; + cdns_torrent_dp_write(regmap, PHY_RESET, + ((0xF & ~lane_bits) << 4) | (0xF & lane_bits)); + + /* release pma_xcvr_pllclk_en_ln_*, only for the master lane */ + cdns_torrent_dp_write(regmap, PHY_PMA_XCVR_PLLCLK_EN, 0x0001); + + /* PHY PMA registers configuration functions */ + /* Initialize PHY with max supported link rate, without SSC. */ + if (cdns_phy->ref_clk_rate == REF_CLK_19_2MHz) + cdns_torrent_dp_pma_cmn_vco_cfg_19_2mhz(cdns_phy, + cdns_phy->max_bit_rate, + false); + else if (cdns_phy->ref_clk_rate == REF_CLK_25MHz) + cdns_torrent_dp_pma_cmn_vco_cfg_25mhz(cdns_phy, + cdns_phy->max_bit_rate, + false); + cdns_torrent_dp_pma_cmn_rate(cdns_phy, cdns_phy->max_bit_rate, + inst->num_lanes); + + /* take out of reset */ + regmap_field_write(cdns_phy->phy_reset_ctrl, 0x1); + + cdns_torrent_phy_on(phy); + + ret = cdns_torrent_dp_wait_pma_cmn_ready(cdns_phy); + if (ret) + return ret; + + ret = cdns_torrent_dp_run(cdns_phy, inst->num_lanes); + + return ret; +} + +static int cdns_torrent_dp_exit(struct phy *phy) +{ + struct cdns_torrent_phy *cdns_phy = dev_get_drvdata(phy->dev.parent); + + clk_disable_unprepare(cdns_phy->clk); + return 0; +} + +static +int cdns_torrent_dp_wait_pma_cmn_ready(struct cdns_torrent_phy *cdns_phy) +{ + unsigned int reg; + int ret; + struct regmap *regmap = cdns_phy->regmap_dptx_phy_reg; + + ret = regmap_read_poll_timeout(regmap, PHY_PMA_CMN_READY, reg, + reg & 1, 0, POLL_TIMEOUT_US); + if (ret == -ETIMEDOUT) { + dev_err(cdns_phy->dev, + "timeout waiting for PMA common ready\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static void cdns_torrent_dp_pma_cfg(struct cdns_torrent_phy *cdns_phy, + struct cdns_torrent_inst *inst) +{ + unsigned int i; + + if (cdns_phy->ref_clk_rate == REF_CLK_19_2MHz) + /* PMA common configuration 19.2MHz */ + cdns_torrent_dp_pma_cmn_cfg_19_2mhz(cdns_phy); + else if (cdns_phy->ref_clk_rate == REF_CLK_25MHz) + /* PMA common configuration 25MHz */ + cdns_torrent_dp_pma_cmn_cfg_25mhz(cdns_phy); + + /* PMA lane configuration to deal with multi-link operation */ + for (i = 0; i < inst->num_lanes; i++) + cdns_torrent_dp_pma_lane_cfg(cdns_phy, i); +} + +static +void cdns_torrent_dp_pma_cmn_cfg_19_2mhz(struct cdns_torrent_phy *cdns_phy) +{ + struct regmap *regmap = cdns_phy->regmap_common_cdb; + + /* refclock registers - assumes 19.2 MHz refclock */ + cdns_torrent_phy_write(regmap, CMN_SSM_BIAS_TMR, 0x0014); + cdns_torrent_phy_write(regmap, CMN_PLLSM0_PLLPRE_TMR, 0x0027); + cdns_torrent_phy_write(regmap, CMN_PLLSM0_PLLLOCK_TMR, 0x00A1); + cdns_torrent_phy_write(regmap, CMN_PLLSM1_PLLPRE_TMR, 0x0027); + cdns_torrent_phy_write(regmap, CMN_PLLSM1_PLLLOCK_TMR, 0x00A1); + cdns_torrent_phy_write(regmap, CMN_BGCAL_INIT_TMR, 0x0060); + cdns_torrent_phy_write(regmap, CMN_BGCAL_ITER_TMR, 0x0060); + cdns_torrent_phy_write(regmap, CMN_IBCAL_INIT_TMR, 0x0014); + cdns_torrent_phy_write(regmap, CMN_TXPUCAL_INIT_TMR, 0x0018); + cdns_torrent_phy_write(regmap, CMN_TXPUCAL_ITER_TMR, 0x0005); + cdns_torrent_phy_write(regmap, CMN_TXPDCAL_INIT_TMR, 0x0018); + cdns_torrent_phy_write(regmap, CMN_TXPDCAL_ITER_TMR, 0x0005); + cdns_torrent_phy_write(regmap, CMN_RXCAL_INIT_TMR, 0x0240); + cdns_torrent_phy_write(regmap, CMN_RXCAL_ITER_TMR, 0x0005); + cdns_torrent_phy_write(regmap, CMN_SD_CAL_INIT_TMR, 0x0002); + cdns_torrent_phy_write(regmap, CMN_SD_CAL_ITER_TMR, 0x0002); + cdns_torrent_phy_write(regmap, CMN_SD_CAL_REFTIM_START, 0x000B); + cdns_torrent_phy_write(regmap, CMN_SD_CAL_PLLCNT_START, 0x0137); + + /* PLL registers */ + cdns_torrent_phy_write(regmap, CMN_PDIAG_PLL0_CP_PADJ_M0, 0x0509); + cdns_torrent_phy_write(regmap, CMN_PDIAG_PLL0_CP_IADJ_M0, 0x0F00); + cdns_torrent_phy_write(regmap, CMN_PDIAG_PLL0_FILT_PADJ_M0, 0x0F08); + cdns_torrent_phy_write(regmap, CMN_PLL0_DSM_DIAG_M0, 0x0004); + cdns_torrent_phy_write(regmap, CMN_PDIAG_PLL1_CP_PADJ_M0, 0x0509); + cdns_torrent_phy_write(regmap, CMN_PDIAG_PLL1_CP_IADJ_M0, 0x0F00); + cdns_torrent_phy_write(regmap, CMN_PDIAG_PLL1_FILT_PADJ_M0, 0x0F08); + cdns_torrent_phy_write(regmap, CMN_PLL1_DSM_DIAG_M0, 0x0004); + cdns_torrent_phy_write(regmap, CMN_PLL0_VCOCAL_INIT_TMR, 0x00C0); + cdns_torrent_phy_write(regmap, CMN_PLL0_VCOCAL_ITER_TMR, 0x0004); + cdns_torrent_phy_write(regmap, CMN_PLL1_VCOCAL_INIT_TMR, 0x00C0); + cdns_torrent_phy_write(regmap, CMN_PLL1_VCOCAL_ITER_TMR, 0x0004); + cdns_torrent_phy_write(regmap, CMN_PLL0_VCOCAL_REFTIM_START, 0x0260); + cdns_torrent_phy_write(regmap, CMN_PLL0_VCOCAL_TCTRL, 0x0003); + cdns_torrent_phy_write(regmap, CMN_PLL1_VCOCAL_REFTIM_START, 0x0260); + cdns_torrent_phy_write(regmap, CMN_PLL1_VCOCAL_TCTRL, 0x0003); +} + +/* + * Set registers responsible for enabling and configuring SSC, with second and + * third register values provided by parameters. + */ +static +void cdns_torrent_dp_enable_ssc_19_2mhz(struct cdns_torrent_phy *cdns_phy, + u32 ctrl2_val, u32 ctrl3_val) +{ + struct regmap *regmap = cdns_phy->regmap_common_cdb; + + cdns_torrent_phy_write(regmap, CMN_PLL0_SS_CTRL1_M0, 0x0001); + cdns_torrent_phy_write(regmap, CMN_PLL0_SS_CTRL1_M0, ctrl2_val); + cdns_torrent_phy_write(regmap, CMN_PLL0_SS_CTRL1_M0, ctrl3_val); + cdns_torrent_phy_write(regmap, CMN_PLL0_SS_CTRL4_M0, 0x0003); + cdns_torrent_phy_write(regmap, CMN_PLL1_SS_CTRL1_M0, 0x0001); + cdns_torrent_phy_write(regmap, CMN_PLL1_SS_CTRL1_M0, ctrl2_val); + cdns_torrent_phy_write(regmap, CMN_PLL1_SS_CTRL1_M0, ctrl3_val); + cdns_torrent_phy_write(regmap, CMN_PLL1_SS_CTRL4_M0, 0x0003); +} + +static +void cdns_torrent_dp_pma_cmn_vco_cfg_19_2mhz(struct cdns_torrent_phy *cdns_phy, + u32 rate, bool ssc) +{ + struct regmap *regmap = cdns_phy->regmap_common_cdb; + + /* Assumes 19.2 MHz refclock */ + switch (rate) { + /* Setting VCO for 10.8GHz */ + case 2700: + case 5400: + cdns_torrent_phy_write(regmap, + CMN_PLL0_INTDIV_M0, 0x0119); + cdns_torrent_phy_write(regmap, + CMN_PLL0_FRACDIVL_M0, 0x4000); + cdns_torrent_phy_write(regmap, + CMN_PLL0_FRACDIVH_M0, 0x0002); + cdns_torrent_phy_write(regmap, + CMN_PLL0_HIGH_THR_M0, 0x00BC); + cdns_torrent_phy_write(regmap, + CMN_PDIAG_PLL0_CTRL_M0, 0x0012); + cdns_torrent_phy_write(regmap, + CMN_PLL1_INTDIV_M0, 0x0119); + cdns_torrent_phy_write(regmap, + CMN_PLL1_FRACDIVL_M0, 0x4000); + cdns_torrent_phy_write(regmap, + CMN_PLL1_FRACDIVH_M0, 0x0002); + cdns_torrent_phy_write(regmap, + CMN_PLL1_HIGH_THR_M0, 0x00BC); + cdns_torrent_phy_write(regmap, + CMN_PDIAG_PLL1_CTRL_M0, 0x0012); + if (ssc) + cdns_torrent_dp_enable_ssc_19_2mhz(cdns_phy, 0x033A, + 0x006A); + break; + /* Setting VCO for 9.72GHz */ + case 1620: + case 2430: + case 3240: + cdns_torrent_phy_write(regmap, + CMN_PLL0_INTDIV_M0, 0x01FA); + cdns_torrent_phy_write(regmap, + CMN_PLL0_FRACDIVL_M0, 0x4000); + cdns_torrent_phy_write(regmap, + CMN_PLL0_FRACDIVH_M0, 0x0002); + cdns_torrent_phy_write(regmap, + CMN_PLL0_HIGH_THR_M0, 0x0152); + cdns_torrent_phy_write(regmap, + CMN_PDIAG_PLL0_CTRL_M0, 0x0002); + cdns_torrent_phy_write(regmap, + CMN_PLL1_INTDIV_M0, 0x01FA); + cdns_torrent_phy_write(regmap, + CMN_PLL1_FRACDIVL_M0, 0x4000); + cdns_torrent_phy_write(regmap, + CMN_PLL1_FRACDIVH_M0, 0x0002); + cdns_torrent_phy_write(regmap, + CMN_PLL1_HIGH_THR_M0, 0x0152); + cdns_torrent_phy_write(regmap, + CMN_PDIAG_PLL1_CTRL_M0, 0x0002); + if (ssc) + cdns_torrent_dp_enable_ssc_19_2mhz(cdns_phy, 0x05DD, + 0x0069); + break; + /* Setting VCO for 8.64GHz */ + case 2160: + case 4320: + cdns_torrent_phy_write(regmap, + CMN_PLL0_INTDIV_M0, 0x01C2); + cdns_torrent_phy_write(regmap, + CMN_PLL0_FRACDIVL_M0, 0x0000); + cdns_torrent_phy_write(regmap, + CMN_PLL0_FRACDIVH_M0, 0x0002); + cdns_torrent_phy_write(regmap, + CMN_PLL0_HIGH_THR_M0, 0x012C); + cdns_torrent_phy_write(regmap, + CMN_PDIAG_PLL0_CTRL_M0, 0x0002); + cdns_torrent_phy_write(regmap, + CMN_PLL1_INTDIV_M0, 0x01C2); + cdns_torrent_phy_write(regmap, + CMN_PLL1_FRACDIVL_M0, 0x0000); + cdns_torrent_phy_write(regmap, + CMN_PLL1_FRACDIVH_M0, 0x0002); + cdns_torrent_phy_write(regmap, + CMN_PLL1_HIGH_THR_M0, 0x012C); + cdns_torrent_phy_write(regmap, + CMN_PDIAG_PLL1_CTRL_M0, 0x0002); + if (ssc) + cdns_torrent_dp_enable_ssc_19_2mhz(cdns_phy, 0x0536, + 0x0069); + break; + /* Setting VCO for 8.1GHz */ + case 8100: + cdns_torrent_phy_write(regmap, + CMN_PLL0_INTDIV_M0, 0x01A5); + cdns_torrent_phy_write(regmap, + CMN_PLL0_FRACDIVL_M0, 0xE000); + cdns_torrent_phy_write(regmap, + CMN_PLL0_FRACDIVH_M0, 0x0002); + cdns_torrent_phy_write(regmap, + CMN_PLL0_HIGH_THR_M0, 0x011A); + cdns_torrent_phy_write(regmap, + CMN_PDIAG_PLL0_CTRL_M0, 0x0002); + cdns_torrent_phy_write(regmap, + CMN_PLL1_INTDIV_M0, 0x01A5); + cdns_torrent_phy_write(regmap, + CMN_PLL1_FRACDIVL_M0, 0xE000); + cdns_torrent_phy_write(regmap, + CMN_PLL1_FRACDIVH_M0, 0x0002); + cdns_torrent_phy_write(regmap, + CMN_PLL1_HIGH_THR_M0, 0x011A); + cdns_torrent_phy_write(regmap, + CMN_PDIAG_PLL1_CTRL_M0, 0x0002); + if (ssc) + cdns_torrent_dp_enable_ssc_19_2mhz(cdns_phy, 0x04D7, + 0x006A); + break; + } + + if (ssc) { + cdns_torrent_phy_write(regmap, + CMN_PLL0_VCOCAL_PLLCNT_START, 0x025E); + cdns_torrent_phy_write(regmap, + CMN_PLL0_LOCK_PLLCNT_THR, 0x0005); + cdns_torrent_phy_write(regmap, + CMN_PLL1_VCOCAL_PLLCNT_START, 0x025E); + cdns_torrent_phy_write(regmap, + CMN_PLL1_LOCK_PLLCNT_THR, 0x0005); + } else { + cdns_torrent_phy_write(regmap, + CMN_PLL0_VCOCAL_PLLCNT_START, 0x0260); + cdns_torrent_phy_write(regmap, + CMN_PLL1_VCOCAL_PLLCNT_START, 0x0260); + /* Set reset register values to disable SSC */ + cdns_torrent_phy_write(regmap, + CMN_PLL0_SS_CTRL1_M0, 0x0002); + cdns_torrent_phy_write(regmap, + CMN_PLL0_SS_CTRL2_M0, 0x0000); + cdns_torrent_phy_write(regmap, + CMN_PLL0_SS_CTRL3_M0, 0x0000); + cdns_torrent_phy_write(regmap, + CMN_PLL0_SS_CTRL4_M0, 0x0000); + cdns_torrent_phy_write(regmap, + CMN_PLL0_LOCK_PLLCNT_THR, 0x0003); + cdns_torrent_phy_write(regmap, + CMN_PLL1_SS_CTRL1_M0, 0x0002); + cdns_torrent_phy_write(regmap, + CMN_PLL1_SS_CTRL2_M0, 0x0000); + cdns_torrent_phy_write(regmap, + CMN_PLL1_SS_CTRL3_M0, 0x0000); + cdns_torrent_phy_write(regmap, + CMN_PLL1_SS_CTRL4_M0, 0x0000); + cdns_torrent_phy_write(regmap, + CMN_PLL1_LOCK_PLLCNT_THR, 0x0003); + } + + cdns_torrent_phy_write(regmap, CMN_PLL0_LOCK_REFCNT_START, 0x0099); + cdns_torrent_phy_write(regmap, CMN_PLL0_LOCK_PLLCNT_START, 0x0099); + cdns_torrent_phy_write(regmap, CMN_PLL1_LOCK_REFCNT_START, 0x0099); + cdns_torrent_phy_write(regmap, CMN_PLL1_LOCK_PLLCNT_START, 0x0099); +} + +static +void cdns_torrent_dp_pma_cmn_cfg_25mhz(struct cdns_torrent_phy *cdns_phy) +{ + struct regmap *regmap = cdns_phy->regmap_common_cdb; + + /* refclock registers - assumes 25 MHz refclock */ + cdns_torrent_phy_write(regmap, CMN_SSM_BIAS_TMR, 0x0019); + cdns_torrent_phy_write(regmap, CMN_PLLSM0_PLLPRE_TMR, 0x0032); + cdns_torrent_phy_write(regmap, CMN_PLLSM0_PLLLOCK_TMR, 0x00D1); + cdns_torrent_phy_write(regmap, CMN_PLLSM1_PLLPRE_TMR, 0x0032); + cdns_torrent_phy_write(regmap, CMN_PLLSM1_PLLLOCK_TMR, 0x00D1); + cdns_torrent_phy_write(regmap, CMN_BGCAL_INIT_TMR, 0x007D); + cdns_torrent_phy_write(regmap, CMN_BGCAL_ITER_TMR, 0x007D); + cdns_torrent_phy_write(regmap, CMN_IBCAL_INIT_TMR, 0x0019); + cdns_torrent_phy_write(regmap, CMN_TXPUCAL_INIT_TMR, 0x001E); + cdns_torrent_phy_write(regmap, CMN_TXPUCAL_ITER_TMR, 0x0006); + cdns_torrent_phy_write(regmap, CMN_TXPDCAL_INIT_TMR, 0x001E); + cdns_torrent_phy_write(regmap, CMN_TXPDCAL_ITER_TMR, 0x0006); + cdns_torrent_phy_write(regmap, CMN_RXCAL_INIT_TMR, 0x02EE); + cdns_torrent_phy_write(regmap, CMN_RXCAL_ITER_TMR, 0x0006); + cdns_torrent_phy_write(regmap, CMN_SD_CAL_INIT_TMR, 0x0002); + cdns_torrent_phy_write(regmap, CMN_SD_CAL_ITER_TMR, 0x0002); + cdns_torrent_phy_write(regmap, CMN_SD_CAL_REFTIM_START, 0x000E); + cdns_torrent_phy_write(regmap, CMN_SD_CAL_PLLCNT_START, 0x012B); + + /* PLL registers */ + cdns_torrent_phy_write(regmap, CMN_PDIAG_PLL0_CP_PADJ_M0, 0x0509); + cdns_torrent_phy_write(regmap, CMN_PDIAG_PLL0_CP_IADJ_M0, 0x0F00); + cdns_torrent_phy_write(regmap, CMN_PDIAG_PLL0_FILT_PADJ_M0, 0x0F08); + cdns_torrent_phy_write(regmap, CMN_PLL0_DSM_DIAG_M0, 0x0004); + cdns_torrent_phy_write(regmap, CMN_PDIAG_PLL1_CP_PADJ_M0, 0x0509); + cdns_torrent_phy_write(regmap, CMN_PDIAG_PLL1_CP_IADJ_M0, 0x0F00); + cdns_torrent_phy_write(regmap, CMN_PDIAG_PLL1_FILT_PADJ_M0, 0x0F08); + cdns_torrent_phy_write(regmap, CMN_PLL1_DSM_DIAG_M0, 0x0004); + cdns_torrent_phy_write(regmap, CMN_PLL0_VCOCAL_INIT_TMR, 0x00FA); + cdns_torrent_phy_write(regmap, CMN_PLL0_VCOCAL_ITER_TMR, 0x0004); + cdns_torrent_phy_write(regmap, CMN_PLL1_VCOCAL_INIT_TMR, 0x00FA); + cdns_torrent_phy_write(regmap, CMN_PLL1_VCOCAL_ITER_TMR, 0x0004); + cdns_torrent_phy_write(regmap, CMN_PLL0_VCOCAL_REFTIM_START, 0x0317); + cdns_torrent_phy_write(regmap, CMN_PLL0_VCOCAL_TCTRL, 0x0003); + cdns_torrent_phy_write(regmap, CMN_PLL1_VCOCAL_REFTIM_START, 0x0317); + cdns_torrent_phy_write(regmap, CMN_PLL1_VCOCAL_TCTRL, 0x0003); +} + +/* + * Set registers responsible for enabling and configuring SSC, with second + * register value provided by a parameter. + */ +static void cdns_torrent_dp_enable_ssc_25mhz(struct cdns_torrent_phy *cdns_phy, + u32 ctrl2_val) +{ + struct regmap *regmap = cdns_phy->regmap_common_cdb; + + cdns_torrent_phy_write(regmap, CMN_PLL0_SS_CTRL1_M0, 0x0001); + cdns_torrent_phy_write(regmap, CMN_PLL0_SS_CTRL1_M0, ctrl2_val); + cdns_torrent_phy_write(regmap, CMN_PLL0_SS_CTRL1_M0, 0x007F); + cdns_torrent_phy_write(regmap, CMN_PLL0_SS_CTRL4_M0, 0x0003); + cdns_torrent_phy_write(regmap, CMN_PLL1_SS_CTRL1_M0, 0x0001); + cdns_torrent_phy_write(regmap, CMN_PLL1_SS_CTRL1_M0, ctrl2_val); + cdns_torrent_phy_write(regmap, CMN_PLL1_SS_CTRL1_M0, 0x007F); + cdns_torrent_phy_write(regmap, CMN_PLL1_SS_CTRL4_M0, 0x0003); +} + +static +void cdns_torrent_dp_pma_cmn_vco_cfg_25mhz(struct cdns_torrent_phy *cdns_phy, + u32 rate, bool ssc) +{ + struct regmap *regmap = cdns_phy->regmap_common_cdb; + + /* Assumes 25 MHz refclock */ + switch (rate) { + /* Setting VCO for 10.8GHz */ + case 2700: + case 5400: + cdns_torrent_phy_write(regmap, CMN_PLL0_INTDIV_M0, 0x01B0); + cdns_torrent_phy_write(regmap, CMN_PLL0_FRACDIVL_M0, 0x0000); + cdns_torrent_phy_write(regmap, CMN_PLL0_FRACDIVH_M0, 0x0002); + cdns_torrent_phy_write(regmap, CMN_PLL0_HIGH_THR_M0, 0x0120); + cdns_torrent_phy_write(regmap, CMN_PLL1_INTDIV_M0, 0x01B0); + cdns_torrent_phy_write(regmap, CMN_PLL1_FRACDIVL_M0, 0x0000); + cdns_torrent_phy_write(regmap, CMN_PLL1_FRACDIVH_M0, 0x0002); + cdns_torrent_phy_write(regmap, CMN_PLL1_HIGH_THR_M0, 0x0120); + if (ssc) + cdns_torrent_dp_enable_ssc_25mhz(cdns_phy, 0x0423); + break; + /* Setting VCO for 9.72GHz */ + case 1620: + case 2430: + case 3240: + cdns_torrent_phy_write(regmap, CMN_PLL0_INTDIV_M0, 0x0184); + cdns_torrent_phy_write(regmap, CMN_PLL0_FRACDIVL_M0, 0xCCCD); + cdns_torrent_phy_write(regmap, CMN_PLL0_FRACDIVH_M0, 0x0002); + cdns_torrent_phy_write(regmap, CMN_PLL0_HIGH_THR_M0, 0x0104); + cdns_torrent_phy_write(regmap, CMN_PLL1_INTDIV_M0, 0x0184); + cdns_torrent_phy_write(regmap, CMN_PLL1_FRACDIVL_M0, 0xCCCD); + cdns_torrent_phy_write(regmap, CMN_PLL1_FRACDIVH_M0, 0x0002); + cdns_torrent_phy_write(regmap, CMN_PLL1_HIGH_THR_M0, 0x0104); + if (ssc) + cdns_torrent_dp_enable_ssc_25mhz(cdns_phy, 0x03B9); + break; + /* Setting VCO for 8.64GHz */ + case 2160: + case 4320: + cdns_torrent_phy_write(regmap, CMN_PLL0_INTDIV_M0, 0x0159); + cdns_torrent_phy_write(regmap, CMN_PLL0_FRACDIVL_M0, 0x999A); + cdns_torrent_phy_write(regmap, CMN_PLL0_FRACDIVH_M0, 0x0002); + cdns_torrent_phy_write(regmap, CMN_PLL0_HIGH_THR_M0, 0x00E7); + cdns_torrent_phy_write(regmap, CMN_PLL1_INTDIV_M0, 0x0159); + cdns_torrent_phy_write(regmap, CMN_PLL1_FRACDIVL_M0, 0x999A); + cdns_torrent_phy_write(regmap, CMN_PLL1_FRACDIVH_M0, 0x0002); + cdns_torrent_phy_write(regmap, CMN_PLL1_HIGH_THR_M0, 0x00E7); + if (ssc) + cdns_torrent_dp_enable_ssc_25mhz(cdns_phy, 0x034F); + break; + /* Setting VCO for 8.1GHz */ + case 8100: + cdns_torrent_phy_write(regmap, CMN_PLL0_INTDIV_M0, 0x0144); + cdns_torrent_phy_write(regmap, CMN_PLL0_FRACDIVL_M0, 0x0000); + cdns_torrent_phy_write(regmap, CMN_PLL0_FRACDIVH_M0, 0x0002); + cdns_torrent_phy_write(regmap, CMN_PLL0_HIGH_THR_M0, 0x00D8); + cdns_torrent_phy_write(regmap, CMN_PLL1_INTDIV_M0, 0x0144); + cdns_torrent_phy_write(regmap, CMN_PLL1_FRACDIVL_M0, 0x0000); + cdns_torrent_phy_write(regmap, CMN_PLL1_FRACDIVH_M0, 0x0002); + cdns_torrent_phy_write(regmap, CMN_PLL1_HIGH_THR_M0, 0x00D8); + if (ssc) + cdns_torrent_dp_enable_ssc_25mhz(cdns_phy, 0x031A); + break; + } + + cdns_torrent_phy_write(regmap, CMN_PDIAG_PLL0_CTRL_M0, 0x0002); + cdns_torrent_phy_write(regmap, CMN_PDIAG_PLL1_CTRL_M0, 0x0002); + + if (ssc) { + cdns_torrent_phy_write(regmap, + CMN_PLL0_VCOCAL_PLLCNT_START, 0x0315); + cdns_torrent_phy_write(regmap, + CMN_PLL0_LOCK_PLLCNT_THR, 0x0005); + cdns_torrent_phy_write(regmap, + CMN_PLL1_VCOCAL_PLLCNT_START, 0x0315); + cdns_torrent_phy_write(regmap, + CMN_PLL1_LOCK_PLLCNT_THR, 0x0005); + } else { + cdns_torrent_phy_write(regmap, + CMN_PLL0_VCOCAL_PLLCNT_START, 0x0317); + cdns_torrent_phy_write(regmap, + CMN_PLL1_VCOCAL_PLLCNT_START, 0x0317); + /* Set reset register values to disable SSC */ + cdns_torrent_phy_write(regmap, CMN_PLL0_SS_CTRL1_M0, 0x0002); + cdns_torrent_phy_write(regmap, CMN_PLL0_SS_CTRL2_M0, 0x0000); + cdns_torrent_phy_write(regmap, CMN_PLL0_SS_CTRL3_M0, 0x0000); + cdns_torrent_phy_write(regmap, CMN_PLL0_SS_CTRL4_M0, 0x0000); + cdns_torrent_phy_write(regmap, + CMN_PLL0_LOCK_PLLCNT_THR, 0x0003); + cdns_torrent_phy_write(regmap, CMN_PLL1_SS_CTRL1_M0, 0x0002); + cdns_torrent_phy_write(regmap, CMN_PLL1_SS_CTRL2_M0, 0x0000); + cdns_torrent_phy_write(regmap, CMN_PLL1_SS_CTRL3_M0, 0x0000); + cdns_torrent_phy_write(regmap, CMN_PLL1_SS_CTRL4_M0, 0x0000); + cdns_torrent_phy_write(regmap, + CMN_PLL1_LOCK_PLLCNT_THR, 0x0003); + } + + cdns_torrent_phy_write(regmap, CMN_PLL0_LOCK_REFCNT_START, 0x00C7); + cdns_torrent_phy_write(regmap, CMN_PLL0_LOCK_PLLCNT_START, 0x00C7); + cdns_torrent_phy_write(regmap, CMN_PLL1_LOCK_REFCNT_START, 0x00C7); + cdns_torrent_phy_write(regmap, CMN_PLL1_LOCK_PLLCNT_START, 0x00C7); +} + +static void cdns_torrent_dp_pma_cmn_rate(struct cdns_torrent_phy *cdns_phy, + u32 rate, u32 num_lanes) +{ + unsigned int clk_sel_val = 0; + unsigned int hsclk_div_val = 0; + unsigned int i; + + /* 16'h0000 for single DP link configuration */ + regmap_field_write(cdns_phy->phy_pll_cfg, 0x0); + + switch (rate) { + case 1620: + clk_sel_val = 0x0f01; + hsclk_div_val = 2; + break; + case 2160: + case 2430: + case 2700: + clk_sel_val = 0x0701; + hsclk_div_val = 1; + break; + case 3240: + clk_sel_val = 0x0b00; + hsclk_div_val = 2; + break; + case 4320: + case 5400: + clk_sel_val = 0x0301; + hsclk_div_val = 0; + break; + case 8100: + clk_sel_val = 0x0200; + hsclk_div_val = 0; + break; + } + + cdns_torrent_phy_write(cdns_phy->regmap_common_cdb, + CMN_PDIAG_PLL0_CLK_SEL_M0, clk_sel_val); + cdns_torrent_phy_write(cdns_phy->regmap_common_cdb, + CMN_PDIAG_PLL1_CLK_SEL_M0, clk_sel_val); + + /* PMA lane configuration to deal with multi-link operation */ + for (i = 0; i < num_lanes; i++) + cdns_torrent_phy_write(cdns_phy->regmap_tx_lane_cdb[i], + XCVR_DIAG_HSCLK_DIV, hsclk_div_val); +} + +static void cdns_torrent_dp_pma_lane_cfg(struct cdns_torrent_phy *cdns_phy, + unsigned int lane) +{ + /* Per lane, refclock-dependent receiver detection setting */ + if (cdns_phy->ref_clk_rate == REF_CLK_19_2MHz) + cdns_torrent_phy_write(cdns_phy->regmap_tx_lane_cdb[lane], + TX_RCVDET_ST_TMR, 0x0780); + else if (cdns_phy->ref_clk_rate == REF_CLK_25MHz) + cdns_torrent_phy_write(cdns_phy->regmap_tx_lane_cdb[lane], + TX_RCVDET_ST_TMR, 0x09C4); + + /* Writing Tx/Rx Power State Controllers registers */ + cdns_torrent_phy_write(cdns_phy->regmap_tx_lane_cdb[lane], + TX_PSC_A0, 0x00FB); + cdns_torrent_phy_write(cdns_phy->regmap_tx_lane_cdb[lane], + TX_PSC_A2, 0x04AA); + cdns_torrent_phy_write(cdns_phy->regmap_tx_lane_cdb[lane], + TX_PSC_A3, 0x04AA); + cdns_torrent_phy_write(cdns_phy->regmap_rx_lane_cdb[lane], + RX_PSC_A0, 0x0000); + cdns_torrent_phy_write(cdns_phy->regmap_rx_lane_cdb[lane], + RX_PSC_A2, 0x0000); + cdns_torrent_phy_write(cdns_phy->regmap_rx_lane_cdb[lane], + RX_PSC_A3, 0x0000); + + cdns_torrent_phy_write(cdns_phy->regmap_rx_lane_cdb[lane], + RX_PSC_CAL, 0x0000); + + cdns_torrent_phy_write(cdns_phy->regmap_rx_lane_cdb[lane], + RX_REE_GCSM1_CTRL, 0x0000); + cdns_torrent_phy_write(cdns_phy->regmap_rx_lane_cdb[lane], + RX_REE_GCSM2_CTRL, 0x0000); + cdns_torrent_phy_write(cdns_phy->regmap_rx_lane_cdb[lane], + RX_REE_PERGCSM_CTRL, 0x0000); + + cdns_torrent_phy_write(cdns_phy->regmap_tx_lane_cdb[lane], + XCVR_DIAG_BIDI_CTRL, 0x000F); + cdns_torrent_phy_write(cdns_phy->regmap_tx_lane_cdb[lane], + XCVR_DIAG_PLLDRC_CTRL, 0x0001); + cdns_torrent_phy_write(cdns_phy->regmap_tx_lane_cdb[lane], + XCVR_DIAG_HSCLK_SEL, 0x0000); +} + +static int cdns_torrent_dp_set_power_state(struct cdns_torrent_phy *cdns_phy, + u32 num_lanes, + enum phy_powerstate powerstate) +{ + /* Register value for power state for a single byte. */ + u32 value_part; + u32 value; + u32 mask; + u32 read_val; + u32 ret; + struct regmap *regmap = cdns_phy->regmap_dptx_phy_reg; + + switch (powerstate) { + case (POWERSTATE_A0): + value_part = 0x01U; + break; + case (POWERSTATE_A2): + value_part = 0x04U; + break; + default: + /* Powerstate A3 */ + value_part = 0x08U; + break; + } + + /* Select values of registers and mask, depending on enabled + * lane count. + */ + switch (num_lanes) { + /* lane 0 */ + case (1): + value = value_part; + mask = 0x0000003FU; + break; + /* lanes 0-1 */ + case (2): + value = (value_part + | (value_part << 8)); + mask = 0x00003F3FU; + break; + /* lanes 0-3, all */ + default: + value = (value_part + | (value_part << 8) + | (value_part << 16) + | (value_part << 24)); + mask = 0x3F3F3F3FU; + break; + } + + /* Set power state A<n>. */ + cdns_torrent_dp_write(regmap, PHY_PMA_XCVR_POWER_STATE_REQ, value); + /* Wait, until PHY acknowledges power state completion. */ + ret = regmap_read_poll_timeout(regmap, PHY_PMA_XCVR_POWER_STATE_ACK, + read_val, (read_val & mask) == value, 0, + POLL_TIMEOUT_US); + cdns_torrent_dp_write(regmap, PHY_PMA_XCVR_POWER_STATE_REQ, 0x00000000); + ndelay(100); + + return ret; +} + +static int cdns_torrent_dp_run(struct cdns_torrent_phy *cdns_phy, u32 num_lanes) +{ + unsigned int read_val; + int ret; + struct regmap *regmap = cdns_phy->regmap_dptx_phy_reg; + + /* + * waiting for ACK of pma_xcvr_pllclk_en_ln_*, only for the + * master lane + */ + ret = regmap_read_poll_timeout(regmap, PHY_PMA_XCVR_PLLCLK_EN_ACK, + read_val, read_val & 1, + 0, POLL_TIMEOUT_US); + if (ret == -ETIMEDOUT) { + dev_err(cdns_phy->dev, + "timeout waiting for link PLL clock enable ack\n"); + return ret; + } + + ndelay(100); + + ret = cdns_torrent_dp_set_power_state(cdns_phy, num_lanes, + POWERSTATE_A2); + if (ret) + return ret; + + ret = cdns_torrent_dp_set_power_state(cdns_phy, num_lanes, + POWERSTATE_A0); + + return ret; +} + +static int cdns_torrent_phy_on(struct phy *phy) +{ + struct cdns_torrent_inst *inst = phy_get_drvdata(phy); + struct cdns_torrent_phy *cdns_phy = dev_get_drvdata(phy->dev.parent); + int ret; + + /* Take the PHY out of reset */ + ret = reset_control_deassert(cdns_phy->phy_rst); + if (ret) + return ret; + + /* Take the PHY lane group out of reset */ + return reset_control_deassert(inst->lnk_rst); +} + +static int cdns_torrent_phy_off(struct phy *phy) +{ + struct cdns_torrent_inst *inst = phy_get_drvdata(phy); + struct cdns_torrent_phy *cdns_phy = dev_get_drvdata(phy->dev.parent); + int ret; + + ret = reset_control_assert(cdns_phy->phy_rst); + if (ret) + return ret; + + return reset_control_assert(inst->lnk_rst); +} + +static struct regmap *cdns_regmap_init(struct device *dev, void __iomem *base, + u32 block_offset, + u8 reg_offset_shift, + const struct regmap_config *config) +{ + struct cdns_regmap_cdb_context *ctx; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return ERR_PTR(-ENOMEM); + + ctx->dev = dev; + ctx->base = base + block_offset; + ctx->reg_offset_shift = reg_offset_shift; + + return devm_regmap_init(dev, NULL, ctx, config); +} + +static int cdns_regfield_init(struct cdns_torrent_phy *cdns_phy) +{ + struct device *dev = cdns_phy->dev; + struct regmap_field *field; + struct regmap *regmap; + + regmap = cdns_phy->regmap_phy_pcs_common_cdb; + field = devm_regmap_field_alloc(dev, regmap, phy_pll_cfg); + if (IS_ERR(field)) { + dev_err(dev, "PHY_PLL_CFG reg field init failed\n"); + return PTR_ERR(field); + } + cdns_phy->phy_pll_cfg = field; + + regmap = cdns_phy->regmap_phy_pma_common_cdb; + field = devm_regmap_field_alloc(dev, regmap, phy_pma_cmn_ctrl_2); + if (IS_ERR(field)) { + dev_err(dev, "PHY_PMA_CMN_CTRL2 reg field init failed\n"); + return PTR_ERR(field); + } + cdns_phy->phy_pma_cmn_ctrl_2 = field; + + regmap = cdns_phy->regmap_phy_pma_common_cdb; + field = devm_regmap_field_alloc(dev, regmap, phy_pma_pll_raw_ctrl); + if (IS_ERR(field)) { + dev_err(dev, "PHY_PMA_PLL_RAW_CTRL reg field init failed\n"); + return PTR_ERR(field); + } + cdns_phy->phy_pma_pll_raw_ctrl = field; + + regmap = cdns_phy->regmap_dptx_phy_reg; + field = devm_regmap_field_alloc(dev, regmap, phy_reset_ctrl); + if (IS_ERR(field)) { + dev_err(dev, "PHY_RESET reg field init failed\n"); + return PTR_ERR(field); + } + cdns_phy->phy_reset_ctrl = field; + + return 0; +} + +static int cdns_regmap_init_torrent_dp(struct cdns_torrent_phy *cdns_phy, + void __iomem *sd_base, + void __iomem *base, + u8 block_offset_shift, + u8 reg_offset_shift) +{ + struct device *dev = cdns_phy->dev; + struct regmap *regmap; + u32 block_offset; + int i; + + for (i = 0; i < MAX_NUM_LANES; i++) { + block_offset = TORRENT_TX_LANE_CDB_OFFSET(i, block_offset_shift, + reg_offset_shift); + regmap = cdns_regmap_init(dev, sd_base, block_offset, + reg_offset_shift, + &cdns_torrent_tx_lane_cdb_config[i]); + if (IS_ERR(regmap)) { + dev_err(dev, "Failed to init tx lane CDB regmap\n"); + return PTR_ERR(regmap); + } + cdns_phy->regmap_tx_lane_cdb[i] = regmap; + + block_offset = TORRENT_RX_LANE_CDB_OFFSET(i, block_offset_shift, + reg_offset_shift); + regmap = cdns_regmap_init(dev, sd_base, block_offset, + reg_offset_shift, + &cdns_torrent_rx_lane_cdb_config[i]); + if (IS_ERR(regmap)) { + dev_err(dev, "Failed to init rx lane CDB regmap\n"); + return PTR_ERR(regmap); + } + cdns_phy->regmap_rx_lane_cdb[i] = regmap; + } + + block_offset = TORRENT_COMMON_CDB_OFFSET; + regmap = cdns_regmap_init(dev, sd_base, block_offset, + reg_offset_shift, + &cdns_torrent_common_cdb_config); + if (IS_ERR(regmap)) { + dev_err(dev, "Failed to init common CDB regmap\n"); + return PTR_ERR(regmap); + } + cdns_phy->regmap_common_cdb = regmap; + + block_offset = TORRENT_PHY_PCS_COMMON_OFFSET(block_offset_shift); + regmap = cdns_regmap_init(dev, sd_base, block_offset, + reg_offset_shift, + &cdns_torrent_phy_pcs_cmn_cdb_config); + if (IS_ERR(regmap)) { + dev_err(dev, "Failed to init PHY PCS common CDB regmap\n"); + return PTR_ERR(regmap); + } + cdns_phy->regmap_phy_pcs_common_cdb = regmap; + + block_offset = TORRENT_PHY_PMA_COMMON_OFFSET(block_offset_shift); + regmap = cdns_regmap_init(dev, sd_base, block_offset, + reg_offset_shift, + &cdns_torrent_phy_pma_cmn_cdb_config); + if (IS_ERR(regmap)) { + dev_err(dev, "Failed to init PHY PMA common CDB regmap\n"); + return PTR_ERR(regmap); + } + cdns_phy->regmap_phy_pma_common_cdb = regmap; + + block_offset = TORRENT_DPTX_PHY_OFFSET; + regmap = cdns_regmap_init(dev, base, block_offset, + reg_offset_shift, + &cdns_torrent_dptx_phy_config); + if (IS_ERR(regmap)) { + dev_err(dev, "Failed to init DPTX PHY regmap\n"); + return PTR_ERR(regmap); + } + cdns_phy->regmap_dptx_phy_reg = regmap; + + return 0; +} + +static int cdns_torrent_phy_probe(struct platform_device *pdev) +{ + struct resource *regs; + struct cdns_torrent_phy *cdns_phy; + struct device *dev = &pdev->dev; + struct phy_provider *phy_provider; + const struct of_device_id *match; + struct cdns_torrent_data *data; + struct device_node *child; + int ret, subnodes, node = 0, i; + + /* Get init data for this PHY */ + match = of_match_device(cdns_torrent_phy_of_match, dev); + if (!match) + return -EINVAL; + + data = (struct cdns_torrent_data *)match->data; + + cdns_phy = devm_kzalloc(dev, sizeof(*cdns_phy), GFP_KERNEL); + if (!cdns_phy) + return -ENOMEM; + + dev_set_drvdata(dev, cdns_phy); + cdns_phy->dev = dev; + + cdns_phy->phy_rst = devm_reset_control_get_exclusive_by_index(dev, 0); + if (IS_ERR(cdns_phy->phy_rst)) { + dev_err(dev, "%s: failed to get reset\n", + dev->of_node->full_name); + return PTR_ERR(cdns_phy->phy_rst); + } + + cdns_phy->clk = devm_clk_get(dev, "refclk"); + if (IS_ERR(cdns_phy->clk)) { + dev_err(dev, "phy ref clock not found\n"); + return PTR_ERR(cdns_phy->clk); + } + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + cdns_phy->sd_base = devm_ioremap_resource(&pdev->dev, regs); + if (IS_ERR(cdns_phy->sd_base)) + return PTR_ERR(cdns_phy->sd_base); + + subnodes = of_get_available_child_count(dev->of_node); + if (subnodes == 0) { + dev_err(dev, "No available link subnodes found\n"); + return -EINVAL; + } else if (subnodes != 1) { + dev_err(dev, "Driver supports only one link subnode.\n"); + return -EINVAL; + } + + for_each_available_child_of_node(dev->of_node, child) { + struct phy *gphy; + + cdns_phy->phys[node].lnk_rst = + of_reset_control_array_get_exclusive(child); + if (IS_ERR(cdns_phy->phys[node].lnk_rst)) { + dev_err(dev, "%s: failed to get reset\n", + child->full_name); + ret = PTR_ERR(cdns_phy->phys[node].lnk_rst); + goto put_lnk_rst; + } + + if (of_property_read_u32(child, "reg", + &cdns_phy->phys[node].mlane)) { + dev_err(dev, "%s: No \"reg\"-property.\n", + child->full_name); + ret = -EINVAL; + goto put_child; + } + + if (cdns_phy->phys[node].mlane != 0) { + dev_err(dev, + "%s: Driver supports only lane-0 as master lane.\n", + child->full_name); + ret = -EINVAL; + goto put_child; + } + + if (of_property_read_u32(child, "cdns,phy-type", + &cdns_phy->phys[node].phy_type)) { + dev_err(dev, "%s: No \"cdns,phy-type\"-property.\n", + child->full_name); + ret = -EINVAL; + goto put_child; + } + + cdns_phy->phys[node].num_lanes = DEFAULT_NUM_LANES; + of_property_read_u32(child, "cdns,num-lanes", + &cdns_phy->phys[node].num_lanes); + + if (cdns_phy->phys[node].phy_type == PHY_TYPE_DP) { + switch (cdns_phy->phys[node].num_lanes) { + case 1: + case 2: + case 4: + /* valid number of lanes */ + break; + default: + dev_err(dev, "unsupported number of lanes: %d\n", + cdns_phy->phys[node].num_lanes); + ret = -EINVAL; + goto put_child; + } + + cdns_phy->max_bit_rate = DEFAULT_MAX_BIT_RATE; + of_property_read_u32(child, "cdns,max-bit-rate", + &cdns_phy->max_bit_rate); + + switch (cdns_phy->max_bit_rate) { + case 1620: + case 2160: + case 2430: + case 2700: + case 3240: + case 4320: + case 5400: + case 8100: + /* valid bit rate */ + break; + default: + dev_err(dev, "unsupported max bit rate: %dMbps\n", + cdns_phy->max_bit_rate); + ret = -EINVAL; + goto put_child; + } + + /* DPTX registers */ + regs = platform_get_resource(pdev, IORESOURCE_MEM, 1); + cdns_phy->base = devm_ioremap_resource(&pdev->dev, + regs); + if (IS_ERR(cdns_phy->base)) { + ret = PTR_ERR(cdns_phy->base); + goto put_child; + } + + gphy = devm_phy_create(dev, child, + &cdns_torrent_phy_ops); + if (IS_ERR(gphy)) { + ret = PTR_ERR(gphy); + goto put_child; + } + + dev_info(dev, "%d lanes, max bit rate %d.%03d Gbps\n", + cdns_phy->phys[node].num_lanes, + cdns_phy->max_bit_rate / 1000, + cdns_phy->max_bit_rate % 1000); + } else { + dev_err(dev, "Driver supports only PHY_TYPE_DP\n"); + ret = -ENOTSUPP; + goto put_child; + } + cdns_phy->phys[node].phy = gphy; + phy_set_drvdata(gphy, &cdns_phy->phys[node]); + + node++; + } + cdns_phy->nsubnodes = node; + + ret = cdns_regmap_init_torrent_dp(cdns_phy, cdns_phy->sd_base, + cdns_phy->base, + data->block_offset_shift, + data->reg_offset_shift); + if (ret) + goto put_lnk_rst; + + ret = cdns_regfield_init(cdns_phy); + if (ret) + goto put_lnk_rst; + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(phy_provider)) { + ret = PTR_ERR(phy_provider); + goto put_lnk_rst; + } + + return 0; + +put_child: + node++; +put_lnk_rst: + for (i = 0; i < node; i++) + reset_control_put(cdns_phy->phys[i].lnk_rst); + of_node_put(child); + return ret; +} + +static int cdns_torrent_phy_remove(struct platform_device *pdev) +{ + struct cdns_torrent_phy *cdns_phy = platform_get_drvdata(pdev); + int i; + + reset_control_assert(cdns_phy->phy_rst); + for (i = 0; i < cdns_phy->nsubnodes; i++) { + reset_control_assert(cdns_phy->phys[i].lnk_rst); + reset_control_put(cdns_phy->phys[i].lnk_rst); + } + + return 0; +} + +static const struct cdns_torrent_data cdns_map_torrent = { + .block_offset_shift = 0x2, + .reg_offset_shift = 0x2, +}; + +static const struct cdns_torrent_data ti_j721e_map_torrent = { + .block_offset_shift = 0x0, + .reg_offset_shift = 0x1, +}; + +static const struct of_device_id cdns_torrent_phy_of_match[] = { + { + .compatible = "cdns,torrent-phy", + .data = &cdns_map_torrent, + }, + { + .compatible = "ti,j721e-serdes-10g", + .data = &ti_j721e_map_torrent, + }, + {} +}; +MODULE_DEVICE_TABLE(of, cdns_torrent_phy_of_match); + +static struct platform_driver cdns_torrent_phy_driver = { + .probe = cdns_torrent_phy_probe, + .remove = cdns_torrent_phy_remove, + .driver = { + .name = "cdns-torrent-phy", + .of_match_table = cdns_torrent_phy_of_match, + } +}; +module_platform_driver(cdns_torrent_phy_driver); + +MODULE_AUTHOR("Cadence Design Systems, Inc."); +MODULE_DESCRIPTION("Cadence Torrent PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/mediatek/phy-mtk-tphy.c b/drivers/phy/mediatek/phy-mtk-tphy.c index cb2ed3b25068..cdbcc49f7115 100644 --- a/drivers/phy/mediatek/phy-mtk-tphy.c +++ b/drivers/phy/mediatek/phy-mtk-tphy.c @@ -43,6 +43,8 @@ #define PA0_RG_USB20_INTR_EN BIT(5) #define U3P_USBPHYACR1 0x004 +#define PA1_RG_INTR_CAL GENMASK(23, 19) +#define PA1_RG_INTR_CAL_VAL(x) ((0x1f & (x)) << 19) #define PA1_RG_VRT_SEL GENMASK(14, 12) #define PA1_RG_VRT_SEL_VAL(x) ((0x7 & (x)) << 12) #define PA1_RG_TERM_SEL GENMASK(10, 8) @@ -60,6 +62,8 @@ #define U3P_USBPHYACR6 0x018 #define PA6_RG_U2_BC11_SW_EN BIT(23) #define PA6_RG_U2_OTG_VBUSCMP_EN BIT(20) +#define PA6_RG_U2_DISCTH GENMASK(7, 4) +#define PA6_RG_U2_DISCTH_VAL(x) ((0xf & (x)) << 4) #define PA6_RG_U2_SQTH GENMASK(3, 0) #define PA6_RG_U2_SQTH_VAL(x) (0xf & (x)) @@ -294,20 +298,21 @@ struct mtk_phy_instance { struct u2phy_banks u2_banks; struct u3phy_banks u3_banks; }; - struct clk *ref_clk; /* reference clock of anolog phy */ + struct clk *ref_clk; /* reference clock of (digital) phy */ + struct clk *da_ref_clk; /* reference clock of analog phy */ u32 index; u8 type; int eye_src; int eye_vrt; int eye_term; + int intr; + int discth; bool bc12_en; }; struct mtk_tphy { struct device *dev; void __iomem *sif_base; /* only shared sif */ - /* deprecated, use @ref_clk instead in phy instance */ - struct clk *u3phya_ref; /* reference clock of usb3 anolog phy */ const struct mtk_phy_pdata *pdata; struct mtk_phy_instance **phys; int nphys; @@ -850,9 +855,14 @@ static void phy_parse_property(struct mtk_tphy *tphy, &instance->eye_vrt); device_property_read_u32(dev, "mediatek,eye-term", &instance->eye_term); - dev_dbg(dev, "bc12:%d, src:%d, vrt:%d, term:%d\n", + device_property_read_u32(dev, "mediatek,intr", + &instance->intr); + device_property_read_u32(dev, "mediatek,discth", + &instance->discth); + dev_dbg(dev, "bc12:%d, src:%d, vrt:%d, term:%d, intr:%d, disc:%d\n", instance->bc12_en, instance->eye_src, - instance->eye_vrt, instance->eye_term); + instance->eye_vrt, instance->eye_term, + instance->intr, instance->discth); } static void u2_phy_props_set(struct mtk_tphy *tphy, @@ -888,6 +898,20 @@ static void u2_phy_props_set(struct mtk_tphy *tphy, tmp |= PA1_RG_TERM_SEL_VAL(instance->eye_term); writel(tmp, com + U3P_USBPHYACR1); } + + if (instance->intr) { + tmp = readl(com + U3P_USBPHYACR1); + tmp &= ~PA1_RG_INTR_CAL; + tmp |= PA1_RG_INTR_CAL_VAL(instance->intr); + writel(tmp, com + U3P_USBPHYACR1); + } + + if (instance->discth) { + tmp = readl(com + U3P_USBPHYACR6); + tmp &= ~PA6_RG_U2_DISCTH; + tmp |= PA6_RG_U2_DISCTH_VAL(instance->discth); + writel(tmp, com + U3P_USBPHYACR6); + } } static int mtk_phy_init(struct phy *phy) @@ -896,15 +920,16 @@ static int mtk_phy_init(struct phy *phy) struct mtk_tphy *tphy = dev_get_drvdata(phy->dev.parent); int ret; - ret = clk_prepare_enable(tphy->u3phya_ref); + ret = clk_prepare_enable(instance->ref_clk); if (ret) { - dev_err(tphy->dev, "failed to enable u3phya_ref\n"); + dev_err(tphy->dev, "failed to enable ref_clk\n"); return ret; } - ret = clk_prepare_enable(instance->ref_clk); + ret = clk_prepare_enable(instance->da_ref_clk); if (ret) { - dev_err(tphy->dev, "failed to enable ref_clk\n"); + dev_err(tphy->dev, "failed to enable da_ref\n"); + clk_disable_unprepare(instance->ref_clk); return ret; } @@ -967,7 +992,7 @@ static int mtk_phy_exit(struct phy *phy) u2_phy_instance_exit(tphy, instance); clk_disable_unprepare(instance->ref_clk); - clk_disable_unprepare(tphy->u3phya_ref); + clk_disable_unprepare(instance->da_ref_clk); return 0; } @@ -1102,11 +1127,6 @@ static int mtk_tphy_probe(struct platform_device *pdev) } } - /* it's deprecated, make it optional for backward compatibility */ - tphy->u3phya_ref = devm_clk_get_optional(dev, "u3phya_ref"); - if (IS_ERR(tphy->u3phya_ref)) - return PTR_ERR(tphy->u3phya_ref); - tphy->src_ref_clk = U3P_REF_CLK; tphy->src_coef = U3P_SLEW_RATE_COEF; /* update parameters of slew rate calibrate if exist */ @@ -1153,16 +1173,20 @@ static int mtk_tphy_probe(struct platform_device *pdev) phy_set_drvdata(phy, instance); port++; - /* if deprecated clock is provided, ignore instance's one */ - if (tphy->u3phya_ref) - continue; - - instance->ref_clk = devm_clk_get(&phy->dev, "ref"); + instance->ref_clk = devm_clk_get_optional(&phy->dev, "ref"); if (IS_ERR(instance->ref_clk)) { dev_err(dev, "failed to get ref_clk(id-%d)\n", port); retval = PTR_ERR(instance->ref_clk); goto put_child; } + + instance->da_ref_clk = + devm_clk_get_optional(&phy->dev, "da_ref"); + if (IS_ERR(instance->da_ref_clk)) { + dev_err(dev, "failed to get da_ref_clk(id-%d)\n", port); + retval = PTR_ERR(instance->da_ref_clk); + goto put_child; + } } provider = devm_of_phy_provider_register(dev, mtk_phy_xlate); diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig index e46824da29f6..98674ed094d9 100644 --- a/drivers/phy/qualcomm/Kconfig +++ b/drivers/phy/qualcomm/Kconfig @@ -91,3 +91,23 @@ config PHY_QCOM_USB_HSIC select GENERIC_PHY help Support for the USB HSIC ULPI compliant PHY on QCOM chipsets. + +config PHY_QCOM_USB_HS_28NM + tristate "Qualcomm 28nm High-Speed PHY" + depends on ARCH_QCOM || COMPILE_TEST + depends on EXTCON || !EXTCON # if EXTCON=m, this cannot be built-in + select GENERIC_PHY + help + Enable this to support the Qualcomm Synopsys DesignWare Core 28nm + High-Speed PHY driver. This driver supports the Hi-Speed PHY which + is usually paired with either the ChipIdea or Synopsys DWC3 USB + IPs on MSM SOCs. + +config PHY_QCOM_USB_SS + tristate "Qualcomm USB Super-Speed PHY driver" + depends on ARCH_QCOM || COMPILE_TEST + depends on EXTCON || !EXTCON # if EXTCON=m, this cannot be built-in + select GENERIC_PHY + help + Enable this to support the Super-Speed USB transceiver on various + Qualcomm chipsets. diff --git a/drivers/phy/qualcomm/Makefile b/drivers/phy/qualcomm/Makefile index 283251d6a5d9..1f14aeacbd70 100644 --- a/drivers/phy/qualcomm/Makefile +++ b/drivers/phy/qualcomm/Makefile @@ -10,3 +10,5 @@ obj-$(CONFIG_PHY_QCOM_UFS_14NM) += phy-qcom-ufs-qmp-14nm.o obj-$(CONFIG_PHY_QCOM_UFS_20NM) += phy-qcom-ufs-qmp-20nm.o obj-$(CONFIG_PHY_QCOM_USB_HS) += phy-qcom-usb-hs.o obj-$(CONFIG_PHY_QCOM_USB_HSIC) += phy-qcom-usb-hsic.o +obj-$(CONFIG_PHY_QCOM_USB_HS_28NM) += phy-qcom-usb-hs-28nm.o +obj-$(CONFIG_PHY_QCOM_USB_SS) += phy-qcom-usb-ss.o diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.c b/drivers/phy/qualcomm/phy-qcom-qmp.c index 7db2a94f7a99..c190406246ab 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp.c @@ -121,6 +121,11 @@ enum qphy_reg_layout { QPHY_PCS_LFPS_RXTERM_IRQ_STATUS, }; +static const unsigned int msm8996_ufsphy_regs_layout[] = { + [QPHY_START_CTRL] = 0x00, + [QPHY_PCS_READY_STATUS] = 0x168, +}; + static const unsigned int pciephy_regs_layout[] = { [QPHY_COM_SW_RESET] = 0x400, [QPHY_COM_POWER_DOWN_CONTROL] = 0x404, @@ -160,6 +165,18 @@ static const unsigned int qmp_v3_usb3phy_regs_layout[] = { [QPHY_PCS_LFPS_RXTERM_IRQ_STATUS] = 0x170, }; +static const unsigned int sdm845_qmp_pciephy_regs_layout[] = { + [QPHY_SW_RESET] = 0x00, + [QPHY_START_CTRL] = 0x08, + [QPHY_PCS_STATUS] = 0x174, +}; + +static const unsigned int sdm845_qhp_pciephy_regs_layout[] = { + [QPHY_SW_RESET] = 0x00, + [QPHY_START_CTRL] = 0x08, + [QPHY_PCS_STATUS] = 0x2ac, +}; + static const unsigned int sdm845_ufsphy_regs_layout[] = { [QPHY_START_CTRL] = 0x00, [QPHY_PCS_READY_STATUS] = 0x160, @@ -331,6 +348,75 @@ static const struct qmp_phy_init_tbl msm8998_pcie_pcs_tbl[] = { QMP_PHY_INIT_CFG(QPHY_V3_PCS_SIGDET_CNTRL, 0x03), }; +static const struct qmp_phy_init_tbl msm8996_ufs_serdes_tbl[] = { + QMP_PHY_INIT_CFG(QPHY_POWER_DOWN_CONTROL, 0x01), + QMP_PHY_INIT_CFG(QSERDES_COM_CMN_CONFIG, 0x0e), + QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0xd7), + QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x30), + QMP_PHY_INIT_CFG(QSERDES_COM_SYS_CLK_CTRL, 0x06), + QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08), + QMP_PHY_INIT_CFG(QSERDES_COM_BG_TIMER, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x05), + QMP_PHY_INIT_CFG(QSERDES_COM_CORECLK_DIV, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_COM_CORECLK_DIV_MODE1, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP_EN, 0x01), + QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_CTRL, 0x10), + QMP_PHY_INIT_CFG(QSERDES_COM_RESETSM_CNTRL, 0x20), + QMP_PHY_INIT_CFG(QSERDES_COM_CORE_CLK_EN, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP_CFG, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_TIMER1, 0xff), + QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_TIMER2, 0x3f), + QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_MAP, 0x54), + QMP_PHY_INIT_CFG(QSERDES_COM_SVS_MODE_CLK_SEL, 0x05), + QMP_PHY_INIT_CFG(QSERDES_COM_DEC_START_MODE0, 0x82), + QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_CP_CTRL_MODE0, 0x0b), + QMP_PHY_INIT_CFG(QSERDES_COM_PLL_RCTRL_MODE0, 0x16), + QMP_PHY_INIT_CFG(QSERDES_COM_PLL_CCTRL_MODE0, 0x28), + QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80), + QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE1_MODE0, 0x28), + QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE2_MODE0, 0x02), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP1_MODE0, 0xff), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP2_MODE0, 0x0c), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP3_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_DEC_START_MODE1, 0x98), + QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START1_MODE1, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START2_MODE1, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START3_MODE1, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_CP_CTRL_MODE1, 0x0b), + QMP_PHY_INIT_CFG(QSERDES_COM_PLL_RCTRL_MODE1, 0x16), + QMP_PHY_INIT_CFG(QSERDES_COM_PLL_CCTRL_MODE1, 0x28), + QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN0_MODE1, 0x80), + QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN1_MODE1, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE1_MODE1, 0xd6), + QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE2_MODE1, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP1_MODE1, 0x32), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP2_MODE1, 0x0f), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP3_MODE1, 0x00), +}; + +static const struct qmp_phy_init_tbl msm8996_ufs_tx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x45), + QMP_PHY_INIT_CFG(QSERDES_TX_LANE_MODE, 0x02), +}; + +static const struct qmp_phy_init_tbl msm8996_ufs_rx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_LVL, 0x24), + QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_CNTRL, 0x02), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_INTERFACE_MODE, 0x00), + QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x18), + QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_FASTLOCK_FO_GAIN, 0x0B), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_TERM_BW, 0x5b), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQ_GAIN1_LSB, 0xff), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQ_GAIN1_MSB, 0x3f), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQ_GAIN2_LSB, 0xff), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQ_GAIN2_MSB, 0x0f), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0E), +}; + static const struct qmp_phy_init_tbl msm8996_usb3_serdes_tbl[] = { QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0x14), QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08), @@ -481,6 +567,229 @@ static const struct qmp_phy_init_tbl ipq8074_pcie_pcs_tbl[] = { QMP_PHY_INIT_CFG_L(QPHY_START_CTRL, 0x3), }; +static const struct qmp_phy_init_tbl sdm845_qmp_pcie_serdes_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V3_COM_BIAS_EN_CLKBUFLR_EN, 0x14), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CLK_SELECT, 0x30), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_IVCO, 0x007), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CMN_CONFIG, 0x06), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP_EN, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_RESETSM_CNTRL, 0x20), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE_MAP, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE2_MODE0, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE1_MODE0, 0xc9), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE_TIMER1, 0xff), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE_TIMER2, 0x3f), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SVS_MODE_CLK_SEL, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CORE_CLK_EN, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CORECLK_DIV_MODE0, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CLK_EP_DIV, 0x19), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CLK_ENABLE1, 0x90), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_DEC_START_MODE0, 0x82), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START3_MODE0, 0x02), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START2_MODE0, 0xea), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START1_MODE0, 0xab), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP3_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP2_MODE0, 0x0d), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP1_MODE0, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_HSCLK_SEL, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CP_CTRL_MODE0, 0x06), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_RCTRL_MODE0, 0x16), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_CCTRL_MODE0, 0x36), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CMN_MODE, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CLK_SELECT, 0x33), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYS_CLK_CTRL, 0x02), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYSCLK_BUF_ENABLE, 0x06), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYSCLK_EN_SEL, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_INTEGLOOP_GAIN1_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_INTEGLOOP_GAIN0_MODE0, 0x3f), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_BG_TIMER, 0x09), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_EN_CENTER, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_PER1, 0x40), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_PER2, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_ADJ_PER1, 0x02), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_ADJ_PER2, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_STEP_SIZE1, 0x7e), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_STEP_SIZE2, 0x15), +}; + +static const struct qmp_phy_init_tbl sdm845_qmp_pcie_tx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V3_TX_RES_CODE_LANE_OFFSET_TX, 0x02), + QMP_PHY_INIT_CFG(QSERDES_V3_TX_RCV_DETECT_LVL_2, 0x12), + QMP_PHY_INIT_CFG(QSERDES_V3_TX_HIGHZ_DRVR_EN, 0x10), + QMP_PHY_INIT_CFG(QSERDES_V3_TX_LANE_MODE_1, 0x06), +}; + +static const struct qmp_phy_init_tbl sdm845_qmp_pcie_rx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_CNTRL, 0x03), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_ENABLES, 0x10), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_DEGLITCH_CNTRL, 0x14), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0e), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL3, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL4, 0x1a), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x4b), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SO_GAIN, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SO_GAIN_HALF, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1, 0x71), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_MODE_00, 0x59), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_MODE_01, 0x59), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_OFFSET_ADAPTOR_CNTRL2, 0x80), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_INTERFACE_MODE, 0x40), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_PI_CONTROLS, 0x71), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_FASTLOCK_COUNT_LOW, 0x40), +}; + +static const struct qmp_phy_init_tbl sdm845_qmp_pcie_pcs_tbl[] = { + QMP_PHY_INIT_CFG(QPHY_V3_PCS_ENDPOINT_REFCLK_DRIVE, 0x04), + + QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNTRL2, 0x83), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNT_VAL_L, 0x09), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNT_VAL_H_TOL, 0xa2), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_MAN_CODE, 0x40), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNTRL1, 0x02), + + QMP_PHY_INIT_CFG(QPHY_V3_PCS_OSC_DTCT_ACTIONS, 0x00), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_PWRUP_RESET_DLY_TIME_AUXCLK, 0x01), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_L1SS_WAKEUP_DLY_TIME_AUXCLK_MSB, 0x00), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_L1SS_WAKEUP_DLY_TIME_AUXCLK_LSB, 0x20), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_LP_WAKEUP_DLY_TIME_AUXCLK_MSB, 0x00), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_LP_WAKEUP_DLY_TIME_AUXCLK, 0x01), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_PLL_LOCK_CHK_DLY_TIME, 0x73), + + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RX_SIGDET_LVL, 0xbb), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_SIGDET_CNTRL, 0x03), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_REFGEN_REQ_CONFIG1, 0x0d), + + QMP_PHY_INIT_CFG(QPHY_V3_PCS_POWER_STATE_CONFIG4, 0x00), +}; + +static const struct qmp_phy_init_tbl sdm845_qmp_pcie_pcs_misc_tbl[] = { + QMP_PHY_INIT_CFG(QPHY_V3_PCS_MISC_OSC_DTCT_CONFIG2, 0x52), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_MISC_OSC_DTCT_MODE2_CONFIG2, 0x10), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_MISC_OSC_DTCT_MODE2_CONFIG4, 0x1a), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_MISC_OSC_DTCT_MODE2_CONFIG5, 0x06), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_MISC_PCIE_INT_AUX_CLK_CONFIG1, 0x00), +}; + +static const struct qmp_phy_init_tbl sdm845_qhp_pcie_serdes_tbl[] = { + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_SYSCLK_EN_SEL, 0x27), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_SSC_EN_CENTER, 0x01), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_SSC_PER1, 0x31), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_SSC_PER2, 0x01), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_SSC_STEP_SIZE1, 0xde), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_SSC_STEP_SIZE2, 0x07), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_SSC_STEP_SIZE1_MODE1, 0x4c), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_SSC_STEP_SIZE2_MODE1, 0x06), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_BIAS_EN_CKBUFLR_EN, 0x18), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_CLK_ENABLE1, 0xb0), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_LOCK_CMP1_MODE0, 0x8c), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_LOCK_CMP2_MODE0, 0x20), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_LOCK_CMP1_MODE1, 0x14), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_LOCK_CMP2_MODE1, 0x34), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_CP_CTRL_MODE0, 0x06), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_CP_CTRL_MODE1, 0x06), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_PLL_RCTRL_MODE0, 0x16), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_PLL_RCTRL_MODE1, 0x16), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_PLL_CCTRL_MODE0, 0x36), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_PLL_CCTRL_MODE1, 0x36), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_RESTRIM_CTRL2, 0x05), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_LOCK_CMP_EN, 0x42), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_DEC_START_MODE0, 0x82), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_DEC_START_MODE1, 0x68), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_DIV_FRAC_START1_MODE0, 0x55), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_DIV_FRAC_START2_MODE0, 0x55), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_DIV_FRAC_START3_MODE0, 0x03), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_DIV_FRAC_START1_MODE1, 0xab), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_DIV_FRAC_START2_MODE1, 0xaa), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_DIV_FRAC_START3_MODE1, 0x02), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_INTEGLOOP_GAIN0_MODE0, 0x3f), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_INTEGLOOP_GAIN0_MODE1, 0x3f), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_VCO_TUNE_MAP, 0x10), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_CLK_SELECT, 0x04), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_HSCLK_SEL1, 0x30), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_CORECLK_DIV, 0x04), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_CORE_CLK_EN, 0x73), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_CMN_CONFIG, 0x0c), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_SVS_MODE_CLK_SEL, 0x15), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_CORECLK_DIV_MODE1, 0x04), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_CMN_MODE, 0x01), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_VREGCLK_DIV1, 0x22), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_VREGCLK_DIV2, 0x00), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_BGV_TRIM, 0x20), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_COM_BG_CTRL, 0x07), +}; + +static const struct qmp_phy_init_tbl sdm845_qhp_pcie_tx_tbl[] = { + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_DRVR_CTRL0, 0x00), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_DRVR_TAP_EN, 0x0d), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_TX_BAND_MODE, 0x01), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_LANE_MODE, 0x1a), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_PARALLEL_RATE, 0x2f), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_CML_CTRL_MODE0, 0x09), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_CML_CTRL_MODE1, 0x09), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_CML_CTRL_MODE2, 0x1b), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_PREAMP_CTRL_MODE1, 0x01), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_PREAMP_CTRL_MODE2, 0x07), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_MIXER_CTRL_MODE0, 0x31), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_MIXER_CTRL_MODE1, 0x31), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_MIXER_CTRL_MODE2, 0x03), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_CTLE_THRESH_DFE, 0x02), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_CGA_THRESH_DFE, 0x00), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_RXENGINE_EN0, 0x12), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_CTLE_TRAIN_TIME, 0x25), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_CTLE_DFE_OVRLP_TIME, 0x00), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_DFE_REFRESH_TIME, 0x05), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_DFE_ENABLE_TIME, 0x01), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_VGA_GAIN, 0x26), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_DFE_GAIN, 0x12), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_EQ_GAIN, 0x04), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_OFFSET_GAIN, 0x04), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_PRE_GAIN, 0x09), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_EQ_INTVAL, 0x15), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_EDAC_INITVAL, 0x28), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_RXEQ_INITB0, 0x7f), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_RXEQ_INITB1, 0x07), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_RCVRDONE_THRESH1, 0x04), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_RXEQ_CTRL, 0x70), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_UCDR_FO_GAIN_MODE0, 0x8b), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_UCDR_FO_GAIN_MODE1, 0x08), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_UCDR_FO_GAIN_MODE2, 0x0a), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_UCDR_SO_GAIN_MODE0, 0x03), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_UCDR_SO_GAIN_MODE1, 0x04), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_UCDR_SO_GAIN_MODE2, 0x04), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_UCDR_SO_CONFIG, 0x0c), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_RX_BAND, 0x02), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_RX_RCVR_PATH1_MODE0, 0x5c), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_RX_RCVR_PATH1_MODE1, 0x3e), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_RX_RCVR_PATH1_MODE2, 0x3f), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_SIGDET_ENABLES, 0x01), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_SIGDET_CNTRL, 0xa0), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_SIGDET_DEGLITCH_CNTRL, 0x08), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_DCC_GAIN, 0x01), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_RX_EN_SIGNAL, 0xc3), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_PSM_RX_EN_CAL, 0x00), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_RX_MISC_CNTRL0, 0xbc), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_TS0_TIMER, 0x7f), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_DLL_HIGHDATARATE, 0x15), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_DRVR_CTRL1, 0x0c), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_DRVR_CTRL2, 0x0f), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_RX_RESETCODE_OFFSET, 0x04), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_VGA_INITVAL, 0x20), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_L0_RSM_START, 0x01), +}; + +static const struct qmp_phy_init_tbl sdm845_qhp_pcie_rx_tbl[] = { +}; + +static const struct qmp_phy_init_tbl sdm845_qhp_pcie_pcs_tbl[] = { + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_PHY_POWER_STATE_CONFIG, 0x3f), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_PHY_PCS_TX_RX_CONFIG, 0x50), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_PHY_TXMGN_MAIN_V0_M3P5DB, 0x19), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_PHY_TXMGN_POST_V0_M3P5DB, 0x07), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_PHY_TXMGN_MAIN_V0_M6DB, 0x17), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_PHY_TXMGN_POST_V0_M6DB, 0x09), + QMP_PHY_INIT_CFG(PCIE_GEN3_QHP_PHY_POWER_STATE_CONFIG5, 0x9f), +}; + static const struct qmp_phy_init_tbl qmp_v3_usb3_serdes_tbl[] = { QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_IVCO, 0x07), QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYSCLK_EN_SEL, 0x14), @@ -988,6 +1297,8 @@ struct qmp_phy_cfg { int rx_tbl_num; const struct qmp_phy_init_tbl *pcs_tbl; int pcs_tbl_num; + const struct qmp_phy_init_tbl *pcs_misc_tbl; + int pcs_misc_tbl_num; /* clock ids to be requested */ const char * const *clk_list; @@ -1122,10 +1433,18 @@ static const char * const msm8996_phy_clk_l[] = { "aux", "cfg_ahb", "ref", }; +static const char * const msm8996_ufs_phy_clk_l[] = { + "ref", +}; + static const char * const qmp_v3_phy_clk_l[] = { "aux", "cfg_ahb", "ref", "com_aux", }; +static const char * const sdm845_pciephy_clk_l[] = { + "aux", "cfg_ahb", "ref", "refgen", +}; + static const char * const sdm845_ufs_phy_clk_l[] = { "ref", "ref_aux", }; @@ -1139,6 +1458,10 @@ static const char * const msm8996_usb3phy_reset_l[] = { "phy", "common", }; +static const char * const sdm845_pciephy_reset_l[] = { + "phy", +}; + /* list of regulators */ static const char * const qmp_phy_vreg_l[] = { "vdda-phy", "vdda-pll", @@ -1175,6 +1498,31 @@ static const struct qmp_phy_cfg msm8996_pciephy_cfg = { .pwrdn_delay_max = POWER_DOWN_DELAY_US_MAX, }; +static const struct qmp_phy_cfg msm8996_ufs_cfg = { + .type = PHY_TYPE_UFS, + .nlanes = 1, + + .serdes_tbl = msm8996_ufs_serdes_tbl, + .serdes_tbl_num = ARRAY_SIZE(msm8996_ufs_serdes_tbl), + .tx_tbl = msm8996_ufs_tx_tbl, + .tx_tbl_num = ARRAY_SIZE(msm8996_ufs_tx_tbl), + .rx_tbl = msm8996_ufs_rx_tbl, + .rx_tbl_num = ARRAY_SIZE(msm8996_ufs_rx_tbl), + + .clk_list = msm8996_ufs_phy_clk_l, + .num_clks = ARRAY_SIZE(msm8996_ufs_phy_clk_l), + + .vreg_list = qmp_phy_vreg_l, + .num_vregs = ARRAY_SIZE(qmp_phy_vreg_l), + + .regs = msm8996_ufsphy_regs_layout, + + .start_ctrl = SERDES_START, + .pwrdn_ctrl = SW_PWRDN, + + .no_pcs_sw_reset = true, +}; + static const struct qmp_phy_cfg msm8996_usb3phy_cfg = { .type = PHY_TYPE_USB3, .nlanes = 1, @@ -1234,6 +1582,64 @@ static const struct qmp_phy_cfg ipq8074_pciephy_cfg = { .pwrdn_delay_max = 1005, /* us */ }; +static const struct qmp_phy_cfg sdm845_qmp_pciephy_cfg = { + .type = PHY_TYPE_PCIE, + .nlanes = 1, + + .serdes_tbl = sdm845_qmp_pcie_serdes_tbl, + .serdes_tbl_num = ARRAY_SIZE(sdm845_qmp_pcie_serdes_tbl), + .tx_tbl = sdm845_qmp_pcie_tx_tbl, + .tx_tbl_num = ARRAY_SIZE(sdm845_qmp_pcie_tx_tbl), + .rx_tbl = sdm845_qmp_pcie_rx_tbl, + .rx_tbl_num = ARRAY_SIZE(sdm845_qmp_pcie_rx_tbl), + .pcs_tbl = sdm845_qmp_pcie_pcs_tbl, + .pcs_tbl_num = ARRAY_SIZE(sdm845_qmp_pcie_pcs_tbl), + .pcs_misc_tbl = sdm845_qmp_pcie_pcs_misc_tbl, + .pcs_misc_tbl_num = ARRAY_SIZE(sdm845_qmp_pcie_pcs_misc_tbl), + .clk_list = sdm845_pciephy_clk_l, + .num_clks = ARRAY_SIZE(sdm845_pciephy_clk_l), + .reset_list = sdm845_pciephy_reset_l, + .num_resets = ARRAY_SIZE(sdm845_pciephy_reset_l), + .vreg_list = qmp_phy_vreg_l, + .num_vregs = ARRAY_SIZE(qmp_phy_vreg_l), + .regs = sdm845_qmp_pciephy_regs_layout, + + .start_ctrl = PCS_START | SERDES_START, + .pwrdn_ctrl = SW_PWRDN | REFCLK_DRV_DSBL, + + .has_pwrdn_delay = true, + .pwrdn_delay_min = 995, /* us */ + .pwrdn_delay_max = 1005, /* us */ +}; + +static const struct qmp_phy_cfg sdm845_qhp_pciephy_cfg = { + .type = PHY_TYPE_PCIE, + .nlanes = 1, + + .serdes_tbl = sdm845_qhp_pcie_serdes_tbl, + .serdes_tbl_num = ARRAY_SIZE(sdm845_qhp_pcie_serdes_tbl), + .tx_tbl = sdm845_qhp_pcie_tx_tbl, + .tx_tbl_num = ARRAY_SIZE(sdm845_qhp_pcie_tx_tbl), + .rx_tbl = sdm845_qhp_pcie_rx_tbl, + .rx_tbl_num = ARRAY_SIZE(sdm845_qhp_pcie_rx_tbl), + .pcs_tbl = sdm845_qhp_pcie_pcs_tbl, + .pcs_tbl_num = ARRAY_SIZE(sdm845_qhp_pcie_pcs_tbl), + .clk_list = sdm845_pciephy_clk_l, + .num_clks = ARRAY_SIZE(sdm845_pciephy_clk_l), + .reset_list = sdm845_pciephy_reset_l, + .num_resets = ARRAY_SIZE(sdm845_pciephy_reset_l), + .vreg_list = qmp_phy_vreg_l, + .num_vregs = ARRAY_SIZE(qmp_phy_vreg_l), + .regs = sdm845_qhp_pciephy_regs_layout, + + .start_ctrl = PCS_START | SERDES_START, + .pwrdn_ctrl = SW_PWRDN | REFCLK_DRV_DSBL, + + .has_pwrdn_delay = true, + .pwrdn_delay_min = 995, /* us */ + .pwrdn_delay_max = 1005, /* us */ +}; + static const struct qmp_phy_cfg qmp_v3_usb3phy_cfg = { .type = PHY_TYPE_USB3, .nlanes = 1, @@ -1563,6 +1969,7 @@ static int qcom_qmp_phy_enable(struct phy *phy) void __iomem *tx = qphy->tx; void __iomem *rx = qphy->rx; void __iomem *pcs = qphy->pcs; + void __iomem *pcs_misc = qphy->pcs_misc; void __iomem *dp_com = qmp->dp_com; void __iomem *status; unsigned int mask, val, ready; @@ -1633,6 +2040,9 @@ static int qcom_qmp_phy_enable(struct phy *phy) if (ret) goto err_lane_rst; + qcom_qmp_phy_configure(pcs_misc, cfg->regs, cfg->pcs_misc_tbl, + cfg->pcs_misc_tbl_num); + /* * Pull out PHY from POWER DOWN state. * This is active low enable signal to power-down PHY. @@ -1967,7 +2377,7 @@ static const struct phy_ops qcom_qmp_phy_gen_ops = { .owner = THIS_MODULE, }; -static const struct phy_ops qcom_qmp_ufs_ops = { +static const struct phy_ops qcom_qmp_pcie_ufs_ops = { .power_on = qcom_qmp_phy_enable, .power_off = qcom_qmp_phy_disable, .set_mode = qcom_qmp_phy_set_mode, @@ -2067,8 +2477,8 @@ int qcom_qmp_phy_create(struct device *dev, struct device_node *np, int id) } } - if (qmp->cfg->type == PHY_TYPE_UFS) - ops = &qcom_qmp_ufs_ops; + if (qmp->cfg->type == PHY_TYPE_UFS || qmp->cfg->type == PHY_TYPE_PCIE) + ops = &qcom_qmp_pcie_ufs_ops; generic_phy = devm_phy_create(dev, np, ops); if (IS_ERR(generic_phy)) { @@ -2091,6 +2501,9 @@ static const struct of_device_id qcom_qmp_phy_of_match_table[] = { .compatible = "qcom,msm8996-qmp-pcie-phy", .data = &msm8996_pciephy_cfg, }, { + .compatible = "qcom,msm8996-qmp-ufs-phy", + .data = &msm8996_ufs_cfg, + }, { .compatible = "qcom,msm8996-qmp-usb3-phy", .data = &msm8996_usb3phy_cfg, }, { @@ -2103,6 +2516,12 @@ static const struct of_device_id qcom_qmp_phy_of_match_table[] = { .compatible = "qcom,ipq8074-qmp-pcie-phy", .data = &ipq8074_pciephy_cfg, }, { + .compatible = "qcom,sdm845-qhp-pcie-phy", + .data = &sdm845_qhp_pciephy_cfg, + }, { + .compatible = "qcom,sdm845-qmp-pcie-phy", + .data = &sdm845_qmp_pciephy_cfg, + }, { .compatible = "qcom,sdm845-qmp-usb3-phy", .data = &qmp_v3_usb3phy_cfg, }, { diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.h b/drivers/phy/qualcomm/phy-qcom-qmp.h index 90f793c2293d..dece0e67704b 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp.h +++ b/drivers/phy/qualcomm/phy-qcom-qmp.h @@ -409,4 +409,118 @@ #define QPHY_V4_TX_MID_TERM_CTRL1 0x1d8 #define QPHY_V4_MULTI_LANE_CTRL1 0x1e0 +/* PCIE GEN3 COM registers */ +#define PCIE_GEN3_QHP_COM_SSC_EN_CENTER 0x14 +#define PCIE_GEN3_QHP_COM_SSC_PER1 0x20 +#define PCIE_GEN3_QHP_COM_SSC_PER2 0x24 +#define PCIE_GEN3_QHP_COM_SSC_STEP_SIZE1 0x28 +#define PCIE_GEN3_QHP_COM_SSC_STEP_SIZE2 0x2c +#define PCIE_GEN3_QHP_COM_SSC_STEP_SIZE1_MODE1 0x34 +#define PCIE_GEN3_QHP_COM_SSC_STEP_SIZE2_MODE1 0x38 +#define PCIE_GEN3_QHP_COM_BIAS_EN_CKBUFLR_EN 0x54 +#define PCIE_GEN3_QHP_COM_CLK_ENABLE1 0x58 +#define PCIE_GEN3_QHP_COM_LOCK_CMP1_MODE0 0x6c +#define PCIE_GEN3_QHP_COM_LOCK_CMP2_MODE0 0x70 +#define PCIE_GEN3_QHP_COM_LOCK_CMP1_MODE1 0x78 +#define PCIE_GEN3_QHP_COM_LOCK_CMP2_MODE1 0x7c +#define PCIE_GEN3_QHP_COM_BGV_TRIM 0x98 +#define PCIE_GEN3_QHP_COM_CP_CTRL_MODE0 0xb4 +#define PCIE_GEN3_QHP_COM_CP_CTRL_MODE1 0xb8 +#define PCIE_GEN3_QHP_COM_PLL_RCTRL_MODE0 0xc0 +#define PCIE_GEN3_QHP_COM_PLL_RCTRL_MODE1 0xc4 +#define PCIE_GEN3_QHP_COM_PLL_CCTRL_MODE0 0xcc +#define PCIE_GEN3_QHP_COM_PLL_CCTRL_MODE1 0xd0 +#define PCIE_GEN3_QHP_COM_SYSCLK_EN_SEL 0xdc +#define PCIE_GEN3_QHP_COM_RESTRIM_CTRL2 0xf0 +#define PCIE_GEN3_QHP_COM_LOCK_CMP_EN 0xf8 +#define PCIE_GEN3_QHP_COM_DEC_START_MODE0 0x100 +#define PCIE_GEN3_QHP_COM_DEC_START_MODE1 0x108 +#define PCIE_GEN3_QHP_COM_DIV_FRAC_START1_MODE0 0x11c +#define PCIE_GEN3_QHP_COM_DIV_FRAC_START2_MODE0 0x120 +#define PCIE_GEN3_QHP_COM_DIV_FRAC_START3_MODE0 0x124 +#define PCIE_GEN3_QHP_COM_DIV_FRAC_START1_MODE1 0x128 +#define PCIE_GEN3_QHP_COM_DIV_FRAC_START2_MODE1 0x12c +#define PCIE_GEN3_QHP_COM_DIV_FRAC_START3_MODE1 0x130 +#define PCIE_GEN3_QHP_COM_INTEGLOOP_GAIN0_MODE0 0x150 +#define PCIE_GEN3_QHP_COM_INTEGLOOP_GAIN0_MODE1 0x158 +#define PCIE_GEN3_QHP_COM_VCO_TUNE_MAP 0x178 +#define PCIE_GEN3_QHP_COM_BG_CTRL 0x1c8 +#define PCIE_GEN3_QHP_COM_CLK_SELECT 0x1cc +#define PCIE_GEN3_QHP_COM_HSCLK_SEL1 0x1d0 +#define PCIE_GEN3_QHP_COM_CORECLK_DIV 0x1e0 +#define PCIE_GEN3_QHP_COM_CORE_CLK_EN 0x1e8 +#define PCIE_GEN3_QHP_COM_CMN_CONFIG 0x1f0 +#define PCIE_GEN3_QHP_COM_SVS_MODE_CLK_SEL 0x1fc +#define PCIE_GEN3_QHP_COM_CORECLK_DIV_MODE1 0x21c +#define PCIE_GEN3_QHP_COM_CMN_MODE 0x224 +#define PCIE_GEN3_QHP_COM_VREGCLK_DIV1 0x228 +#define PCIE_GEN3_QHP_COM_VREGCLK_DIV2 0x22c + +/* PCIE GEN3 QHP Lane registers */ +#define PCIE_GEN3_QHP_L0_DRVR_CTRL0 0xc +#define PCIE_GEN3_QHP_L0_DRVR_CTRL1 0x10 +#define PCIE_GEN3_QHP_L0_DRVR_CTRL2 0x14 +#define PCIE_GEN3_QHP_L0_DRVR_TAP_EN 0x18 +#define PCIE_GEN3_QHP_L0_TX_BAND_MODE 0x60 +#define PCIE_GEN3_QHP_L0_LANE_MODE 0x64 +#define PCIE_GEN3_QHP_L0_PARALLEL_RATE 0x7c +#define PCIE_GEN3_QHP_L0_CML_CTRL_MODE0 0xc0 +#define PCIE_GEN3_QHP_L0_CML_CTRL_MODE1 0xc4 +#define PCIE_GEN3_QHP_L0_CML_CTRL_MODE2 0xc8 +#define PCIE_GEN3_QHP_L0_PREAMP_CTRL_MODE1 0xd0 +#define PCIE_GEN3_QHP_L0_PREAMP_CTRL_MODE2 0xd4 +#define PCIE_GEN3_QHP_L0_MIXER_CTRL_MODE0 0xd8 +#define PCIE_GEN3_QHP_L0_MIXER_CTRL_MODE1 0xdc +#define PCIE_GEN3_QHP_L0_MIXER_CTRL_MODE2 0xe0 +#define PCIE_GEN3_QHP_L0_CTLE_THRESH_DFE 0xfc +#define PCIE_GEN3_QHP_L0_CGA_THRESH_DFE 0x100 +#define PCIE_GEN3_QHP_L0_RXENGINE_EN0 0x108 +#define PCIE_GEN3_QHP_L0_CTLE_TRAIN_TIME 0x114 +#define PCIE_GEN3_QHP_L0_CTLE_DFE_OVRLP_TIME 0x118 +#define PCIE_GEN3_QHP_L0_DFE_REFRESH_TIME 0x11c +#define PCIE_GEN3_QHP_L0_DFE_ENABLE_TIME 0x120 +#define PCIE_GEN3_QHP_L0_VGA_GAIN 0x124 +#define PCIE_GEN3_QHP_L0_DFE_GAIN 0x128 +#define PCIE_GEN3_QHP_L0_EQ_GAIN 0x130 +#define PCIE_GEN3_QHP_L0_OFFSET_GAIN 0x134 +#define PCIE_GEN3_QHP_L0_PRE_GAIN 0x138 +#define PCIE_GEN3_QHP_L0_VGA_INITVAL 0x13c +#define PCIE_GEN3_QHP_L0_EQ_INTVAL 0x154 +#define PCIE_GEN3_QHP_L0_EDAC_INITVAL 0x160 +#define PCIE_GEN3_QHP_L0_RXEQ_INITB0 0x168 +#define PCIE_GEN3_QHP_L0_RXEQ_INITB1 0x16c +#define PCIE_GEN3_QHP_L0_RCVRDONE_THRESH1 0x178 +#define PCIE_GEN3_QHP_L0_RXEQ_CTRL 0x180 +#define PCIE_GEN3_QHP_L0_UCDR_FO_GAIN_MODE0 0x184 +#define PCIE_GEN3_QHP_L0_UCDR_FO_GAIN_MODE1 0x188 +#define PCIE_GEN3_QHP_L0_UCDR_FO_GAIN_MODE2 0x18c +#define PCIE_GEN3_QHP_L0_UCDR_SO_GAIN_MODE0 0x190 +#define PCIE_GEN3_QHP_L0_UCDR_SO_GAIN_MODE1 0x194 +#define PCIE_GEN3_QHP_L0_UCDR_SO_GAIN_MODE2 0x198 +#define PCIE_GEN3_QHP_L0_UCDR_SO_CONFIG 0x19c +#define PCIE_GEN3_QHP_L0_RX_BAND 0x1a4 +#define PCIE_GEN3_QHP_L0_RX_RCVR_PATH1_MODE0 0x1c0 +#define PCIE_GEN3_QHP_L0_RX_RCVR_PATH1_MODE1 0x1c4 +#define PCIE_GEN3_QHP_L0_RX_RCVR_PATH1_MODE2 0x1c8 +#define PCIE_GEN3_QHP_L0_SIGDET_ENABLES 0x230 +#define PCIE_GEN3_QHP_L0_SIGDET_CNTRL 0x234 +#define PCIE_GEN3_QHP_L0_SIGDET_DEGLITCH_CNTRL 0x238 +#define PCIE_GEN3_QHP_L0_DCC_GAIN 0x2a4 +#define PCIE_GEN3_QHP_L0_RSM_START 0x2a8 +#define PCIE_GEN3_QHP_L0_RX_EN_SIGNAL 0x2ac +#define PCIE_GEN3_QHP_L0_PSM_RX_EN_CAL 0x2b0 +#define PCIE_GEN3_QHP_L0_RX_MISC_CNTRL0 0x2b8 +#define PCIE_GEN3_QHP_L0_TS0_TIMER 0x2c0 +#define PCIE_GEN3_QHP_L0_DLL_HIGHDATARATE 0x2c4 +#define PCIE_GEN3_QHP_L0_RX_RESETCODE_OFFSET 0x2cc + +/* PCIE GEN3 PCS registers */ +#define PCIE_GEN3_QHP_PHY_TXMGN_MAIN_V0_M3P5DB 0x2c +#define PCIE_GEN3_QHP_PHY_TXMGN_POST_V0_M3P5DB 0x40 +#define PCIE_GEN3_QHP_PHY_TXMGN_MAIN_V0_M6DB 0x54 +#define PCIE_GEN3_QHP_PHY_TXMGN_POST_V0_M6DB 0x68 +#define PCIE_GEN3_QHP_PHY_POWER_STATE_CONFIG 0x15c +#define PCIE_GEN3_QHP_PHY_POWER_STATE_CONFIG5 0x16c +#define PCIE_GEN3_QHP_PHY_PCS_TX_RX_CONFIG 0x174 + #endif diff --git a/drivers/phy/qualcomm/phy-qcom-qusb2.c b/drivers/phy/qualcomm/phy-qcom-qusb2.c index bf94a52d3087..3708d43b7508 100644 --- a/drivers/phy/qualcomm/phy-qcom-qusb2.c +++ b/drivers/phy/qualcomm/phy-qcom-qusb2.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (c) 2017, The Linux Foundation. All rights reserved. + * Copyright (c) 2017, 2019, The Linux Foundation. All rights reserved. */ #include <linux/clk.h> @@ -66,6 +66,14 @@ #define IMP_RES_OFFSET_MASK GENMASK(5, 0) #define IMP_RES_OFFSET_SHIFT 0x0 +/* QUSB2PHY_PLL_BIAS_CONTROL_2 register bits */ +#define BIAS_CTRL2_RES_OFFSET_MASK GENMASK(5, 0) +#define BIAS_CTRL2_RES_OFFSET_SHIFT 0x0 + +/* QUSB2PHY_CHG_CONTROL_2 register bits */ +#define CHG_CTRL2_OFFSET_MASK GENMASK(5, 4) +#define CHG_CTRL2_OFFSET_SHIFT 0x4 + /* QUSB2PHY_PORT_TUNE1 register bits */ #define HSTX_TRIM_MASK GENMASK(7, 4) #define HSTX_TRIM_SHIFT 0x4 @@ -73,6 +81,10 @@ #define PREEMPHASIS_EN_MASK GENMASK(1, 0) #define PREEMPHASIS_EN_SHIFT 0x0 +/* QUSB2PHY_PORT_TUNE2 register bits */ +#define HSDISC_TRIM_MASK GENMASK(1, 0) +#define HSDISC_TRIM_SHIFT 0x0 + #define QUSB2PHY_PLL_ANALOG_CONTROLS_TWO 0x04 #define QUSB2PHY_PLL_CLOCK_INVERTERS 0x18c #define QUSB2PHY_PLL_CMODE 0x2c @@ -177,7 +189,7 @@ static const struct qusb2_phy_init_tbl msm8998_init_tbl[] = { QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_DIGITAL_TIMERS_TWO, 0x19), }; -static const unsigned int sdm845_regs_layout[] = { +static const unsigned int qusb2_v2_regs_layout[] = { [QUSB2PHY_PLL_CORE_INPUT_OVERRIDE] = 0xa8, [QUSB2PHY_PLL_STATUS] = 0x1a0, [QUSB2PHY_PORT_TUNE1] = 0x240, @@ -191,7 +203,7 @@ static const unsigned int sdm845_regs_layout[] = { [QUSB2PHY_INTR_CTRL] = 0x230, }; -static const struct qusb2_phy_init_tbl sdm845_init_tbl[] = { +static const struct qusb2_phy_init_tbl qusb2_v2_init_tbl[] = { QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_ANALOG_CONTROLS_TWO, 0x03), QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_CLOCK_INVERTERS, 0x7c), QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_CMODE, 0x80), @@ -258,10 +270,10 @@ static const struct qusb2_phy_cfg msm8998_phy_cfg = { .update_tune1_with_efuse = true, }; -static const struct qusb2_phy_cfg sdm845_phy_cfg = { - .tbl = sdm845_init_tbl, - .tbl_num = ARRAY_SIZE(sdm845_init_tbl), - .regs = sdm845_regs_layout, +static const struct qusb2_phy_cfg qusb2_v2_phy_cfg = { + .tbl = qusb2_v2_init_tbl, + .tbl_num = ARRAY_SIZE(qusb2_v2_init_tbl), + .regs = qusb2_v2_regs_layout, .disable_ctrl = (PWR_CTRL1_VREF_SUPPLY_TRIM | PWR_CTRL1_CLAMP_N_EN | POWER_DOWN), @@ -277,6 +289,34 @@ static const char * const qusb2_phy_vreg_names[] = { #define QUSB2_NUM_VREGS ARRAY_SIZE(qusb2_phy_vreg_names) +/* struct override_param - structure holding qusb2 v2 phy overriding param + * set override true if the device tree property exists and read and assign + * to value + */ +struct override_param { + bool override; + u8 value; +}; + +/*struct override_params - structure holding qusb2 v2 phy overriding params + * @imp_res_offset: rescode offset to be updated in IMP_CTRL1 register + * @hstx_trim: HSTX_TRIM to be updated in TUNE1 register + * @preemphasis: Amplitude Pre-Emphasis to be updated in TUNE1 register + * @preemphasis_width: half/full-width Pre-Emphasis updated via TUNE1 + * @bias_ctrl: bias ctrl to be updated in BIAS_CONTROL_2 register + * @charge_ctrl: charge ctrl to be updated in CHG_CTRL2 register + * @hsdisc_trim: disconnect threshold to be updated in TUNE2 register + */ +struct override_params { + struct override_param imp_res_offset; + struct override_param hstx_trim; + struct override_param preemphasis; + struct override_param preemphasis_width; + struct override_param bias_ctrl; + struct override_param charge_ctrl; + struct override_param hsdisc_trim; +}; + /** * struct qusb2_phy - structure holding qusb2 phy attributes * @@ -292,14 +332,7 @@ static const char * const qusb2_phy_vreg_names[] = { * @tcsr: TCSR syscon register map * @cell: nvmem cell containing phy tuning value * - * @override_imp_res_offset: PHY should use different rescode offset - * @imp_res_offset_value: rescode offset to be updated in IMP_CTRL1 register - * @override_hstx_trim: PHY should use different HSTX o/p current value - * @hstx_trim_value: HSTX_TRIM value to be updated in TUNE1 register - * @override_preemphasis: PHY should use different pre-amphasis amplitude - * @preemphasis_level: Amplitude Pre-Emphasis to be updated in TUNE1 register - * @override_preemphasis_width: PHY should use different pre-emphasis duration - * @preemphasis_width: half/full-width Pre-Emphasis updated via TUNE1 + * @overrides: pointer to structure for all overriding tuning params * * @cfg: phy config data * @has_se_clk_scheme: indicate if PHY has single-ended ref clock scheme @@ -319,14 +352,7 @@ struct qusb2_phy { struct regmap *tcsr; struct nvmem_cell *cell; - bool override_imp_res_offset; - u8 imp_res_offset_value; - bool override_hstx_trim; - u8 hstx_trim_value; - bool override_preemphasis; - u8 preemphasis_level; - bool override_preemphasis_width; - u8 preemphasis_width; + struct override_params overrides; const struct qusb2_phy_cfg *cfg; bool has_se_clk_scheme; @@ -394,24 +420,35 @@ void qcom_qusb2_phy_configure(void __iomem *base, static void qusb2_phy_override_phy_params(struct qusb2_phy *qphy) { const struct qusb2_phy_cfg *cfg = qphy->cfg; + struct override_params *or = &qphy->overrides; - if (qphy->override_imp_res_offset) + if (or->imp_res_offset.override) qusb2_write_mask(qphy->base, QUSB2PHY_IMP_CTRL1, - qphy->imp_res_offset_value << IMP_RES_OFFSET_SHIFT, + or->imp_res_offset.value << IMP_RES_OFFSET_SHIFT, IMP_RES_OFFSET_MASK); - if (qphy->override_hstx_trim) + if (or->bias_ctrl.override) + qusb2_write_mask(qphy->base, QUSB2PHY_PLL_BIAS_CONTROL_2, + or->bias_ctrl.value << BIAS_CTRL2_RES_OFFSET_SHIFT, + BIAS_CTRL2_RES_OFFSET_MASK); + + if (or->charge_ctrl.override) + qusb2_write_mask(qphy->base, QUSB2PHY_CHG_CTRL2, + or->charge_ctrl.value << CHG_CTRL2_OFFSET_SHIFT, + CHG_CTRL2_OFFSET_MASK); + + if (or->hstx_trim.override) qusb2_write_mask(qphy->base, cfg->regs[QUSB2PHY_PORT_TUNE1], - qphy->hstx_trim_value << HSTX_TRIM_SHIFT, + or->hstx_trim.value << HSTX_TRIM_SHIFT, HSTX_TRIM_MASK); - if (qphy->override_preemphasis) + if (or->preemphasis.override) qusb2_write_mask(qphy->base, cfg->regs[QUSB2PHY_PORT_TUNE1], - qphy->preemphasis_level << PREEMPHASIS_EN_SHIFT, + or->preemphasis.value << PREEMPHASIS_EN_SHIFT, PREEMPHASIS_EN_MASK); - if (qphy->override_preemphasis_width) { - if (qphy->preemphasis_width == + if (or->preemphasis_width.override) { + if (or->preemphasis_width.value == QUSB2_V2_PREEMPHASIS_WIDTH_HALF_BIT) qusb2_setbits(qphy->base, cfg->regs[QUSB2PHY_PORT_TUNE1], @@ -421,6 +458,11 @@ static void qusb2_phy_override_phy_params(struct qusb2_phy *qphy) cfg->regs[QUSB2PHY_PORT_TUNE1], PREEMPH_WIDTH_HALF_BIT); } + + if (or->hsdisc_trim.override) + qusb2_write_mask(qphy->base, cfg->regs[QUSB2PHY_PORT_TUNE2], + or->hsdisc_trim.value << HSDISC_TRIM_SHIFT, + HSDISC_TRIM_MASK); } /* @@ -774,8 +816,8 @@ static const struct of_device_id qusb2_phy_of_match_table[] = { .compatible = "qcom,msm8998-qusb2-phy", .data = &msm8998_phy_cfg, }, { - .compatible = "qcom,sdm845-qusb2-phy", - .data = &sdm845_phy_cfg, + .compatible = "qcom,qusb2-v2-phy", + .data = &qusb2_v2_phy_cfg, }, { }, }; @@ -796,10 +838,12 @@ static int qusb2_phy_probe(struct platform_device *pdev) int ret, i; int num; u32 value; + struct override_params *or; qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL); if (!qphy) return -ENOMEM; + or = &qphy->overrides; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); qphy->base = devm_ioremap_resource(dev, res); @@ -864,26 +908,44 @@ static int qusb2_phy_probe(struct platform_device *pdev) if (!of_property_read_u32(dev->of_node, "qcom,imp-res-offset-value", &value)) { - qphy->imp_res_offset_value = (u8)value; - qphy->override_imp_res_offset = true; + or->imp_res_offset.value = (u8)value; + or->imp_res_offset.override = true; + } + + if (!of_property_read_u32(dev->of_node, "qcom,bias-ctrl-value", + &value)) { + or->bias_ctrl.value = (u8)value; + or->bias_ctrl.override = true; + } + + if (!of_property_read_u32(dev->of_node, "qcom,charge-ctrl-value", + &value)) { + or->charge_ctrl.value = (u8)value; + or->charge_ctrl.override = true; } if (!of_property_read_u32(dev->of_node, "qcom,hstx-trim-value", &value)) { - qphy->hstx_trim_value = (u8)value; - qphy->override_hstx_trim = true; + or->hstx_trim.value = (u8)value; + or->hstx_trim.override = true; } if (!of_property_read_u32(dev->of_node, "qcom,preemphasis-level", &value)) { - qphy->preemphasis_level = (u8)value; - qphy->override_preemphasis = true; + or->preemphasis.value = (u8)value; + or->preemphasis.override = true; } if (!of_property_read_u32(dev->of_node, "qcom,preemphasis-width", &value)) { - qphy->preemphasis_width = (u8)value; - qphy->override_preemphasis_width = true; + or->preemphasis_width.value = (u8)value; + or->preemphasis_width.override = true; + } + + if (!of_property_read_u32(dev->of_node, "qcom,hsdisc-trim-value", + &value)) { + or->hsdisc_trim.value = (u8)value; + or->hsdisc_trim.override = true; } pm_runtime_set_active(dev); diff --git a/drivers/phy/qualcomm/phy-qcom-usb-hs-28nm.c b/drivers/phy/qualcomm/phy-qcom-usb-hs-28nm.c new file mode 100644 index 000000000000..d998e65c89c8 --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-usb-hs-28nm.c @@ -0,0 +1,415 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2009-2018, Linux Foundation. All rights reserved. + * Copyright (c) 2018-2020, Linaro Limited + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/slab.h> + +/* PHY register and bit definitions */ +#define PHY_CTRL_COMMON0 0x078 +#define SIDDQ BIT(2) +#define PHY_IRQ_CMD 0x0d0 +#define PHY_INTR_MASK0 0x0d4 +#define PHY_INTR_CLEAR0 0x0dc +#define DPDM_MASK 0x1e +#define DP_1_0 BIT(4) +#define DP_0_1 BIT(3) +#define DM_1_0 BIT(2) +#define DM_0_1 BIT(1) + +enum hsphy_voltage { + VOL_NONE, + VOL_MIN, + VOL_MAX, + VOL_NUM, +}; + +enum hsphy_vreg { + VDD, + VDDA_1P8, + VDDA_3P3, + VREG_NUM, +}; + +struct hsphy_init_seq { + int offset; + int val; + int delay; +}; + +struct hsphy_data { + const struct hsphy_init_seq *init_seq; + unsigned int init_seq_num; +}; + +struct hsphy_priv { + void __iomem *base; + struct clk_bulk_data *clks; + int num_clks; + struct reset_control *phy_reset; + struct reset_control *por_reset; + struct regulator_bulk_data vregs[VREG_NUM]; + const struct hsphy_data *data; + enum phy_mode mode; +}; + +static int qcom_snps_hsphy_set_mode(struct phy *phy, enum phy_mode mode, + int submode) +{ + struct hsphy_priv *priv = phy_get_drvdata(phy); + + priv->mode = PHY_MODE_INVALID; + + if (mode > 0) + priv->mode = mode; + + return 0; +} + +static void qcom_snps_hsphy_enable_hv_interrupts(struct hsphy_priv *priv) +{ + u32 val; + + /* Clear any existing interrupts before enabling the interrupts */ + val = readb(priv->base + PHY_INTR_CLEAR0); + val |= DPDM_MASK; + writeb(val, priv->base + PHY_INTR_CLEAR0); + + writeb(0x0, priv->base + PHY_IRQ_CMD); + usleep_range(200, 220); + writeb(0x1, priv->base + PHY_IRQ_CMD); + + /* Make sure the interrupts are cleared */ + usleep_range(200, 220); + + val = readb(priv->base + PHY_INTR_MASK0); + switch (priv->mode) { + case PHY_MODE_USB_HOST_HS: + case PHY_MODE_USB_HOST_FS: + case PHY_MODE_USB_DEVICE_HS: + case PHY_MODE_USB_DEVICE_FS: + val |= DP_1_0 | DM_0_1; + break; + case PHY_MODE_USB_HOST_LS: + case PHY_MODE_USB_DEVICE_LS: + val |= DP_0_1 | DM_1_0; + break; + default: + /* No device connected */ + val |= DP_0_1 | DM_0_1; + break; + } + writeb(val, priv->base + PHY_INTR_MASK0); +} + +static void qcom_snps_hsphy_disable_hv_interrupts(struct hsphy_priv *priv) +{ + u32 val; + + val = readb(priv->base + PHY_INTR_MASK0); + val &= ~DPDM_MASK; + writeb(val, priv->base + PHY_INTR_MASK0); + + /* Clear any pending interrupts */ + val = readb(priv->base + PHY_INTR_CLEAR0); + val |= DPDM_MASK; + writeb(val, priv->base + PHY_INTR_CLEAR0); + + writeb(0x0, priv->base + PHY_IRQ_CMD); + usleep_range(200, 220); + + writeb(0x1, priv->base + PHY_IRQ_CMD); + usleep_range(200, 220); +} + +static void qcom_snps_hsphy_enter_retention(struct hsphy_priv *priv) +{ + u32 val; + + val = readb(priv->base + PHY_CTRL_COMMON0); + val |= SIDDQ; + writeb(val, priv->base + PHY_CTRL_COMMON0); +} + +static void qcom_snps_hsphy_exit_retention(struct hsphy_priv *priv) +{ + u32 val; + + val = readb(priv->base + PHY_CTRL_COMMON0); + val &= ~SIDDQ; + writeb(val, priv->base + PHY_CTRL_COMMON0); +} + +static int qcom_snps_hsphy_power_on(struct phy *phy) +{ + struct hsphy_priv *priv = phy_get_drvdata(phy); + int ret; + + ret = regulator_bulk_enable(VREG_NUM, priv->vregs); + if (ret) + return ret; + ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks); + if (ret) + goto err_disable_regulator; + qcom_snps_hsphy_disable_hv_interrupts(priv); + qcom_snps_hsphy_exit_retention(priv); + + return 0; + +err_disable_regulator: + regulator_bulk_disable(VREG_NUM, priv->vregs); + + return ret; +} + +static int qcom_snps_hsphy_power_off(struct phy *phy) +{ + struct hsphy_priv *priv = phy_get_drvdata(phy); + + qcom_snps_hsphy_enter_retention(priv); + qcom_snps_hsphy_enable_hv_interrupts(priv); + clk_bulk_disable_unprepare(priv->num_clks, priv->clks); + regulator_bulk_disable(VREG_NUM, priv->vregs); + + return 0; +} + +static int qcom_snps_hsphy_reset(struct hsphy_priv *priv) +{ + int ret; + + ret = reset_control_assert(priv->phy_reset); + if (ret) + return ret; + + usleep_range(10, 15); + + ret = reset_control_deassert(priv->phy_reset); + if (ret) + return ret; + + usleep_range(80, 100); + + return 0; +} + +static void qcom_snps_hsphy_init_sequence(struct hsphy_priv *priv) +{ + const struct hsphy_data *data = priv->data; + const struct hsphy_init_seq *seq; + int i; + + /* Device match data is optional. */ + if (!data) + return; + + seq = data->init_seq; + + for (i = 0; i < data->init_seq_num; i++, seq++) { + writeb(seq->val, priv->base + seq->offset); + if (seq->delay) + usleep_range(seq->delay, seq->delay + 10); + } +} + +static int qcom_snps_hsphy_por_reset(struct hsphy_priv *priv) +{ + int ret; + + ret = reset_control_assert(priv->por_reset); + if (ret) + return ret; + + /* + * The Femto PHY is POR reset in the following scenarios. + * + * 1. After overriding the parameter registers. + * 2. Low power mode exit from PHY retention. + * + * Ensure that SIDDQ is cleared before bringing the PHY + * out of reset. + */ + qcom_snps_hsphy_exit_retention(priv); + + /* + * As per databook, 10 usec delay is required between + * PHY POR assert and de-assert. + */ + usleep_range(10, 20); + ret = reset_control_deassert(priv->por_reset); + if (ret) + return ret; + + /* + * As per databook, it takes 75 usec for PHY to stabilize + * after the reset. + */ + usleep_range(80, 100); + + return 0; +} + +static int qcom_snps_hsphy_init(struct phy *phy) +{ + struct hsphy_priv *priv = phy_get_drvdata(phy); + int ret; + + ret = qcom_snps_hsphy_reset(priv); + if (ret) + return ret; + + qcom_snps_hsphy_init_sequence(priv); + + ret = qcom_snps_hsphy_por_reset(priv); + if (ret) + return ret; + + return 0; +} + +static const struct phy_ops qcom_snps_hsphy_ops = { + .init = qcom_snps_hsphy_init, + .power_on = qcom_snps_hsphy_power_on, + .power_off = qcom_snps_hsphy_power_off, + .set_mode = qcom_snps_hsphy_set_mode, + .owner = THIS_MODULE, +}; + +static const char * const qcom_snps_hsphy_clks[] = { + "ref", + "ahb", + "sleep", +}; + +static int qcom_snps_hsphy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phy_provider *provider; + struct hsphy_priv *priv; + struct phy *phy; + int ret; + int i; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->num_clks = ARRAY_SIZE(qcom_snps_hsphy_clks); + priv->clks = devm_kcalloc(dev, priv->num_clks, sizeof(*priv->clks), + GFP_KERNEL); + if (!priv->clks) + return -ENOMEM; + + for (i = 0; i < priv->num_clks; i++) + priv->clks[i].id = qcom_snps_hsphy_clks[i]; + + ret = devm_clk_bulk_get(dev, priv->num_clks, priv->clks); + if (ret) + return ret; + + priv->phy_reset = devm_reset_control_get_exclusive(dev, "phy"); + if (IS_ERR(priv->phy_reset)) + return PTR_ERR(priv->phy_reset); + + priv->por_reset = devm_reset_control_get_exclusive(dev, "por"); + if (IS_ERR(priv->por_reset)) + return PTR_ERR(priv->por_reset); + + priv->vregs[VDD].supply = "vdd"; + priv->vregs[VDDA_1P8].supply = "vdda1p8"; + priv->vregs[VDDA_3P3].supply = "vdda3p3"; + + ret = devm_regulator_bulk_get(dev, VREG_NUM, priv->vregs); + if (ret) + return ret; + + /* Get device match data */ + priv->data = device_get_match_data(dev); + + phy = devm_phy_create(dev, dev->of_node, &qcom_snps_hsphy_ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + phy_set_drvdata(phy, priv); + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(provider)) + return PTR_ERR(provider); + + ret = regulator_set_load(priv->vregs[VDDA_1P8].consumer, 19000); + if (ret < 0) + return ret; + + ret = regulator_set_load(priv->vregs[VDDA_3P3].consumer, 16000); + if (ret < 0) + goto unset_1p8_load; + + return 0; + +unset_1p8_load: + regulator_set_load(priv->vregs[VDDA_1P8].consumer, 0); + + return ret; +} + +/* + * The macro is used to define an initialization sequence. Each tuple + * is meant to program 'value' into phy register at 'offset' with 'delay' + * in us followed. + */ +#define HSPHY_INIT_CFG(o, v, d) { .offset = o, .val = v, .delay = d, } + +static const struct hsphy_init_seq init_seq_femtophy[] = { + HSPHY_INIT_CFG(0xc0, 0x01, 0), + HSPHY_INIT_CFG(0xe8, 0x0d, 0), + HSPHY_INIT_CFG(0x74, 0x12, 0), + HSPHY_INIT_CFG(0x98, 0x63, 0), + HSPHY_INIT_CFG(0x9c, 0x03, 0), + HSPHY_INIT_CFG(0xa0, 0x1d, 0), + HSPHY_INIT_CFG(0xa4, 0x03, 0), + HSPHY_INIT_CFG(0x8c, 0x23, 0), + HSPHY_INIT_CFG(0x78, 0x08, 0), + HSPHY_INIT_CFG(0x7c, 0xdc, 0), + HSPHY_INIT_CFG(0x90, 0xe0, 20), + HSPHY_INIT_CFG(0x74, 0x10, 0), + HSPHY_INIT_CFG(0x90, 0x60, 0), +}; + +static const struct hsphy_data hsphy_data_femtophy = { + .init_seq = init_seq_femtophy, + .init_seq_num = ARRAY_SIZE(init_seq_femtophy), +}; + +static const struct of_device_id qcom_snps_hsphy_match[] = { + { .compatible = "qcom,usb-hs-28nm-femtophy", .data = &hsphy_data_femtophy, }, + { }, +}; +MODULE_DEVICE_TABLE(of, qcom_snps_hsphy_match); + +static struct platform_driver qcom_snps_hsphy_driver = { + .probe = qcom_snps_hsphy_probe, + .driver = { + .name = "qcom,usb-hs-28nm-phy", + .of_match_table = qcom_snps_hsphy_match, + }, +}; +module_platform_driver(qcom_snps_hsphy_driver); + +MODULE_DESCRIPTION("Qualcomm 28nm Hi-Speed USB PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/qualcomm/phy-qcom-usb-ss.c b/drivers/phy/qualcomm/phy-qcom-usb-ss.c new file mode 100644 index 000000000000..a3a6d3ce7ea1 --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-usb-ss.c @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2012-2014,2017 The Linux Foundation. All rights reserved. + * Copyright (c) 2018-2020, Linaro Limited + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/slab.h> + +#define PHY_CTRL0 0x6C +#define PHY_CTRL1 0x70 +#define PHY_CTRL2 0x74 +#define PHY_CTRL4 0x7C + +/* PHY_CTRL bits */ +#define REF_PHY_EN BIT(0) +#define LANE0_PWR_ON BIT(2) +#define SWI_PCS_CLK_SEL BIT(4) +#define TST_PWR_DOWN BIT(4) +#define PHY_RESET BIT(7) + +#define NUM_BULK_CLKS 3 +#define NUM_BULK_REGS 2 + +struct ssphy_priv { + void __iomem *base; + struct device *dev; + struct reset_control *reset_com; + struct reset_control *reset_phy; + struct regulator_bulk_data regs[NUM_BULK_REGS]; + struct clk_bulk_data clks[NUM_BULK_CLKS]; + enum phy_mode mode; +}; + +static inline void qcom_ssphy_updatel(void __iomem *addr, u32 mask, u32 val) +{ + writel((readl(addr) & ~mask) | val, addr); +} + +static int qcom_ssphy_do_reset(struct ssphy_priv *priv) +{ + int ret; + + if (!priv->reset_com) { + qcom_ssphy_updatel(priv->base + PHY_CTRL1, PHY_RESET, + PHY_RESET); + usleep_range(10, 20); + qcom_ssphy_updatel(priv->base + PHY_CTRL1, PHY_RESET, 0); + } else { + ret = reset_control_assert(priv->reset_com); + if (ret) { + dev_err(priv->dev, "Failed to assert reset com\n"); + return ret; + } + + ret = reset_control_assert(priv->reset_phy); + if (ret) { + dev_err(priv->dev, "Failed to assert reset phy\n"); + return ret; + } + + usleep_range(10, 20); + + ret = reset_control_deassert(priv->reset_com); + if (ret) { + dev_err(priv->dev, "Failed to deassert reset com\n"); + return ret; + } + + ret = reset_control_deassert(priv->reset_phy); + if (ret) { + dev_err(priv->dev, "Failed to deassert reset phy\n"); + return ret; + } + } + + return 0; +} + +static int qcom_ssphy_power_on(struct phy *phy) +{ + struct ssphy_priv *priv = phy_get_drvdata(phy); + int ret; + + ret = regulator_bulk_enable(NUM_BULK_REGS, priv->regs); + if (ret) + return ret; + + ret = clk_bulk_prepare_enable(NUM_BULK_CLKS, priv->clks); + if (ret) + goto err_disable_regulator; + + ret = qcom_ssphy_do_reset(priv); + if (ret) + goto err_disable_clock; + + writeb(SWI_PCS_CLK_SEL, priv->base + PHY_CTRL0); + qcom_ssphy_updatel(priv->base + PHY_CTRL4, LANE0_PWR_ON, LANE0_PWR_ON); + qcom_ssphy_updatel(priv->base + PHY_CTRL2, REF_PHY_EN, REF_PHY_EN); + qcom_ssphy_updatel(priv->base + PHY_CTRL4, TST_PWR_DOWN, 0); + + return 0; +err_disable_clock: + clk_bulk_disable_unprepare(NUM_BULK_CLKS, priv->clks); +err_disable_regulator: + regulator_bulk_disable(NUM_BULK_REGS, priv->regs); + + return ret; +} + +static int qcom_ssphy_power_off(struct phy *phy) +{ + struct ssphy_priv *priv = phy_get_drvdata(phy); + + qcom_ssphy_updatel(priv->base + PHY_CTRL4, LANE0_PWR_ON, 0); + qcom_ssphy_updatel(priv->base + PHY_CTRL2, REF_PHY_EN, 0); + qcom_ssphy_updatel(priv->base + PHY_CTRL4, TST_PWR_DOWN, TST_PWR_DOWN); + + clk_bulk_disable_unprepare(NUM_BULK_CLKS, priv->clks); + regulator_bulk_disable(NUM_BULK_REGS, priv->regs); + + return 0; +} + +static int qcom_ssphy_init_clock(struct ssphy_priv *priv) +{ + priv->clks[0].id = "ref"; + priv->clks[1].id = "ahb"; + priv->clks[2].id = "pipe"; + + return devm_clk_bulk_get(priv->dev, NUM_BULK_CLKS, priv->clks); +} + +static int qcom_ssphy_init_regulator(struct ssphy_priv *priv) +{ + int ret; + + priv->regs[0].supply = "vdd"; + priv->regs[1].supply = "vdda1p8"; + ret = devm_regulator_bulk_get(priv->dev, NUM_BULK_REGS, priv->regs); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(priv->dev, "Failed to get regulators\n"); + return ret; + } + + return ret; +} + +static int qcom_ssphy_init_reset(struct ssphy_priv *priv) +{ + priv->reset_com = devm_reset_control_get_optional_exclusive(priv->dev, "com"); + if (IS_ERR(priv->reset_com)) { + dev_err(priv->dev, "Failed to get reset control com\n"); + return PTR_ERR(priv->reset_com); + } + + if (priv->reset_com) { + /* if reset_com is present, reset_phy is no longer optional */ + priv->reset_phy = devm_reset_control_get_exclusive(priv->dev, "phy"); + if (IS_ERR(priv->reset_phy)) { + dev_err(priv->dev, "Failed to get reset control phy\n"); + return PTR_ERR(priv->reset_phy); + } + } + + return 0; +} + +static const struct phy_ops qcom_ssphy_ops = { + .power_off = qcom_ssphy_power_off, + .power_on = qcom_ssphy_power_on, + .owner = THIS_MODULE, +}; + +static int qcom_ssphy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phy_provider *provider; + struct ssphy_priv *priv; + struct phy *phy; + int ret; + + priv = devm_kzalloc(dev, sizeof(struct ssphy_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->mode = PHY_MODE_INVALID; + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + ret = qcom_ssphy_init_clock(priv); + if (ret) + return ret; + + ret = qcom_ssphy_init_reset(priv); + if (ret) + return ret; + + ret = qcom_ssphy_init_regulator(priv); + if (ret) + return ret; + + phy = devm_phy_create(dev, dev->of_node, &qcom_ssphy_ops); + if (IS_ERR(phy)) { + dev_err(dev, "Failed to create the SS phy\n"); + return PTR_ERR(phy); + } + + phy_set_drvdata(phy, priv); + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(provider); +} + +static const struct of_device_id qcom_ssphy_match[] = { + { .compatible = "qcom,usb-ss-28nm-phy", }, + { }, +}; +MODULE_DEVICE_TABLE(of, qcom_ssphy_match); + +static struct platform_driver qcom_ssphy_driver = { + .probe = qcom_ssphy_probe, + .driver = { + .name = "qcom-usb-ssphy", + .of_match_table = qcom_ssphy_match, + }, +}; +module_platform_driver(qcom_ssphy_driver); + +MODULE_DESCRIPTION("Qualcomm SuperSpeed USB PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c index 680cc0c8825c..a84e9f027fc4 100644 --- a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c @@ -763,7 +763,7 @@ static void rockchip_chg_detect_work(struct work_struct *work) /* put the controller in normal mode */ property_enable(base, &rphy->phy_cfg->chg_det.opmode, true); rockchip_usb2phy_otg_sm_work(&rport->otg_sm_work.work); - dev_info(&rport->phy->dev, "charger = %s\n", + dev_dbg(&rport->phy->dev, "charger = %s\n", chg_to_string(rphy->chg_type)); return; default: diff --git a/drivers/phy/socionext/phy-uniphier-pcie.c b/drivers/phy/socionext/phy-uniphier-pcie.c index 93ffbd2940fa..e4adab375c73 100644 --- a/drivers/phy/socionext/phy-uniphier-pcie.c +++ b/drivers/phy/socionext/phy-uniphier-pcie.c @@ -19,6 +19,10 @@ #include <linux/resource.h> /* PHY */ +#define PCL_PHY_CLKCTRL 0x0000 +#define PORT_SEL_MASK GENMASK(11, 9) +#define PORT_SEL_1 FIELD_PREP(PORT_SEL_MASK, 1) + #define PCL_PHY_TEST_I 0x2000 #define PCL_PHY_TEST_O 0x2004 #define TESTI_DAT_MASK GENMASK(13, 6) @@ -45,13 +49,14 @@ struct uniphier_pciephy_priv { void __iomem *base; struct device *dev; - struct clk *clk; - struct reset_control *rst; + struct clk *clk, *clk_gio; + struct reset_control *rst, *rst_gio; const struct uniphier_pciephy_soc_data *data; }; struct uniphier_pciephy_soc_data { - bool has_syscon; + bool is_legacy; + void (*set_phymode)(struct regmap *regmap); }; static void uniphier_pciephy_testio_write(struct uniphier_pciephy_priv *priv, @@ -111,16 +116,35 @@ static void uniphier_pciephy_deassert(struct uniphier_pciephy_priv *priv) static int uniphier_pciephy_init(struct phy *phy) { struct uniphier_pciephy_priv *priv = phy_get_drvdata(phy); + u32 val; int ret; ret = clk_prepare_enable(priv->clk); if (ret) return ret; - ret = reset_control_deassert(priv->rst); + ret = clk_prepare_enable(priv->clk_gio); if (ret) goto out_clk_disable; + ret = reset_control_deassert(priv->rst); + if (ret) + goto out_clk_gio_disable; + + ret = reset_control_deassert(priv->rst_gio); + if (ret) + goto out_rst_assert; + + /* support only 1 port */ + val = readl(priv->base + PCL_PHY_CLKCTRL); + val &= ~PORT_SEL_MASK; + val |= PORT_SEL_1; + writel(val, priv->base + PCL_PHY_CLKCTRL); + + /* legacy controller doesn't have phy_reset and parameters */ + if (priv->data->is_legacy) + return 0; + uniphier_pciephy_set_param(priv, PCL_PHY_R00, RX_EQ_ADJ_EN, RX_EQ_ADJ_EN); uniphier_pciephy_set_param(priv, PCL_PHY_R06, RX_EQ_ADJ, @@ -134,6 +158,10 @@ static int uniphier_pciephy_init(struct phy *phy) return 0; +out_rst_assert: + reset_control_assert(priv->rst); +out_clk_gio_disable: + clk_disable_unprepare(priv->clk_gio); out_clk_disable: clk_disable_unprepare(priv->clk); @@ -144,8 +172,11 @@ static int uniphier_pciephy_exit(struct phy *phy) { struct uniphier_pciephy_priv *priv = phy_get_drvdata(phy); - uniphier_pciephy_assert(priv); + if (!priv->data->is_legacy) + uniphier_pciephy_assert(priv); + reset_control_assert(priv->rst_gio); reset_control_assert(priv->rst); + clk_disable_unprepare(priv->clk_gio); clk_disable_unprepare(priv->clk); return 0; @@ -163,7 +194,6 @@ static int uniphier_pciephy_probe(struct platform_device *pdev) struct phy_provider *phy_provider; struct device *dev = &pdev->dev; struct regmap *regmap; - struct resource *res; struct phy *phy; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); @@ -176,18 +206,36 @@ static int uniphier_pciephy_probe(struct platform_device *pdev) priv->dev = dev; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - priv->base = devm_ioremap_resource(dev, res); + priv->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(priv->base)) return PTR_ERR(priv->base); - priv->clk = devm_clk_get(dev, NULL); - if (IS_ERR(priv->clk)) - return PTR_ERR(priv->clk); - - priv->rst = devm_reset_control_get_shared(dev, NULL); - if (IS_ERR(priv->rst)) - return PTR_ERR(priv->rst); + if (priv->data->is_legacy) { + priv->clk_gio = devm_clk_get(dev, "gio"); + if (IS_ERR(priv->clk_gio)) + return PTR_ERR(priv->clk_gio); + + priv->rst_gio = + devm_reset_control_get_shared(dev, "gio"); + if (IS_ERR(priv->rst_gio)) + return PTR_ERR(priv->rst_gio); + + priv->clk = devm_clk_get(dev, "link"); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + priv->rst = devm_reset_control_get_shared(dev, "link"); + if (IS_ERR(priv->rst)) + return PTR_ERR(priv->rst); + } else { + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + priv->rst = devm_reset_control_get_shared(dev, NULL); + if (IS_ERR(priv->rst)) + return PTR_ERR(priv->rst); + } phy = devm_phy_create(dev, dev->of_node, &uniphier_pciephy_ops); if (IS_ERR(phy)) @@ -195,9 +243,8 @@ static int uniphier_pciephy_probe(struct platform_device *pdev) regmap = syscon_regmap_lookup_by_phandle(dev->of_node, "socionext,syscon"); - if (!IS_ERR(regmap) && priv->data->has_syscon) - regmap_update_bits(regmap, SG_USBPCIESEL, - SG_USBPCIESEL_PCIE, SG_USBPCIESEL_PCIE); + if (!IS_ERR(regmap) && priv->data->set_phymode) + priv->data->set_phymode(regmap); phy_set_drvdata(phy, priv); phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); @@ -205,16 +252,31 @@ static int uniphier_pciephy_probe(struct platform_device *pdev) return PTR_ERR_OR_ZERO(phy_provider); } +static void uniphier_pciephy_ld20_setmode(struct regmap *regmap) +{ + regmap_update_bits(regmap, SG_USBPCIESEL, + SG_USBPCIESEL_PCIE, SG_USBPCIESEL_PCIE); +} + +static const struct uniphier_pciephy_soc_data uniphier_pro5_data = { + .is_legacy = true, +}; + static const struct uniphier_pciephy_soc_data uniphier_ld20_data = { - .has_syscon = true, + .is_legacy = false, + .set_phymode = uniphier_pciephy_ld20_setmode, }; static const struct uniphier_pciephy_soc_data uniphier_pxs3_data = { - .has_syscon = false, + .is_legacy = false, }; static const struct of_device_id uniphier_pciephy_match[] = { { + .compatible = "socionext,uniphier-pro5-pcie-phy", + .data = &uniphier_pro5_data, + }, + { .compatible = "socionext,uniphier-ld20-pcie-phy", .data = &uniphier_ld20_data, }, diff --git a/drivers/phy/socionext/phy-uniphier-usb3hs.c b/drivers/phy/socionext/phy-uniphier-usb3hs.c index 50f379fc4e06..a9bc74121f38 100644 --- a/drivers/phy/socionext/phy-uniphier-usb3hs.c +++ b/drivers/phy/socionext/phy-uniphier-usb3hs.c @@ -41,10 +41,12 @@ #define PHY_F(regno, msb, lsb) { (regno), (msb), (lsb) } +#define RX_CHK_SYNC PHY_F(0, 5, 5) /* RX sync mode */ +#define RX_SYNC_SEL PHY_F(1, 1, 0) /* RX sync length */ #define LS_SLEW PHY_F(10, 6, 6) /* LS mode slew rate */ #define FS_LS_DRV PHY_F(10, 5, 5) /* FS/LS slew rate */ -#define MAX_PHY_PARAMS 2 +#define MAX_PHY_PARAMS 4 struct uniphier_u3hsphy_param { struct { @@ -66,13 +68,14 @@ struct uniphier_u3hsphy_trim_param { struct uniphier_u3hsphy_priv { struct device *dev; void __iomem *base; - struct clk *clk, *clk_parent, *clk_ext; - struct reset_control *rst, *rst_parent; + struct clk *clk, *clk_parent, *clk_ext, *clk_parent_gio; + struct reset_control *rst, *rst_parent, *rst_parent_gio; struct regulator *vbus; const struct uniphier_u3hsphy_soc_data *data; }; struct uniphier_u3hsphy_soc_data { + bool is_legacy; int nparams; const struct uniphier_u3hsphy_param param[MAX_PHY_PARAMS]; u32 config0; @@ -256,11 +259,20 @@ static int uniphier_u3hsphy_init(struct phy *phy) if (ret) return ret; - ret = reset_control_deassert(priv->rst_parent); + ret = clk_prepare_enable(priv->clk_parent_gio); if (ret) goto out_clk_disable; - if (!priv->data->config0 && !priv->data->config1) + ret = reset_control_deassert(priv->rst_parent); + if (ret) + goto out_clk_gio_disable; + + ret = reset_control_deassert(priv->rst_parent_gio); + if (ret) + goto out_rst_assert; + + if ((priv->data->is_legacy) + || (!priv->data->config0 && !priv->data->config1)) return 0; config0 = priv->data->config0; @@ -280,6 +292,8 @@ static int uniphier_u3hsphy_init(struct phy *phy) out_rst_assert: reset_control_assert(priv->rst_parent); +out_clk_gio_disable: + clk_disable_unprepare(priv->clk_parent_gio); out_clk_disable: clk_disable_unprepare(priv->clk_parent); @@ -290,7 +304,9 @@ static int uniphier_u3hsphy_exit(struct phy *phy) { struct uniphier_u3hsphy_priv *priv = phy_get_drvdata(phy); + reset_control_assert(priv->rst_parent_gio); reset_control_assert(priv->rst_parent); + clk_disable_unprepare(priv->clk_parent_gio); clk_disable_unprepare(priv->clk_parent); return 0; @@ -309,7 +325,6 @@ static int uniphier_u3hsphy_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct uniphier_u3hsphy_priv *priv; struct phy_provider *phy_provider; - struct resource *res; struct phy *phy; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); @@ -322,27 +337,38 @@ static int uniphier_u3hsphy_probe(struct platform_device *pdev) priv->data->nparams > MAX_PHY_PARAMS)) return -EINVAL; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - priv->base = devm_ioremap_resource(dev, res); + priv->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(priv->base)) return PTR_ERR(priv->base); - priv->clk = devm_clk_get(dev, "phy"); - if (IS_ERR(priv->clk)) - return PTR_ERR(priv->clk); + if (!priv->data->is_legacy) { + priv->clk = devm_clk_get(dev, "phy"); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + priv->clk_ext = devm_clk_get_optional(dev, "phy-ext"); + if (IS_ERR(priv->clk_ext)) + return PTR_ERR(priv->clk_ext); + + priv->rst = devm_reset_control_get_shared(dev, "phy"); + if (IS_ERR(priv->rst)) + return PTR_ERR(priv->rst); + + } else { + priv->clk_parent_gio = devm_clk_get(dev, "gio"); + if (IS_ERR(priv->clk_parent_gio)) + return PTR_ERR(priv->clk_parent_gio); + + priv->rst_parent_gio = + devm_reset_control_get_shared(dev, "gio"); + if (IS_ERR(priv->rst_parent_gio)) + return PTR_ERR(priv->rst_parent_gio); + } priv->clk_parent = devm_clk_get(dev, "link"); if (IS_ERR(priv->clk_parent)) return PTR_ERR(priv->clk_parent); - priv->clk_ext = devm_clk_get_optional(dev, "phy-ext"); - if (IS_ERR(priv->clk_ext)) - return PTR_ERR(priv->clk_ext); - - priv->rst = devm_reset_control_get_shared(dev, "phy"); - if (IS_ERR(priv->rst)) - return PTR_ERR(priv->rst); - priv->rst_parent = devm_reset_control_get_shared(dev, "link"); if (IS_ERR(priv->rst_parent)) return PTR_ERR(priv->rst_parent); @@ -364,13 +390,26 @@ static int uniphier_u3hsphy_probe(struct platform_device *pdev) return PTR_ERR_OR_ZERO(phy_provider); } -static const struct uniphier_u3hsphy_soc_data uniphier_pxs2_data = { +static const struct uniphier_u3hsphy_soc_data uniphier_pro5_data = { + .is_legacy = true, .nparams = 0, }; -static const struct uniphier_u3hsphy_soc_data uniphier_ld20_data = { +static const struct uniphier_u3hsphy_soc_data uniphier_pxs2_data = { + .is_legacy = false, .nparams = 2, .param = { + { RX_CHK_SYNC, 1 }, + { RX_SYNC_SEL, 1 }, + }, +}; + +static const struct uniphier_u3hsphy_soc_data uniphier_ld20_data = { + .is_legacy = false, + .nparams = 4, + .param = { + { RX_CHK_SYNC, 1 }, + { RX_SYNC_SEL, 1 }, { LS_SLEW, 1 }, { FS_LS_DRV, 1 }, }, @@ -380,7 +419,12 @@ static const struct uniphier_u3hsphy_soc_data uniphier_ld20_data = { }; static const struct uniphier_u3hsphy_soc_data uniphier_pxs3_data = { - .nparams = 0, + .is_legacy = false, + .nparams = 2, + .param = { + { RX_CHK_SYNC, 1 }, + { RX_SYNC_SEL, 1 }, + }, .trim_func = uniphier_u3hsphy_trim_ld20, .config0 = 0x92316680, .config1 = 0x00000106, @@ -388,6 +432,10 @@ static const struct uniphier_u3hsphy_soc_data uniphier_pxs3_data = { static const struct of_device_id uniphier_u3hsphy_match[] = { { + .compatible = "socionext,uniphier-pro5-usb3-hsphy", + .data = &uniphier_pro5_data, + }, + { .compatible = "socionext,uniphier-pxs2-usb3-hsphy", .data = &uniphier_pxs2_data, }, diff --git a/drivers/phy/socionext/phy-uniphier-usb3ss.c b/drivers/phy/socionext/phy-uniphier-usb3ss.c index ec231e40ef2a..6700645bcbe6 100644 --- a/drivers/phy/socionext/phy-uniphier-usb3ss.c +++ b/drivers/phy/socionext/phy-uniphier-usb3ss.c @@ -215,7 +215,6 @@ static int uniphier_u3ssphy_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct uniphier_u3ssphy_priv *priv; struct phy_provider *phy_provider; - struct resource *res; struct phy *phy; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); @@ -228,8 +227,7 @@ static int uniphier_u3ssphy_probe(struct platform_device *pdev) priv->data->nparams > MAX_PHY_PARAMS)) return -EINVAL; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - priv->base = devm_ioremap_resource(dev, res); + priv->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(priv->base)) return PTR_ERR(priv->base); @@ -315,6 +313,10 @@ static const struct of_device_id uniphier_u3ssphy_match[] = { .data = &uniphier_pro4_data, }, { + .compatible = "socionext,uniphier-pro5-usb3-ssphy", + .data = &uniphier_pro4_data, + }, + { .compatible = "socionext,uniphier-pxs2-usb3-ssphy", .data = &uniphier_pxs2_data, }, diff --git a/drivers/phy/tegra/Kconfig b/drivers/phy/tegra/Kconfig index f9817c3ae85f..a208aca4ba7b 100644 --- a/drivers/phy/tegra/Kconfig +++ b/drivers/phy/tegra/Kconfig @@ -2,6 +2,8 @@ config PHY_TEGRA_XUSB tristate "NVIDIA Tegra XUSB pad controller driver" depends on ARCH_TEGRA + select USB_CONN_GPIO + select USB_PHY help Choose this option if you have an NVIDIA Tegra SoC. diff --git a/drivers/phy/tegra/Makefile b/drivers/phy/tegra/Makefile index 320dd389f34d..89b84067cb4c 100644 --- a/drivers/phy/tegra/Makefile +++ b/drivers/phy/tegra/Makefile @@ -6,4 +6,5 @@ phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_124_SOC) += xusb-tegra124.o phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_132_SOC) += xusb-tegra124.o phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_210_SOC) += xusb-tegra210.o phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_186_SOC) += xusb-tegra186.o +phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_194_SOC) += xusb-tegra186.o obj-$(CONFIG_PHY_TEGRA194_P2U) += phy-tegra194-p2u.o diff --git a/drivers/phy/tegra/xusb-tegra124.c b/drivers/phy/tegra/xusb-tegra124.c index 98d84920c676..db56c7fbe60b 100644 --- a/drivers/phy/tegra/xusb-tegra124.c +++ b/drivers/phy/tegra/xusb-tegra124.c @@ -1422,6 +1422,8 @@ tegra124_usb2_port_map(struct tegra_xusb_port *port) } static const struct tegra_xusb_port_ops tegra124_usb2_port_ops = { + .release = tegra_xusb_usb2_port_release, + .remove = tegra_xusb_usb2_port_remove, .enable = tegra124_usb2_port_enable, .disable = tegra124_usb2_port_disable, .map = tegra124_usb2_port_map, @@ -1443,6 +1445,7 @@ tegra124_ulpi_port_map(struct tegra_xusb_port *port) } static const struct tegra_xusb_port_ops tegra124_ulpi_port_ops = { + .release = tegra_xusb_ulpi_port_release, .enable = tegra124_ulpi_port_enable, .disable = tegra124_ulpi_port_disable, .map = tegra124_ulpi_port_map, @@ -1464,6 +1467,7 @@ tegra124_hsic_port_map(struct tegra_xusb_port *port) } static const struct tegra_xusb_port_ops tegra124_hsic_port_ops = { + .release = tegra_xusb_hsic_port_release, .enable = tegra124_hsic_port_enable, .disable = tegra124_hsic_port_disable, .map = tegra124_hsic_port_map, @@ -1647,6 +1651,8 @@ tegra124_usb3_port_map(struct tegra_xusb_port *port) } static const struct tegra_xusb_port_ops tegra124_usb3_port_ops = { + .release = tegra_xusb_usb3_port_release, + .remove = tegra_xusb_usb3_port_remove, .enable = tegra124_usb3_port_enable, .disable = tegra124_usb3_port_disable, .map = tegra124_usb3_port_map, diff --git a/drivers/phy/tegra/xusb-tegra186.c b/drivers/phy/tegra/xusb-tegra186.c index 84c27394c181..5d64f69b39a9 100644 --- a/drivers/phy/tegra/xusb-tegra186.c +++ b/drivers/phy/tegra/xusb-tegra186.c @@ -63,6 +63,10 @@ #define SSPX_ELPG_CLAMP_EN(x) BIT(0 + (x) * 3) #define SSPX_ELPG_CLAMP_EN_EARLY(x) BIT(1 + (x) * 3) #define SSPX_ELPG_VCORE_DOWN(x) BIT(2 + (x) * 3) +#define XUSB_PADCTL_SS_PORT_CFG 0x2c +#define PORTX_SPEED_SUPPORT_SHIFT(x) ((x) * 4) +#define PORTX_SPEED_SUPPORT_MASK (0x3) +#define PORT_SPEED_SUPPORT_GEN1 (0x0) #define XUSB_PADCTL_USB2_OTG_PADX_CTL0(x) (0x88 + (x) * 0x40) #define HS_CURR_LEVEL(x) ((x) & 0x3f) @@ -301,6 +305,97 @@ static void tegra_phy_xusb_utmi_pad_power_down(struct phy *phy) tegra186_utmi_bias_pad_power_off(padctl); } +static int tegra186_xusb_padctl_vbus_override(struct tegra_xusb_padctl *padctl, + bool status) +{ + u32 value; + + dev_dbg(padctl->dev, "%s vbus override\n", status ? "set" : "clear"); + + value = padctl_readl(padctl, USB2_VBUS_ID); + + if (status) { + value |= VBUS_OVERRIDE; + value &= ~ID_OVERRIDE(~0); + value |= ID_OVERRIDE_FLOATING; + } else { + value &= ~VBUS_OVERRIDE; + } + + padctl_writel(padctl, value, USB2_VBUS_ID); + + return 0; +} + +static int tegra186_xusb_padctl_id_override(struct tegra_xusb_padctl *padctl, + bool status) +{ + u32 value; + + dev_dbg(padctl->dev, "%s id override\n", status ? "set" : "clear"); + + value = padctl_readl(padctl, USB2_VBUS_ID); + + if (status) { + if (value & VBUS_OVERRIDE) { + value &= ~VBUS_OVERRIDE; + padctl_writel(padctl, value, USB2_VBUS_ID); + usleep_range(1000, 2000); + + value = padctl_readl(padctl, USB2_VBUS_ID); + } + + value &= ~ID_OVERRIDE(~0); + value |= ID_OVERRIDE_GROUNDED; + } else { + value &= ~ID_OVERRIDE(~0); + value |= ID_OVERRIDE_FLOATING; + } + + padctl_writel(padctl, value, USB2_VBUS_ID); + + return 0; +} + +static int tegra186_utmi_phy_set_mode(struct phy *phy, enum phy_mode mode, + int submode) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + struct tegra_xusb_usb2_port *port = tegra_xusb_find_usb2_port(padctl, + lane->index); + int err = 0; + + mutex_lock(&padctl->lock); + + dev_dbg(&port->base.dev, "%s: mode %d", __func__, mode); + + if (mode == PHY_MODE_USB_OTG) { + if (submode == USB_ROLE_HOST) { + tegra186_xusb_padctl_id_override(padctl, true); + + err = regulator_enable(port->supply); + } else if (submode == USB_ROLE_DEVICE) { + tegra186_xusb_padctl_vbus_override(padctl, true); + } else if (submode == USB_ROLE_NONE) { + /* + * When port is peripheral only or role transitions to + * USB_ROLE_NONE from USB_ROLE_DEVICE, regulator is not + * enabled. + */ + if (regulator_is_enabled(port->supply)) + regulator_disable(port->supply); + + tegra186_xusb_padctl_id_override(padctl, false); + tegra186_xusb_padctl_vbus_override(padctl, false); + } + } + + mutex_unlock(&padctl->lock); + + return err; +} + static int tegra186_utmi_phy_power_on(struct phy *phy) { struct tegra_xusb_lane *lane = phy_get_drvdata(phy); @@ -439,6 +534,7 @@ static const struct phy_ops utmi_phy_ops = { .exit = tegra186_utmi_phy_exit, .power_on = tegra186_utmi_phy_power_on, .power_off = tegra186_utmi_phy_power_off, + .set_mode = tegra186_utmi_phy_set_mode, .owner = THIS_MODULE, }; @@ -503,19 +599,6 @@ static const char * const tegra186_usb2_functions[] = { "xusb", }; -static const struct tegra_xusb_lane_soc tegra186_usb2_lanes[] = { - TEGRA186_LANE("usb2-0", 0, 0, 0, usb2), - TEGRA186_LANE("usb2-1", 0, 0, 0, usb2), - TEGRA186_LANE("usb2-2", 0, 0, 0, usb2), -}; - -static const struct tegra_xusb_pad_soc tegra186_usb2_pad = { - .name = "usb2", - .num_lanes = ARRAY_SIZE(tegra186_usb2_lanes), - .lanes = tegra186_usb2_lanes, - .ops = &tegra186_usb2_pad_ops, -}; - static int tegra186_usb2_port_enable(struct tegra_xusb_port *port) { return 0; @@ -532,6 +615,8 @@ tegra186_usb2_port_map(struct tegra_xusb_port *port) } static const struct tegra_xusb_port_ops tegra186_usb2_port_ops = { + .release = tegra_xusb_usb2_port_release, + .remove = tegra_xusb_usb2_port_remove, .enable = tegra186_usb2_port_enable, .disable = tegra186_usb2_port_disable, .map = tegra186_usb2_port_map, @@ -591,6 +676,8 @@ tegra186_usb3_port_map(struct tegra_xusb_port *port) } static const struct tegra_xusb_port_ops tegra186_usb3_port_ops = { + .release = tegra_xusb_usb3_port_release, + .remove = tegra_xusb_usb3_port_remove, .enable = tegra186_usb3_port_enable, .disable = tegra186_usb3_port_disable, .map = tegra186_usb3_port_map, @@ -635,6 +722,15 @@ static int tegra186_usb3_phy_power_on(struct phy *phy) padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_CAP); + if (padctl->soc->supports_gen2 && port->disable_gen2) { + value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_CFG); + value &= ~(PORTX_SPEED_SUPPORT_MASK << + PORTX_SPEED_SUPPORT_SHIFT(index)); + value |= (PORT_SPEED_SUPPORT_GEN1 << + PORTX_SPEED_SUPPORT_SHIFT(index)); + padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_CFG); + } + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1); value &= ~SSPX_ELPG_VCORE_DOWN(index); padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1); @@ -765,27 +861,6 @@ static const char * const tegra186_usb3_functions[] = { "xusb", }; -static const struct tegra_xusb_lane_soc tegra186_usb3_lanes[] = { - TEGRA186_LANE("usb3-0", 0, 0, 0, usb3), - TEGRA186_LANE("usb3-1", 0, 0, 0, usb3), - TEGRA186_LANE("usb3-2", 0, 0, 0, usb3), -}; - -static const struct tegra_xusb_pad_soc tegra186_usb3_pad = { - .name = "usb3", - .num_lanes = ARRAY_SIZE(tegra186_usb3_lanes), - .lanes = tegra186_usb3_lanes, - .ops = &tegra186_usb3_pad_ops, -}; - -static const struct tegra_xusb_pad_soc * const tegra186_pads[] = { - &tegra186_usb2_pad, - &tegra186_usb3_pad, -#if 0 /* TODO implement */ - &tegra186_hsic_pad, -#endif -}; - static int tegra186_xusb_read_fuse_calibration(struct tegra186_xusb_padctl *padctl) { @@ -802,7 +877,9 @@ tegra186_xusb_read_fuse_calibration(struct tegra186_xusb_padctl *padctl) err = tegra_fuse_readl(TEGRA_FUSE_SKU_CALIB_0, &value); if (err) { - dev_err(dev, "failed to read calibration fuse: %d\n", err); + if (err != -EPROBE_DEFER) + dev_err(dev, "failed to read calibration fuse: %d\n", + err); return err; } @@ -857,34 +934,13 @@ static void tegra186_xusb_padctl_remove(struct tegra_xusb_padctl *padctl) { } -static int tegra186_xusb_padctl_vbus_override(struct tegra_xusb_padctl *padctl, - bool status) -{ - u32 value; - - dev_dbg(padctl->dev, "%s vbus override\n", status ? "set" : "clear"); - - value = padctl_readl(padctl, USB2_VBUS_ID); - - if (status) { - value |= VBUS_OVERRIDE; - value &= ~ID_OVERRIDE(~0); - value |= ID_OVERRIDE_FLOATING; - } else { - value &= ~VBUS_OVERRIDE; - } - - padctl_writel(padctl, value, USB2_VBUS_ID); - - return 0; -} - static const struct tegra_xusb_padctl_ops tegra186_xusb_padctl_ops = { .probe = tegra186_xusb_padctl_probe, .remove = tegra186_xusb_padctl_remove, .vbus_override = tegra186_xusb_padctl_vbus_override, }; +#if IS_ENABLED(CONFIG_ARCH_TEGRA_186_SOC) static const char * const tegra186_xusb_padctl_supply_names[] = { "avdd-pll-erefeut", "avdd-usb", @@ -892,6 +948,40 @@ static const char * const tegra186_xusb_padctl_supply_names[] = { "vddio-hsic", }; +static const struct tegra_xusb_lane_soc tegra186_usb2_lanes[] = { + TEGRA186_LANE("usb2-0", 0, 0, 0, usb2), + TEGRA186_LANE("usb2-1", 0, 0, 0, usb2), + TEGRA186_LANE("usb2-2", 0, 0, 0, usb2), +}; + +static const struct tegra_xusb_pad_soc tegra186_usb2_pad = { + .name = "usb2", + .num_lanes = ARRAY_SIZE(tegra186_usb2_lanes), + .lanes = tegra186_usb2_lanes, + .ops = &tegra186_usb2_pad_ops, +}; + +static const struct tegra_xusb_lane_soc tegra186_usb3_lanes[] = { + TEGRA186_LANE("usb3-0", 0, 0, 0, usb3), + TEGRA186_LANE("usb3-1", 0, 0, 0, usb3), + TEGRA186_LANE("usb3-2", 0, 0, 0, usb3), +}; + +static const struct tegra_xusb_pad_soc tegra186_usb3_pad = { + .name = "usb3", + .num_lanes = ARRAY_SIZE(tegra186_usb3_lanes), + .lanes = tegra186_usb3_lanes, + .ops = &tegra186_usb3_pad_ops, +}; + +static const struct tegra_xusb_pad_soc * const tegra186_pads[] = { + &tegra186_usb2_pad, + &tegra186_usb3_pad, +#if 0 /* TODO implement */ + &tegra186_hsic_pad, +#endif +}; + const struct tegra_xusb_padctl_soc tegra186_xusb_padctl_soc = { .num_pads = ARRAY_SIZE(tegra186_pads), .pads = tegra186_pads, @@ -916,6 +1006,67 @@ const struct tegra_xusb_padctl_soc tegra186_xusb_padctl_soc = { .num_supplies = ARRAY_SIZE(tegra186_xusb_padctl_supply_names), }; EXPORT_SYMBOL_GPL(tegra186_xusb_padctl_soc); +#endif + +#if IS_ENABLED(CONFIG_ARCH_TEGRA_194_SOC) +static const char * const tegra194_xusb_padctl_supply_names[] = { + "avdd-usb", + "vclamp-usb", +}; + +static const struct tegra_xusb_lane_soc tegra194_usb2_lanes[] = { + TEGRA186_LANE("usb2-0", 0, 0, 0, usb2), + TEGRA186_LANE("usb2-1", 0, 0, 0, usb2), + TEGRA186_LANE("usb2-2", 0, 0, 0, usb2), + TEGRA186_LANE("usb2-3", 0, 0, 0, usb2), +}; + +static const struct tegra_xusb_pad_soc tegra194_usb2_pad = { + .name = "usb2", + .num_lanes = ARRAY_SIZE(tegra194_usb2_lanes), + .lanes = tegra194_usb2_lanes, + .ops = &tegra186_usb2_pad_ops, +}; + +static const struct tegra_xusb_lane_soc tegra194_usb3_lanes[] = { + TEGRA186_LANE("usb3-0", 0, 0, 0, usb3), + TEGRA186_LANE("usb3-1", 0, 0, 0, usb3), + TEGRA186_LANE("usb3-2", 0, 0, 0, usb3), + TEGRA186_LANE("usb3-3", 0, 0, 0, usb3), +}; + +static const struct tegra_xusb_pad_soc tegra194_usb3_pad = { + .name = "usb3", + .num_lanes = ARRAY_SIZE(tegra194_usb3_lanes), + .lanes = tegra194_usb3_lanes, + .ops = &tegra186_usb3_pad_ops, +}; + +static const struct tegra_xusb_pad_soc * const tegra194_pads[] = { + &tegra194_usb2_pad, + &tegra194_usb3_pad, +}; + +const struct tegra_xusb_padctl_soc tegra194_xusb_padctl_soc = { + .num_pads = ARRAY_SIZE(tegra194_pads), + .pads = tegra194_pads, + .ports = { + .usb2 = { + .ops = &tegra186_usb2_port_ops, + .count = 4, + }, + .usb3 = { + .ops = &tegra186_usb3_port_ops, + .count = 4, + }, + }, + .ops = &tegra186_xusb_padctl_ops, + .supply_names = tegra194_xusb_padctl_supply_names, + .num_supplies = ARRAY_SIZE(tegra194_xusb_padctl_supply_names), + .supports_gen2 = true, +}; +EXPORT_SYMBOL_GPL(tegra194_xusb_padctl_soc); +#endif MODULE_AUTHOR("JC Kuo <jckuo@nvidia.com>"); MODULE_DESCRIPTION("NVIDIA Tegra186 XUSB Pad Controller driver"); diff --git a/drivers/phy/tegra/xusb-tegra210.c b/drivers/phy/tegra/xusb-tegra210.c index 394913bb2f20..66bd4613835b 100644 --- a/drivers/phy/tegra/xusb-tegra210.c +++ b/drivers/phy/tegra/xusb-tegra210.c @@ -236,6 +236,7 @@ #define XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_SHIFT 18 #define XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_MASK 0xf #define XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_FLOATING 8 +#define XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_GROUNDED 0 struct tegra210_xusb_fuse_calibration { u32 hs_curr_level[4]; @@ -935,6 +936,103 @@ static int tegra210_usb2_phy_exit(struct phy *phy) return tegra210_xusb_padctl_disable(lane->pad->padctl); } +static int tegra210_xusb_padctl_vbus_override(struct tegra_xusb_padctl *padctl, + bool status) +{ + u32 value; + + dev_dbg(padctl->dev, "%s vbus override\n", status ? "set" : "clear"); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_VBUS_ID); + + if (status) { + value |= XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_VBUS_ON; + value &= ~(XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_MASK << + XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_SHIFT); + value |= XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_FLOATING << + XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_SHIFT; + } else { + value &= ~XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_VBUS_ON; + } + + padctl_writel(padctl, value, XUSB_PADCTL_USB2_VBUS_ID); + + return 0; +} + +static int tegra210_xusb_padctl_id_override(struct tegra_xusb_padctl *padctl, + bool status) +{ + u32 value; + + dev_dbg(padctl->dev, "%s id override\n", status ? "set" : "clear"); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_VBUS_ID); + + if (status) { + if (value & XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_VBUS_ON) { + value &= ~XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_VBUS_ON; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_VBUS_ID); + usleep_range(1000, 2000); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_VBUS_ID); + } + + value &= ~(XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_MASK << + XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_SHIFT); + value |= XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_GROUNDED << + XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_SHIFT; + } else { + value &= ~(XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_MASK << + XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_SHIFT); + value |= XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_FLOATING << + XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_SHIFT; + } + + padctl_writel(padctl, value, XUSB_PADCTL_USB2_VBUS_ID); + + return 0; +} + +static int tegra210_usb2_phy_set_mode(struct phy *phy, enum phy_mode mode, + int submode) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + struct tegra_xusb_usb2_port *port = tegra_xusb_find_usb2_port(padctl, + lane->index); + int err = 0; + + mutex_lock(&padctl->lock); + + dev_dbg(&port->base.dev, "%s: mode %d", __func__, mode); + + if (mode == PHY_MODE_USB_OTG) { + if (submode == USB_ROLE_HOST) { + tegra210_xusb_padctl_id_override(padctl, true); + + err = regulator_enable(port->supply); + } else if (submode == USB_ROLE_DEVICE) { + tegra210_xusb_padctl_vbus_override(padctl, true); + } else if (submode == USB_ROLE_NONE) { + /* + * When port is peripheral only or role transitions to + * USB_ROLE_NONE from USB_ROLE_DEVICE, regulator is not + * be enabled. + */ + if (regulator_is_enabled(port->supply)) + regulator_disable(port->supply); + + tegra210_xusb_padctl_id_override(padctl, false); + tegra210_xusb_padctl_vbus_override(padctl, false); + } + } + + mutex_unlock(&padctl->lock); + + return err; +} + static int tegra210_usb2_phy_power_on(struct phy *phy) { struct tegra_xusb_lane *lane = phy_get_drvdata(phy); @@ -1048,9 +1146,11 @@ static int tegra210_usb2_phy_power_on(struct phy *phy) padctl_writel(padctl, value, XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPADX_CTL1(index)); - err = regulator_enable(port->supply); - if (err) - return err; + if (port->supply && port->mode == USB_DR_MODE_HOST) { + err = regulator_enable(port->supply); + if (err) + return err; + } mutex_lock(&padctl->lock); @@ -1164,6 +1264,7 @@ static const struct phy_ops tegra210_usb2_phy_ops = { .exit = tegra210_usb2_phy_exit, .power_on = tegra210_usb2_phy_power_on, .power_off = tegra210_usb2_phy_power_off, + .set_mode = tegra210_usb2_phy_set_mode, .owner = THIS_MODULE, }; @@ -1852,6 +1953,8 @@ tegra210_usb2_port_map(struct tegra_xusb_port *port) } static const struct tegra_xusb_port_ops tegra210_usb2_port_ops = { + .release = tegra_xusb_usb2_port_release, + .remove = tegra_xusb_usb2_port_remove, .enable = tegra210_usb2_port_enable, .disable = tegra210_usb2_port_disable, .map = tegra210_usb2_port_map, @@ -1873,6 +1976,7 @@ tegra210_hsic_port_map(struct tegra_xusb_port *port) } static const struct tegra_xusb_port_ops tegra210_hsic_port_ops = { + .release = tegra_xusb_hsic_port_release, .enable = tegra210_hsic_port_enable, .disable = tegra210_hsic_port_disable, .map = tegra210_hsic_port_map, @@ -2018,35 +2122,13 @@ tegra210_usb3_port_map(struct tegra_xusb_port *port) } static const struct tegra_xusb_port_ops tegra210_usb3_port_ops = { + .release = tegra_xusb_usb3_port_release, + .remove = tegra_xusb_usb3_port_remove, .enable = tegra210_usb3_port_enable, .disable = tegra210_usb3_port_disable, .map = tegra210_usb3_port_map, }; -static int tegra210_xusb_padctl_vbus_override(struct tegra_xusb_padctl *padctl, - bool status) -{ - u32 value; - - dev_dbg(padctl->dev, "%s vbus override\n", status ? "set" : "clear"); - - value = padctl_readl(padctl, XUSB_PADCTL_USB2_VBUS_ID); - - if (status) { - value |= XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_VBUS_ON; - value &= ~(XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_MASK << - XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_SHIFT); - value |= XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_FLOATING << - XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_SHIFT; - } else { - value &= ~XUSB_PADCTL_USB2_VBUS_ID_OVERRIDE_VBUS_ON; - } - - padctl_writel(padctl, value, XUSB_PADCTL_USB2_VBUS_ID); - - return 0; -} - static int tegra210_utmi_port_reset(struct phy *phy) { struct tegra_xusb_padctl *padctl; diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c index f98ec3922c02..de4a46fe1763 100644 --- a/drivers/phy/tegra/xusb.c +++ b/drivers/phy/tegra/xusb.c @@ -66,6 +66,12 @@ static const struct of_device_id tegra_xusb_padctl_of_match[] = { .data = &tegra186_xusb_padctl_soc, }, #endif +#if defined(CONFIG_ARCH_TEGRA_194_SOC) + { + .compatible = "nvidia,tegra194-xusb-padctl", + .data = &tegra194_xusb_padctl_soc, + }, +#endif { } }; MODULE_DEVICE_TABLE(of, tegra_xusb_padctl_of_match); @@ -501,6 +507,10 @@ tegra_xusb_find_usb3_port(struct tegra_xusb_padctl *padctl, unsigned int index) static void tegra_xusb_port_release(struct device *dev) { + struct tegra_xusb_port *port = to_tegra_xusb_port(dev); + + if (port->ops->release) + port->ops->release(port); } static struct device_type tegra_xusb_port_type = { @@ -541,6 +551,16 @@ unregister: static void tegra_xusb_port_unregister(struct tegra_xusb_port *port) { + if (!IS_ERR_OR_NULL(port->usb_role_sw)) { + of_platform_depopulate(&port->dev); + usb_role_switch_unregister(port->usb_role_sw); + cancel_work_sync(&port->usb_phy_work); + usb_remove_phy(&port->usb_phy); + } + + if (port->ops->remove) + port->ops->remove(port); + device_unregister(&port->dev); } @@ -551,11 +571,146 @@ static const char *const modes[] = { [USB_DR_MODE_OTG] = "otg", }; +static const char * const usb_roles[] = { + [USB_ROLE_NONE] = "none", + [USB_ROLE_HOST] = "host", + [USB_ROLE_DEVICE] = "device", +}; + +static enum usb_phy_events to_usb_phy_event(enum usb_role role) +{ + switch (role) { + case USB_ROLE_DEVICE: + return USB_EVENT_VBUS; + + case USB_ROLE_HOST: + return USB_EVENT_ID; + + default: + return USB_EVENT_NONE; + } +} + +static void tegra_xusb_usb_phy_work(struct work_struct *work) +{ + struct tegra_xusb_port *port = container_of(work, + struct tegra_xusb_port, + usb_phy_work); + enum usb_role role = usb_role_switch_get_role(port->usb_role_sw); + + usb_phy_set_event(&port->usb_phy, to_usb_phy_event(role)); + + dev_dbg(&port->dev, "%s(): calling notifier for role %s\n", __func__, + usb_roles[role]); + + atomic_notifier_call_chain(&port->usb_phy.notifier, 0, &port->usb_phy); +} + +static int tegra_xusb_role_sw_set(struct usb_role_switch *sw, + enum usb_role role) +{ + struct tegra_xusb_port *port = usb_role_switch_get_drvdata(sw); + + dev_dbg(&port->dev, "%s(): role %s\n", __func__, usb_roles[role]); + + schedule_work(&port->usb_phy_work); + + return 0; +} + +static int tegra_xusb_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + struct tegra_xusb_port *port = container_of(otg->usb_phy, + struct tegra_xusb_port, + usb_phy); + + if (gadget != NULL) + schedule_work(&port->usb_phy_work); + + return 0; +} + +static int tegra_xusb_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + struct tegra_xusb_port *port = container_of(otg->usb_phy, + struct tegra_xusb_port, + usb_phy); + + if (host != NULL) + schedule_work(&port->usb_phy_work); + + return 0; +} + + +static int tegra_xusb_setup_usb_role_switch(struct tegra_xusb_port *port) +{ + struct tegra_xusb_lane *lane; + struct usb_role_switch_desc role_sx_desc = { + .fwnode = dev_fwnode(&port->dev), + .set = tegra_xusb_role_sw_set, + }; + int err = 0; + + /* + * USB role switch driver needs parent driver owner info. This is a + * suboptimal solution. TODO: Need to revisit this in a follow-up patch + * where an optimal solution is possible with changes to USB role + * switch driver. + */ + port->dev.driver = devm_kzalloc(&port->dev, + sizeof(struct device_driver), + GFP_KERNEL); + port->dev.driver->owner = THIS_MODULE; + + port->usb_role_sw = usb_role_switch_register(&port->dev, + &role_sx_desc); + if (IS_ERR(port->usb_role_sw)) { + err = PTR_ERR(port->usb_role_sw); + dev_err(&port->dev, "failed to register USB role switch: %d", + err); + return err; + } + + INIT_WORK(&port->usb_phy_work, tegra_xusb_usb_phy_work); + usb_role_switch_set_drvdata(port->usb_role_sw, port); + + port->usb_phy.otg = devm_kzalloc(&port->dev, sizeof(struct usb_otg), + GFP_KERNEL); + if (!port->usb_phy.otg) + return -ENOMEM; + + lane = tegra_xusb_find_lane(port->padctl, "usb2", port->index); + + /* + * Assign phy dev to usb-phy dev. Host/device drivers can use phy + * reference to retrieve usb-phy details. + */ + port->usb_phy.dev = &lane->pad->lanes[port->index]->dev; + port->usb_phy.dev->driver = port->padctl->dev->driver; + port->usb_phy.otg->usb_phy = &port->usb_phy; + port->usb_phy.otg->set_peripheral = tegra_xusb_set_peripheral; + port->usb_phy.otg->set_host = tegra_xusb_set_host; + + err = usb_add_phy_dev(&port->usb_phy); + if (err < 0) { + dev_err(&port->dev, "Failed to add USB PHY: %d\n", err); + return err; + } + + /* populate connector entry */ + of_platform_populate(port->dev.of_node, NULL, NULL, &port->dev); + + return err; +} + static int tegra_xusb_usb2_port_parse_dt(struct tegra_xusb_usb2_port *usb2) { struct tegra_xusb_port *port = &usb2->base; struct device_node *np = port->dev.of_node; const char *mode; + int err; usb2->internal = of_property_read_bool(np, "nvidia,internal"); @@ -572,7 +727,21 @@ static int tegra_xusb_usb2_port_parse_dt(struct tegra_xusb_usb2_port *usb2) usb2->mode = USB_DR_MODE_HOST; } - usb2->supply = devm_regulator_get(&port->dev, "vbus"); + /* usb-role-switch property is mandatory for OTG/Peripheral modes */ + if (usb2->mode == USB_DR_MODE_PERIPHERAL || + usb2->mode == USB_DR_MODE_OTG) { + if (of_property_read_bool(np, "usb-role-switch")) { + err = tegra_xusb_setup_usb_role_switch(port); + if (err < 0) + return err; + } else { + dev_err(&port->dev, "usb-role-switch not found for %s mode", + modes[usb2->mode]); + return -EINVAL; + } + } + + usb2->supply = regulator_get(&port->dev, "vbus"); return PTR_ERR_OR_ZERO(usb2->supply); } @@ -591,7 +760,7 @@ static int tegra_xusb_add_usb2_port(struct tegra_xusb_padctl *padctl, if (!np || !of_device_is_available(np)) goto out; - usb2 = devm_kzalloc(padctl->dev, sizeof(*usb2), GFP_KERNEL); + usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL); if (!usb2) { err = -ENOMEM; goto out; @@ -622,6 +791,20 @@ out: return err; } +void tegra_xusb_usb2_port_release(struct tegra_xusb_port *port) +{ + struct tegra_xusb_usb2_port *usb2 = to_usb2_port(port); + + kfree(usb2); +} + +void tegra_xusb_usb2_port_remove(struct tegra_xusb_port *port) +{ + struct tegra_xusb_usb2_port *usb2 = to_usb2_port(port); + + regulator_put(usb2->supply); +} + static int tegra_xusb_ulpi_port_parse_dt(struct tegra_xusb_ulpi_port *ulpi) { struct tegra_xusb_port *port = &ulpi->base; @@ -643,7 +826,7 @@ static int tegra_xusb_add_ulpi_port(struct tegra_xusb_padctl *padctl, if (!np || !of_device_is_available(np)) goto out; - ulpi = devm_kzalloc(padctl->dev, sizeof(*ulpi), GFP_KERNEL); + ulpi = kzalloc(sizeof(*ulpi), GFP_KERNEL); if (!ulpi) { err = -ENOMEM; goto out; @@ -674,6 +857,13 @@ out: return err; } +void tegra_xusb_ulpi_port_release(struct tegra_xusb_port *port) +{ + struct tegra_xusb_ulpi_port *ulpi = to_ulpi_port(port); + + kfree(ulpi); +} + static int tegra_xusb_hsic_port_parse_dt(struct tegra_xusb_hsic_port *hsic) { /* XXX */ @@ -691,7 +881,7 @@ static int tegra_xusb_add_hsic_port(struct tegra_xusb_padctl *padctl, if (!np || !of_device_is_available(np)) goto out; - hsic = devm_kzalloc(padctl->dev, sizeof(*hsic), GFP_KERNEL); + hsic = kzalloc(sizeof(*hsic), GFP_KERNEL); if (!hsic) { err = -ENOMEM; goto out; @@ -722,10 +912,18 @@ out: return err; } +void tegra_xusb_hsic_port_release(struct tegra_xusb_port *port) +{ + struct tegra_xusb_hsic_port *hsic = to_hsic_port(port); + + kfree(hsic); +} + static int tegra_xusb_usb3_port_parse_dt(struct tegra_xusb_usb3_port *usb3) { struct tegra_xusb_port *port = &usb3->base; struct device_node *np = port->dev.of_node; + enum usb_device_speed maximum_speed; u32 value; int err; @@ -739,7 +937,17 @@ static int tegra_xusb_usb3_port_parse_dt(struct tegra_xusb_usb3_port *usb3) usb3->internal = of_property_read_bool(np, "nvidia,internal"); - usb3->supply = devm_regulator_get(&port->dev, "vbus"); + if (device_property_present(&port->dev, "maximum-speed")) { + maximum_speed = usb_get_maximum_speed(&port->dev); + if (maximum_speed == USB_SPEED_SUPER) + usb3->disable_gen2 = true; + else if (maximum_speed == USB_SPEED_SUPER_PLUS) + usb3->disable_gen2 = false; + else + return -EINVAL; + } + + usb3->supply = regulator_get(&port->dev, "vbus"); return PTR_ERR_OR_ZERO(usb3->supply); } @@ -759,7 +967,7 @@ static int tegra_xusb_add_usb3_port(struct tegra_xusb_padctl *padctl, if (!np || !of_device_is_available(np)) goto out; - usb3 = devm_kzalloc(padctl->dev, sizeof(*usb3), GFP_KERNEL); + usb3 = kzalloc(sizeof(*usb3), GFP_KERNEL); if (!usb3) { err = -ENOMEM; goto out; @@ -790,6 +998,20 @@ out: return err; } +void tegra_xusb_usb3_port_release(struct tegra_xusb_port *port) +{ + struct tegra_xusb_usb3_port *usb3 = to_usb3_port(port); + + kfree(usb3); +} + +void tegra_xusb_usb3_port_remove(struct tegra_xusb_port *port) +{ + struct tegra_xusb_usb3_port *usb3 = to_usb3_port(port); + + regulator_put(usb3->supply); +} + static void __tegra_xusb_remove_ports(struct tegra_xusb_padctl *padctl) { struct tegra_xusb_port *port, *tmp; @@ -1001,7 +1223,13 @@ static int tegra_xusb_padctl_probe(struct platform_device *pdev) err = tegra_xusb_setup_ports(padctl); if (err) { - dev_err(&pdev->dev, "failed to setup XUSB ports: %d\n", err); + const char *level = KERN_ERR; + + if (err == -EPROBE_DEFER) + level = KERN_DEBUG; + + dev_printk(level, &pdev->dev, + dev_fmt("failed to setup XUSB ports: %d\n"), err); goto remove_pads; } @@ -1143,6 +1371,27 @@ int tegra_phy_xusb_utmi_port_reset(struct phy *phy) } EXPORT_SYMBOL_GPL(tegra_phy_xusb_utmi_port_reset); +int tegra_xusb_padctl_get_usb3_companion(struct tegra_xusb_padctl *padctl, + unsigned int port) +{ + struct tegra_xusb_usb2_port *usb2; + struct tegra_xusb_usb3_port *usb3; + int i; + + usb2 = tegra_xusb_find_usb2_port(padctl, port); + if (!usb2) + return -EINVAL; + + for (i = 0; i < padctl->soc->ports.usb3.count; i++) { + usb3 = tegra_xusb_find_usb3_port(padctl, i); + if (usb3 && usb3->port == usb2->base.index) + return usb3->base.index; + } + + return -ENODEV; +} +EXPORT_SYMBOL_GPL(tegra_xusb_padctl_get_usb3_companion); + MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>"); MODULE_DESCRIPTION("Tegra XUSB Pad Controller driver"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/tegra/xusb.h b/drivers/phy/tegra/xusb.h index da94fcce6307..ea35af747066 100644 --- a/drivers/phy/tegra/xusb.h +++ b/drivers/phy/tegra/xusb.h @@ -12,6 +12,7 @@ #include <linux/workqueue.h> #include <linux/usb/otg.h> +#include <linux/usb/role.h> /* legacy entry points for backwards-compatibility */ int tegra_xusb_padctl_legacy_probe(struct platform_device *pdev); @@ -266,9 +267,18 @@ struct tegra_xusb_port { struct list_head list; struct device dev; + struct usb_role_switch *usb_role_sw; + struct work_struct usb_phy_work; + struct usb_phy usb_phy; + const struct tegra_xusb_port_ops *ops; }; +static inline struct tegra_xusb_port *to_tegra_xusb_port(struct device *dev) +{ + return container_of(dev, struct tegra_xusb_port, dev); +} + struct tegra_xusb_lane_map { unsigned int port; const char *type; @@ -303,6 +313,8 @@ to_usb2_port(struct tegra_xusb_port *port) struct tegra_xusb_usb2_port * tegra_xusb_find_usb2_port(struct tegra_xusb_padctl *padctl, unsigned int index); +void tegra_xusb_usb2_port_release(struct tegra_xusb_port *port); +void tegra_xusb_usb2_port_remove(struct tegra_xusb_port *port); struct tegra_xusb_ulpi_port { struct tegra_xusb_port base; @@ -317,6 +329,8 @@ to_ulpi_port(struct tegra_xusb_port *port) return container_of(port, struct tegra_xusb_ulpi_port, base); } +void tegra_xusb_ulpi_port_release(struct tegra_xusb_port *port); + struct tegra_xusb_hsic_port { struct tegra_xusb_port base; }; @@ -327,12 +341,15 @@ to_hsic_port(struct tegra_xusb_port *port) return container_of(port, struct tegra_xusb_hsic_port, base); } +void tegra_xusb_hsic_port_release(struct tegra_xusb_port *port); + struct tegra_xusb_usb3_port { struct tegra_xusb_port base; struct regulator *supply; bool context_saved; unsigned int port; bool internal; + bool disable_gen2; u32 tap1; u32 amp; @@ -349,8 +366,12 @@ to_usb3_port(struct tegra_xusb_port *port) struct tegra_xusb_usb3_port * tegra_xusb_find_usb3_port(struct tegra_xusb_padctl *padctl, unsigned int index); +void tegra_xusb_usb3_port_release(struct tegra_xusb_port *port); +void tegra_xusb_usb3_port_remove(struct tegra_xusb_port *port); struct tegra_xusb_port_ops { + void (*release)(struct tegra_xusb_port *port); + void (*remove)(struct tegra_xusb_port *port); int (*enable)(struct tegra_xusb_port *port); void (*disable)(struct tegra_xusb_port *port); struct tegra_xusb_lane *(*map)(struct tegra_xusb_port *port); @@ -392,6 +413,7 @@ struct tegra_xusb_padctl_soc { const char * const *supply_names; unsigned int num_supplies; + bool supports_gen2; bool need_fake_usb3_port; }; @@ -448,5 +470,8 @@ extern const struct tegra_xusb_padctl_soc tegra210_xusb_padctl_soc; #if defined(CONFIG_ARCH_TEGRA_186_SOC) extern const struct tegra_xusb_padctl_soc tegra186_xusb_padctl_soc; #endif +#if defined(CONFIG_ARCH_TEGRA_194_SOC) +extern const struct tegra_xusb_padctl_soc tegra194_xusb_padctl_soc; +#endif #endif /* __PHY_TEGRA_XUSB_H */ diff --git a/drivers/phy/ti/phy-gmii-sel.c b/drivers/phy/ti/phy-gmii-sel.c index 1c536fc03c83..7edd5c3bc536 100644 --- a/drivers/phy/ti/phy-gmii-sel.c +++ b/drivers/phy/ti/phy-gmii-sel.c @@ -170,6 +170,21 @@ struct phy_gmii_sel_soc_data phy_gmii_sel_soc_dm814 = { .regfields = phy_gmii_sel_fields_am33xx, }; +static const +struct reg_field phy_gmii_sel_fields_am654[][PHY_GMII_SEL_LAST] = { + { + [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x4040, 0, 1), + [PHY_GMII_SEL_RGMII_ID_MODE] = REG_FIELD((~0), 0, 0), + [PHY_GMII_SEL_RMII_IO_CLK_EN] = REG_FIELD((~0), 0, 0), + }, +}; + +static const +struct phy_gmii_sel_soc_data phy_gmii_sel_soc_am654 = { + .num_ports = 1, + .regfields = phy_gmii_sel_fields_am654, +}; + static const struct of_device_id phy_gmii_sel_id_table[] = { { .compatible = "ti,am3352-phy-gmii-sel", @@ -187,6 +202,10 @@ static const struct of_device_id phy_gmii_sel_id_table[] = { .compatible = "ti,dm814-phy-gmii-sel", .data = &phy_gmii_sel_soc_dm814, }, + { + .compatible = "ti,am654-phy-gmii-sel", + .data = &phy_gmii_sel_soc_am654, + }, {} }; MODULE_DEVICE_TABLE(of, phy_gmii_sel_id_table); diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index b7980c856898..68c1b93ac5d9 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -147,10 +147,10 @@ static ssize_t boot_acl_show(struct device *dev, struct device_attribute *attr, for (ret = 0, i = 0; i < tb->nboot_acl; i++) { if (!uuid_is_null(&uuids[i])) - ret += snprintf(buf + ret, PAGE_SIZE - ret, "%pUb", + ret += scnprintf(buf + ret, PAGE_SIZE - ret, "%pUb", &uuids[i]); - ret += snprintf(buf + ret, PAGE_SIZE - ret, "%s", + ret += scnprintf(buf + ret, PAGE_SIZE - ret, "%s", i < tb->nboot_acl - 1 ? "," : "\n"); } diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index 921d164b3f35..b451a5aa90b5 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -247,7 +247,7 @@ struct tb_drom_entry_header { struct tb_drom_entry_generic { struct tb_drom_entry_header header; - u8 data[0]; + u8 data[]; } __packed; struct tb_drom_entry_port { diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index 13e88109742e..fbbe32ca1e69 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -114,7 +114,7 @@ struct icm_notification { struct ep_name_entry { u8 len; u8 type; - u8 data[0]; + u8 data[]; }; #define EP_NAME_INTEL_VSS 0x10 diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index b341fc60c4ba..3d084cec136f 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -259,6 +259,7 @@ int usb4_switch_setup(struct tb_switch *sw) /** * usb4_switch_read_uid() - Read UID from USB4 router * @sw: USB4 router + * @uid: UID is stored here * * Reads 64-bit UID from USB4 router config space. */ @@ -296,6 +297,9 @@ static int usb4_switch_drom_read_block(struct tb_switch *sw, /** * usb4_switch_drom_read() - Read arbitrary bytes from USB4 router DROM * @sw: USB4 router + * @address: Byte address inside DROM to start reading + * @buf: Buffer where the DROM content is stored + * @size: Number of bytes to read from DROM * * Uses USB4 router operations to read router DROM. For devices this * should always work but for hosts it may return %-EOPNOTSUPP in which diff --git a/drivers/usb/atm/ueagle-atm.c b/drivers/usb/atm/ueagle-atm.c index 635cf0466b59..e9fed9a88737 100644 --- a/drivers/usb/atm/ueagle-atm.c +++ b/drivers/usb/atm/ueagle-atm.c @@ -350,7 +350,7 @@ struct l1_code { u8 string_header[E4_L1_STRING_HEADER]; u8 page_number_to_block_index[E4_MAX_PAGE_NUMBER]; struct block_index page_header[E4_NO_SWAPPAGE_HEADERS]; - u8 code[0]; + u8 code[]; } __packed; /* structures describing a block within a DSP page */ diff --git a/drivers/usb/atm/usbatm.h b/drivers/usb/atm/usbatm.h index d3bdc4cc47aa..d96658e2e209 100644 --- a/drivers/usb/atm/usbatm.h +++ b/drivers/usb/atm/usbatm.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0+ +/* SPDX-License-Identifier: GPL-2.0+ */ /****************************************************************************** * usbatm.h - Generic USB xDSL driver core * @@ -164,7 +164,7 @@ struct usbatm_data { unsigned char *cell_buf; /* holds partial rx cell */ unsigned int buf_usage; - struct urb *urbs[0]; + struct urb *urbs[]; }; static inline void *to_usbatm_driver_data(struct usb_interface *intf) diff --git a/drivers/usb/c67x00/c67x00-hcd.h b/drivers/usb/c67x00/c67x00-hcd.h index 3b181d4c7a03..6b6b04a3fe0f 100644 --- a/drivers/usb/c67x00/c67x00-hcd.h +++ b/drivers/usb/c67x00/c67x00-hcd.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0+ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * c67x00-hcd.h: Cypress C67X00 USB HCD * diff --git a/drivers/usb/c67x00/c67x00.h b/drivers/usb/c67x00/c67x00.h index 7ce10928b037..a4456d0fffa9 100644 --- a/drivers/usb/c67x00/c67x00.h +++ b/drivers/usb/c67x00/c67x00.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0+ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * c67x00.h: Cypress C67X00 USB register and field definitions * diff --git a/drivers/usb/cdns3/cdns3-pci-wrap.c b/drivers/usb/cdns3/cdns3-pci-wrap.c index b0a29efe7d31..deeea618ba33 100644 --- a/drivers/usb/cdns3/cdns3-pci-wrap.c +++ b/drivers/usb/cdns3/cdns3-pci-wrap.c @@ -201,4 +201,4 @@ MODULE_DEVICE_TABLE(pci, cdns3_pci_ids); MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>"); MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Cadence USBSS PCI wrapperr"); +MODULE_DESCRIPTION("Cadence USBSS PCI wrapper"); diff --git a/drivers/usb/cdns3/cdns3-ti.c b/drivers/usb/cdns3/cdns3-ti.c index c6a79ca15858..5685ba11480b 100644 --- a/drivers/usb/cdns3/cdns3-ti.c +++ b/drivers/usb/cdns3/cdns3-ti.c @@ -52,8 +52,8 @@ enum modestrap_mode { USBSS_MODESTRAP_MODE_NONE, struct cdns_ti { struct device *dev; void __iomem *usbss; - int usb2_only:1; - int vbus_divider:1; + unsigned usb2_only:1; + unsigned vbus_divider:1; struct clk *usb2_refclk; struct clk *lpm_clk; }; diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c index c2123ef8d8a3..4aafba20f450 100644 --- a/drivers/usb/cdns3/core.c +++ b/drivers/usb/cdns3/core.c @@ -330,9 +330,9 @@ exit: * * Returns role */ -static enum usb_role cdns3_role_get(struct device *dev) +static enum usb_role cdns3_role_get(struct usb_role_switch *sw) { - struct cdns3 *cdns = dev_get_drvdata(dev); + struct cdns3 *cdns = usb_role_switch_get_drvdata(sw); return cdns->role; } @@ -346,9 +346,9 @@ static enum usb_role cdns3_role_get(struct device *dev) * - Role switch for dual-role devices * - USB_ROLE_GADGET <--> USB_ROLE_NONE for peripheral-only devices */ -static int cdns3_role_set(struct device *dev, enum usb_role role) +static int cdns3_role_set(struct usb_role_switch *sw, enum usb_role role) { - struct cdns3 *cdns = dev_get_drvdata(dev); + struct cdns3 *cdns = usb_role_switch_get_drvdata(sw); int ret = 0; pm_runtime_get_sync(cdns->dev); @@ -423,12 +423,6 @@ pm_put: return ret; } -static const struct usb_role_switch_desc cdns3_switch_desc = { - .set = cdns3_role_set, - .get = cdns3_role_get, - .allow_userspace_control = true, -}; - /** * cdns3_probe - probe for cdns3 core device * @pdev: Pointer to cdns3 core platform device @@ -437,6 +431,7 @@ static const struct usb_role_switch_desc cdns3_switch_desc = { */ static int cdns3_probe(struct platform_device *pdev) { + struct usb_role_switch_desc sw_desc = { }; struct device *dev = &pdev->dev; struct resource *res; struct cdns3 *cdns; @@ -529,7 +524,12 @@ static int cdns3_probe(struct platform_device *pdev) if (ret) goto err3; - cdns->role_sw = usb_role_switch_register(dev, &cdns3_switch_desc); + sw_desc.set = cdns3_role_set; + sw_desc.get = cdns3_role_get; + sw_desc.allow_userspace_control = true; + sw_desc.driver_data = cdns; + + cdns->role_sw = usb_role_switch_register(dev, &sw_desc); if (IS_ERR(cdns->role_sw)) { ret = PTR_ERR(cdns->role_sw); dev_warn(dev, "Unable to register Role Switch\n"); diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c index 3574dbb09366..372460ea4df9 100644 --- a/drivers/usb/cdns3/gadget.c +++ b/drivers/usb/cdns3/gadget.c @@ -1380,7 +1380,7 @@ static bool cdns3_request_handled(struct cdns3_endpoint *priv_ep, struct cdns3_request *priv_req) { struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - struct cdns3_trb *trb = priv_req->trb; + struct cdns3_trb *trb; int current_index = 0; int handled = 0; int doorbell; diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h index f003a7801872..52765b098b9e 100644 --- a/drivers/usb/cdns3/gadget.h +++ b/drivers/usb/cdns3/gadget.h @@ -1199,7 +1199,7 @@ struct cdns3_aligned_buf { void *buf; dma_addr_t dma; u32 size; - int in_use:1; + unsigned in_use:1; struct list_head list; }; @@ -1308,8 +1308,8 @@ struct cdns3_device { unsigned u2_allowed:1; unsigned is_selfpowered:1; unsigned setup_pending:1; - int hw_configured_flag:1; - int wake_up_flag:1; + unsigned hw_configured_flag:1; + unsigned wake_up_flag:1; unsigned status_completion_no_call:1; unsigned using_streams:1; int out_mem_is_allocated; diff --git a/drivers/usb/chipidea/bits.h b/drivers/usb/chipidea/bits.h index 98da99510be7..b1540ce93264 100644 --- a/drivers/usb/chipidea/bits.h +++ b/drivers/usb/chipidea/bits.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +/* SPDX-License-Identifier: GPL-2.0 */ /* * bits.h - register bits of the ChipIdea USB IP core * diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index d49d5e1235d0..644ecaef17ee 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +/* SPDX-License-Identifier: GPL-2.0 */ /* * ci.h - common structures, functions, and macros of the ChipIdea driver * diff --git a/drivers/usb/chipidea/ci_hdrc_imx.h b/drivers/usb/chipidea/ci_hdrc_imx.h index de2aac9a2868..c2051aeba13f 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.h +++ b/drivers/usb/chipidea/ci_hdrc_imx.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0+ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * Copyright 2012 Freescale Semiconductor, Inc. */ diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 52139c2a9924..ae0bdc036464 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -600,9 +600,9 @@ static int ci_cable_notifier(struct notifier_block *nb, unsigned long event, return NOTIFY_DONE; } -static enum usb_role ci_usb_role_switch_get(struct device *dev) +static enum usb_role ci_usb_role_switch_get(struct usb_role_switch *sw) { - struct ci_hdrc *ci = dev_get_drvdata(dev); + struct ci_hdrc *ci = usb_role_switch_get_drvdata(sw); enum usb_role role; unsigned long flags; @@ -613,9 +613,10 @@ static enum usb_role ci_usb_role_switch_get(struct device *dev) return role; } -static int ci_usb_role_switch_set(struct device *dev, enum usb_role role) +static int ci_usb_role_switch_set(struct usb_role_switch *sw, + enum usb_role role) { - struct ci_hdrc *ci = dev_get_drvdata(dev); + struct ci_hdrc *ci = usb_role_switch_get_drvdata(sw); struct ci_hdrc_cable *cable = NULL; enum usb_role current_role = ci_role_to_usb_role(ci); enum ci_role ci_role = usb_role_to_ci_role(role); @@ -1118,6 +1119,7 @@ static int ci_hdrc_probe(struct platform_device *pdev) } if (ci_role_switch.fwnode) { + ci_role_switch.driver_data = ci; ci->role_switch = usb_role_switch_register(dev, &ci_role_switch); if (IS_ERR(ci->role_switch)) { diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c index fbfb02e05c97..be63924ea82e 100644 --- a/drivers/usb/chipidea/otg.c +++ b/drivers/usb/chipidea/otg.c @@ -170,6 +170,13 @@ static void ci_handle_id_switch(struct ci_hdrc *ci) dev_dbg(ci->dev, "switching from %s to %s\n", ci_role(ci)->name, ci->roles[role]->name); + if (ci->vbus_active && ci->role == CI_ROLE_GADGET) + /* + * vbus disconnect event is lost due to role + * switch occurs during system suspend. + */ + usb_gadget_vbus_disconnect(&ci->gadget); + ci_role_stop(ci); if (role == CI_ROLE_GADGET && diff --git a/drivers/usb/chipidea/otg.h b/drivers/usb/chipidea/otg.h index 4f8b8179ec96..5e7a6e571dd2 100644 --- a/drivers/usb/chipidea/otg.h +++ b/drivers/usb/chipidea/otg.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (C) 2013-2014 Freescale Semiconductor, Inc. * diff --git a/drivers/usb/chipidea/otg_fsm.h b/drivers/usb/chipidea/otg_fsm.h index 2b49d29bf2fb..1f5c5ae0e71e 100644 --- a/drivers/usb/chipidea/otg_fsm.h +++ b/drivers/usb/chipidea/otg_fsm.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (C) 2014 Freescale Semiconductor, Inc. * diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index 4c4ac30db498..921bcf14dc06 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -1532,7 +1532,7 @@ static void ci_hdrc_gadget_connect(struct usb_gadget *_gadget, int is_active) struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); if (is_active) { - pm_runtime_get_sync(&_gadget->dev); + pm_runtime_get_sync(ci->dev); hw_device_reset(ci); spin_lock_irq(&ci->lock); if (ci->driver) { @@ -1552,7 +1552,7 @@ static void ci_hdrc_gadget_connect(struct usb_gadget *_gadget, int is_active) ci->platdata->notify_event(ci, CI_HDRC_CONTROLLER_STOPPED_EVENT); _gadget_stop_activity(&ci->gadget); - pm_runtime_put_sync(&_gadget->dev); + pm_runtime_put_sync(ci->dev); usb_gadget_set_state(_gadget, USB_STATE_NOTATTACHED); } } @@ -1637,12 +1637,12 @@ static int ci_udc_pullup(struct usb_gadget *_gadget, int is_on) if (ci_otg_is_fsm_mode(ci) || ci->role == CI_ROLE_HOST) return 0; - pm_runtime_get_sync(&ci->gadget.dev); + pm_runtime_get_sync(ci->dev); if (is_on) hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS); else hw_write(ci, OP_USBCMD, USBCMD_RS, 0); - pm_runtime_put_sync(&ci->gadget.dev); + pm_runtime_put_sync(ci->dev); return 0; } @@ -1840,7 +1840,7 @@ static int ci_udc_stop(struct usb_gadget *gadget) CI_HDRC_CONTROLLER_STOPPED_EVENT); _gadget_stop_activity(&ci->gadget); spin_lock_irqsave(&ci->lock, flags); - pm_runtime_put(&ci->gadget.dev); + pm_runtime_put(ci->dev); } spin_unlock_irqrestore(&ci->lock, flags); @@ -1971,9 +1971,6 @@ static int udc_start(struct ci_hdrc *ci) if (retval) goto destroy_eps; - pm_runtime_no_callbacks(&ci->gadget.dev); - pm_runtime_enable(&ci->gadget.dev); - return retval; destroy_eps: diff --git a/drivers/usb/chipidea/udc.h b/drivers/usb/chipidea/udc.h index e023735d94b7..ebb11b625bb8 100644 --- a/drivers/usb/chipidea/udc.h +++ b/drivers/usb/chipidea/udc.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +/* SPDX-License-Identifier: GPL-2.0 */ /* * udc.h - ChipIdea UDC structures * diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 47f09a6ce7bd..84d6f7df09a4 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -923,16 +923,16 @@ static int set_serial_info(struct tty_struct *tty, struct serial_struct *ss) mutex_lock(&acm->port.mutex); - if ((ss->close_delay != old_close_delay) || - (ss->closing_wait != old_closing_wait)) { - if (!capable(CAP_SYS_ADMIN)) + if (!capable(CAP_SYS_ADMIN)) { + if ((ss->close_delay != old_close_delay) || + (ss->closing_wait != old_closing_wait)) retval = -EPERM; - else { - acm->port.close_delay = close_delay; - acm->port.closing_wait = closing_wait; - } - } else - retval = -EOPNOTSUPP; + else + retval = -EOPNOTSUPP; + } else { + acm->port.close_delay = close_delay; + acm->port.closing_wait = closing_wait; + } mutex_unlock(&acm->port.mutex); return retval; diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 2b27d232d7a7..f81606c6a35b 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -261,9 +261,19 @@ static int usb_probe_device(struct device *dev) */ if (!udriver->supports_autosuspend) error = usb_autoresume_device(udev); + if (error) + return error; - if (!error) - error = udriver->probe(udev); + if (udriver->generic_subclass) + error = usb_generic_driver_probe(udev); + if (error) + return error; + + error = udriver->probe(udev); + if (error == -ENODEV && udriver != &usb_generic_driver) { + udev->use_generic_driver = 1; + return -EPROBE_DEFER; + } return error; } @@ -273,7 +283,10 @@ static int usb_unbind_device(struct device *dev) struct usb_device *udev = to_usb_device(dev); struct usb_device_driver *udriver = to_usb_device_driver(dev->driver); - udriver->disconnect(udev); + if (udriver->disconnect) + udriver->disconnect(udev); + if (udriver->generic_subclass) + usb_generic_driver_disconnect(udev); if (!udriver->supports_autosuspend) usb_autosuspend_device(udev); return 0; @@ -790,17 +803,42 @@ const struct usb_device_id *usb_match_id(struct usb_interface *interface, } EXPORT_SYMBOL_GPL(usb_match_id); +const struct usb_device_id *usb_device_match_id(struct usb_device *udev, + const struct usb_device_id *id) +{ + if (!id) + return NULL; + + for (; id->idVendor || id->idProduct ; id++) { + if (usb_match_device(udev, id)) + return id; + } + + return NULL; +} + static int usb_device_match(struct device *dev, struct device_driver *drv) { /* devices and interfaces are handled separately */ if (is_usb_device(dev)) { + struct usb_device *udev; + struct usb_device_driver *udrv; /* interface drivers never match devices */ if (!is_usb_device_driver(drv)) return 0; - /* TODO: Add real matching code */ - return 1; + udev = to_usb_device(dev); + udrv = to_usb_device_driver(drv); + + if (udrv->id_table && + usb_device_match_id(udev, udrv->id_table) != NULL) { + return 1; + } + + if (udrv->match) + return udrv->match(udev); + return 0; } else if (is_usb_interface(dev)) { struct usb_interface *intf; @@ -1149,7 +1187,10 @@ static int usb_suspend_device(struct usb_device *udev, pm_message_t msg) udev->do_remote_wakeup = 0; udriver = &usb_generic_driver; } - status = udriver->suspend(udev, msg); + if (udriver->suspend) + status = udriver->suspend(udev, msg); + if (status == 0 && udriver->generic_subclass) + status = usb_generic_driver_suspend(udev, msg); done: dev_vdbg(&udev->dev, "%s: status %d\n", __func__, status); @@ -1181,7 +1222,10 @@ static int usb_resume_device(struct usb_device *udev, pm_message_t msg) udev->reset_resume = 1; udriver = to_usb_device_driver(udev->dev.driver); - status = udriver->resume(udev, msg); + if (udriver->generic_subclass) + status = usb_generic_driver_resume(udev, msg); + if (status == 0 && udriver->resume) + status = udriver->resume(udev, msg); done: dev_vdbg(&udev->dev, "%s: status %d\n", __func__, status); diff --git a/drivers/usb/core/generic.c b/drivers/usb/core/generic.c index 38f8b3e31762..4626227a6dd2 100644 --- a/drivers/usb/core/generic.c +++ b/drivers/usb/core/generic.c @@ -195,7 +195,38 @@ int usb_choose_configuration(struct usb_device *udev) } EXPORT_SYMBOL_GPL(usb_choose_configuration); -static int generic_probe(struct usb_device *udev) +static int __check_usb_generic(struct device_driver *drv, void *data) +{ + struct usb_device *udev = data; + struct usb_device_driver *udrv; + + if (!is_usb_device_driver(drv)) + return 0; + udrv = to_usb_device_driver(drv); + if (udrv == &usb_generic_driver) + return 0; + if (!udrv->id_table) + return 0; + + return usb_device_match_id(udev, udrv->id_table) != NULL; +} + +static bool usb_generic_driver_match(struct usb_device *udev) +{ + if (udev->use_generic_driver) + return true; + + /* + * If any other driver wants the device, leave the device to this other + * driver. + */ + if (bus_for_each_drv(&usb_bus_type, NULL, udev, __check_usb_generic)) + return false; + + return true; +} + +int usb_generic_driver_probe(struct usb_device *udev) { int err, c; @@ -222,7 +253,7 @@ static int generic_probe(struct usb_device *udev) return 0; } -static void generic_disconnect(struct usb_device *udev) +void usb_generic_driver_disconnect(struct usb_device *udev) { usb_notify_remove_device(udev); @@ -234,7 +265,7 @@ static void generic_disconnect(struct usb_device *udev) #ifdef CONFIG_PM -static int generic_suspend(struct usb_device *udev, pm_message_t msg) +int usb_generic_driver_suspend(struct usb_device *udev, pm_message_t msg) { int rc; @@ -262,7 +293,7 @@ static int generic_suspend(struct usb_device *udev, pm_message_t msg) return rc; } -static int generic_resume(struct usb_device *udev, pm_message_t msg) +int usb_generic_driver_resume(struct usb_device *udev, pm_message_t msg) { int rc; @@ -285,11 +316,12 @@ static int generic_resume(struct usb_device *udev, pm_message_t msg) struct usb_device_driver usb_generic_driver = { .name = "usb", - .probe = generic_probe, - .disconnect = generic_disconnect, + .match = usb_generic_driver_match, + .probe = usb_generic_driver_probe, + .disconnect = usb_generic_driver_disconnect, #ifdef CONFIG_PM - .suspend = generic_suspend, - .resume = generic_resume, + .suspend = usb_generic_driver_suspend, + .resume = usb_generic_driver_resume, #endif .supports_autosuspend = 1, }; diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 5adf489428aa..d5f834f16993 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -5,6 +5,7 @@ * Released under the GPLv2 only. */ +#include <linux/acpi.h> #include <linux/pci.h> /* for scatterlist macros */ #include <linux/usb.h> #include <linux/module.h> @@ -1941,6 +1942,7 @@ free_interfaces: intf->dev.of_node = usb_of_get_interface_node(dev, configuration, ifnum); } + ACPI_COMPANION_SET(&intf->dev, ACPI_COMPANION(&dev->dev)); intf->dev.driver = NULL; intf->dev.bus = &usb_bus_type; intf->dev.type = &usb_if_device_type; diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index f19694e69f5c..9f4320b9d7fc 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -849,7 +849,7 @@ static struct attribute *dev_string_attrs[] = { static umode_t dev_string_attrs_are_visible(struct kobject *kobj, struct attribute *a, int n) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct usb_device *udev = to_usb_device(dev); if (a == &dev_attr_manufacturer.attr) { @@ -883,7 +883,7 @@ read_descriptors(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, char *buf, loff_t off, size_t count) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct usb_device *udev = to_usb_device(dev); size_t nleft = count; size_t srclen, n; @@ -1233,7 +1233,7 @@ static struct attribute *intf_assoc_attrs[] = { static umode_t intf_assoc_attrs_are_visible(struct kobject *kobj, struct attribute *a, int n) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct usb_interface *intf = to_usb_interface(dev); if (intf->intf_assoc == NULL) diff --git a/drivers/usb/core/usb-acpi.c b/drivers/usb/core/usb-acpi.c index 9043d7242d67..50b2fc7fcc0e 100644 --- a/drivers/usb/core/usb-acpi.c +++ b/drivers/usb/core/usb-acpi.c @@ -86,7 +86,7 @@ static enum usb_port_connect_type usb_acpi_get_connect_type(acpi_handle handle, { enum usb_port_connect_type connect_type = USB_PORT_CONNECT_TYPE_UNKNOWN; struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *upc; + union acpi_object *upc = NULL; acpi_status status; /* @@ -98,11 +98,12 @@ static enum usb_port_connect_type usb_acpi_get_connect_type(acpi_handle handle, * no connectable, the port would be not used. */ status = acpi_evaluate_object(handle, "_UPC", NULL, &buffer); + if (ACPI_FAILURE(status)) + goto out; + upc = buffer.pointer; - if (!upc || (upc->type != ACPI_TYPE_PACKAGE) - || upc->package.count != 4) { + if (!upc || (upc->type != ACPI_TYPE_PACKAGE) || upc->package.count != 4) goto out; - } if (upc->package.elements[0].integer.value) if (pld->user_visible) @@ -186,7 +187,7 @@ usb_acpi_find_companion_for_port(struct usb_port *port_dev) handle = adev->handle; status = acpi_get_physical_device_location(handle, &pld); - if (!ACPI_FAILURE(status) && pld) { + if (ACPI_SUCCESS(status) && pld) { port_dev->location = USB_ACPI_LOCATION_VALID | pld->group_token << 8 | pld->group_position; port_dev->connect_type = usb_acpi_get_connect_type(handle, pld); diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index 3ad0ee57e859..64ed4023a8c8 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -50,6 +50,12 @@ extern void usb_release_bos_descriptor(struct usb_device *dev); extern char *usb_cache_string(struct usb_device *udev, int index); extern int usb_set_configuration(struct usb_device *dev, int configuration); extern int usb_choose_configuration(struct usb_device *udev); +extern int usb_generic_driver_probe(struct usb_device *udev); +extern void usb_generic_driver_disconnect(struct usb_device *udev); +extern int usb_generic_driver_suspend(struct usb_device *udev, + pm_message_t msg); +extern int usb_generic_driver_resume(struct usb_device *udev, + pm_message_t msg); static inline unsigned usb_get_max_power(struct usb_device *udev, struct usb_host_config *c) @@ -66,6 +72,8 @@ extern int usb_match_one_id_intf(struct usb_device *dev, const struct usb_device_id *id); extern int usb_match_device(struct usb_device *dev, const struct usb_device_id *id); +extern const struct usb_device_id *usb_device_match_id(struct usb_device *udev, + const struct usb_device_id *id); extern void usb_forced_unbind_intf(struct usb_interface *intf); extern void usb_unbind_and_rebind_marked_interfaces(struct usb_device *udev); diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 968e03b89d04..99b0bdfe0012 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -411,6 +411,10 @@ enum dwc2_ep0_state { * register. * 0 - Deactivate the transceiver (default) * 1 - Activate the transceiver + * @activate_stm_id_vb_detection: Activate external ID pin and Vbus level + * detection using GGPIO register. + * 0 - Deactivate the external level detection (default) + * 1 - Activate the external level detection * @g_dma: Enables gadget dma usage (default: autodetect). * @g_dma_desc: Enables gadget descriptor DMA (default: autodetect). * @g_rx_fifo_size: The periodic rx fifo size for the device, in @@ -481,6 +485,7 @@ struct dwc2_core_params { bool service_interval; u8 hird_threshold; bool activate_stm_fs_transceiver; + bool activate_stm_id_vb_detection; bool ipg_isoc_en; u16 max_packet_count; u32 max_transfer_size; @@ -874,6 +879,8 @@ struct dwc2_hregs_backup { * removed once all SoCs support usb transceiver. * @supplies: Definition of USB power supplies * @vbus_supply: Regulator supplying vbus. + * @usb33d: Optional 3.3v regulator used on some stm32 devices to + * supply ID and VBUS detection hardware. * @lock: Spinlock that protects all the driver data structures * @priv: Stores a pointer to the struct usb_hcd * @queuing_high_bandwidth: True if multiple packets of a high-bandwidth @@ -1061,6 +1068,7 @@ struct dwc2_hsotg { struct dwc2_hsotg_plat *plat; struct regulator_bulk_data supplies[DWC2_NUM_SUPPLIES]; struct regulator *vbus_supply; + struct regulator *usb33d; spinlock_t lock; void *priv; diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 92ed32ec1607..12b98b466287 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -1646,7 +1646,8 @@ static int dwc2_hsotg_process_req_status(struct dwc2_hsotg *hsotg, switch (ctrl->bRequestType & USB_RECIP_MASK) { case USB_RECIP_DEVICE: - status = 1 << USB_DEVICE_SELF_POWERED; + status = hsotg->gadget.is_selfpowered << + USB_DEVICE_SELF_POWERED; status |= hsotg->remote_wakeup_allowed << USB_DEVICE_REMOTE_WAKEUP; reply = cpu_to_le16(status); @@ -4528,6 +4529,26 @@ static int dwc2_hsotg_gadget_getframe(struct usb_gadget *gadget) } /** + * dwc2_hsotg_set_selfpowered - set if device is self/bus powered + * @gadget: The usb gadget state + * @is_selfpowered: Whether the device is self-powered + * + * Set if the device is self or bus powered. + */ +static int dwc2_hsotg_set_selfpowered(struct usb_gadget *gadget, + int is_selfpowered) +{ + struct dwc2_hsotg *hsotg = to_hsotg(gadget); + unsigned long flags; + + spin_lock_irqsave(&hsotg->lock, flags); + gadget->is_selfpowered = !!is_selfpowered; + spin_unlock_irqrestore(&hsotg->lock, flags); + + return 0; +} + +/** * dwc2_hsotg_pullup - connect/disconnect the USB PHY * @gadget: The usb gadget state * @is_on: Current state of the USB PHY @@ -4618,6 +4639,7 @@ static int dwc2_hsotg_vbus_draw(struct usb_gadget *gadget, unsigned int mA) static const struct usb_gadget_ops dwc2_hsotg_gadget_ops = { .get_frame = dwc2_hsotg_gadget_getframe, + .set_selfpowered = dwc2_hsotg_set_selfpowered, .udc_start = dwc2_hsotg_udc_start, .udc_stop = dwc2_hsotg_udc_stop, .pullup = dwc2_hsotg_pullup, diff --git a/drivers/usb/dwc2/hcd.h b/drivers/usb/dwc2/hcd.h index 8ca6d12a6f57..1224fa9df604 100644 --- a/drivers/usb/dwc2/hcd.h +++ b/drivers/usb/dwc2/hcd.h @@ -199,7 +199,7 @@ struct dwc2_hcd_urb { u32 flags; u16 interval; struct dwc2_hcd_pipe_info pipe_info; - struct dwc2_hcd_iso_packet_desc iso_descs[0]; + struct dwc2_hcd_iso_packet_desc iso_descs[]; }; /* Phases for control transfers */ diff --git a/drivers/usb/dwc2/hw.h b/drivers/usb/dwc2/hw.h index 510e87ec0be8..c4027bbcedec 100644 --- a/drivers/usb/dwc2/hw.h +++ b/drivers/usb/dwc2/hw.h @@ -54,6 +54,12 @@ #define GOTGCTL_HSTSETHNPEN BIT(10) #define GOTGCTL_HNPREQ BIT(9) #define GOTGCTL_HSTNEGSCS BIT(8) +#define GOTGCTL_BVALOVAL BIT(7) +#define GOTGCTL_BVALOEN BIT(6) +#define GOTGCTL_AVALOVAL BIT(5) +#define GOTGCTL_AVALOEN BIT(4) +#define GOTGCTL_VBVALOVAL BIT(3) +#define GOTGCTL_VBVALOEN BIT(2) #define GOTGCTL_SESREQ BIT(1) #define GOTGCTL_SESREQSCS BIT(0) @@ -227,6 +233,8 @@ #define GPVNDCTL HSOTG_REG(0x0034) #define GGPIO HSOTG_REG(0x0038) #define GGPIO_STM32_OTG_GCCFG_PWRDWN BIT(16) +#define GGPIO_STM32_OTG_GCCFG_VBDEN BIT(21) +#define GGPIO_STM32_OTG_GCCFG_IDEN BIT(22) #define GUID HSOTG_REG(0x003c) #define GSNPSID HSOTG_REG(0x0040) diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c index 31e090ac9f1e..8ccc83f7eb3f 100644 --- a/drivers/usb/dwc2/params.c +++ b/drivers/usb/dwc2/params.c @@ -163,6 +163,35 @@ static void dwc2_set_stm32f7_hsotg_params(struct dwc2_hsotg *hsotg) p->host_perio_tx_fifo_size = 256; } +static void dwc2_set_stm32mp15_fsotg_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_cap = DWC2_CAP_PARAM_NO_HNP_SRP_CAPABLE; + p->speed = DWC2_SPEED_PARAM_FULL; + p->host_rx_fifo_size = 128; + p->host_nperio_tx_fifo_size = 96; + p->host_perio_tx_fifo_size = 96; + p->max_packet_count = 256; + p->phy_type = DWC2_PHY_TYPE_PARAM_FS; + p->i2c_enable = false; + p->activate_stm_fs_transceiver = true; + p->activate_stm_id_vb_detection = true; + p->power_down = DWC2_POWER_DOWN_PARAM_NONE; +} + +static void dwc2_set_stm32mp15_hsotg_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_cap = DWC2_CAP_PARAM_NO_HNP_SRP_CAPABLE; + p->activate_stm_id_vb_detection = true; + p->host_rx_fifo_size = 440; + p->host_nperio_tx_fifo_size = 256; + p->host_perio_tx_fifo_size = 256; + p->power_down = DWC2_POWER_DOWN_PARAM_NONE; +} + const struct of_device_id dwc2_of_match_table[] = { { .compatible = "brcm,bcm2835-usb", .data = dwc2_set_bcm_params }, { .compatible = "hisilicon,hi6220-usb", .data = dwc2_set_his_params }, @@ -186,6 +215,10 @@ const struct of_device_id dwc2_of_match_table[] = { { .compatible = "st,stm32f4x9-hsotg" }, { .compatible = "st,stm32f7-hsotg", .data = dwc2_set_stm32f7_hsotg_params }, + { .compatible = "st,stm32mp15-fsotg", + .data = dwc2_set_stm32mp15_fsotg_params }, + { .compatible = "st,stm32mp15-hsotg", + .data = dwc2_set_stm32mp15_hsotg_params }, {}, }; MODULE_DEVICE_TABLE(of, dwc2_of_match_table); diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index 3c6ce09a6db5..69972750e161 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -285,7 +285,9 @@ static int dwc2_lowlevel_hw_init(struct dwc2_hsotg *hsotg) ret = devm_regulator_bulk_get(hsotg->dev, ARRAY_SIZE(hsotg->supplies), hsotg->supplies); if (ret) { - dev_err(hsotg->dev, "failed to request supplies: %d\n", ret); + if (ret != -EPROBE_DEFER) + dev_err(hsotg->dev, "failed to request supplies: %d\n", + ret); return ret; } return 0; @@ -312,6 +314,9 @@ static int dwc2_driver_remove(struct platform_device *dev) if (hsotg->gadget_enabled) dwc2_hsotg_remove(hsotg); + if (hsotg->params.activate_stm_id_vb_detection) + regulator_disable(hsotg->usb33d); + if (hsotg->ll_hw_enabled) dwc2_lowlevel_hw_disable(hsotg); @@ -392,8 +397,7 @@ static int dwc2_driver_probe(struct platform_device *dev) return retval; } - res = platform_get_resource(dev, IORESOURCE_MEM, 0); - hsotg->regs = devm_ioremap_resource(&dev->dev, res); + hsotg->regs = devm_platform_get_and_ioremap_resource(dev, 0, &res); if (IS_ERR(hsotg->regs)) return PTR_ERR(hsotg->regs); @@ -464,10 +468,35 @@ static int dwc2_driver_probe(struct platform_device *dev) if (retval) goto error; + if (hsotg->params.activate_stm_id_vb_detection) { + u32 ggpio; + + hsotg->usb33d = devm_regulator_get(hsotg->dev, "usb33d"); + if (IS_ERR(hsotg->usb33d)) { + retval = PTR_ERR(hsotg->usb33d); + if (retval != -EPROBE_DEFER) + dev_err(hsotg->dev, + "failed to request usb33d supply: %d\n", + retval); + goto error; + } + retval = regulator_enable(hsotg->usb33d); + if (retval) { + dev_err(hsotg->dev, + "failed to enable usb33d supply: %d\n", retval); + goto error; + } + + ggpio = dwc2_readl(hsotg, GGPIO); + ggpio |= GGPIO_STM32_OTG_GCCFG_IDEN; + ggpio |= GGPIO_STM32_OTG_GCCFG_VBDEN; + dwc2_writel(hsotg, ggpio, GGPIO); + } + if (hsotg->dr_mode != USB_DR_MODE_HOST) { retval = dwc2_gadget_init(hsotg); if (retval) - goto error; + goto error_init; hsotg->gadget_enabled = 1; } @@ -493,7 +522,7 @@ static int dwc2_driver_probe(struct platform_device *dev) if (retval) { if (hsotg->gadget_enabled) dwc2_hsotg_remove(hsotg); - goto error; + goto error_init; } hsotg->hcd_enabled = 1; } @@ -509,6 +538,9 @@ static int dwc2_driver_probe(struct platform_device *dev) return 0; +error_init: + if (hsotg->params.activate_stm_id_vb_detection) + regulator_disable(hsotg->usb33d); error: dwc2_lowlevel_hw_disable(hsotg); return retval; @@ -523,6 +555,37 @@ static int __maybe_unused dwc2_suspend(struct device *dev) if (is_device_mode) dwc2_hsotg_suspend(dwc2); + if (dwc2->params.activate_stm_id_vb_detection) { + unsigned long flags; + u32 ggpio, gotgctl; + + /* + * Need to force the mode to the current mode to avoid Mode + * Mismatch Interrupt when ID detection will be disabled. + */ + dwc2_force_mode(dwc2, !is_device_mode); + + spin_lock_irqsave(&dwc2->lock, flags); + gotgctl = dwc2_readl(dwc2, GOTGCTL); + /* bypass debounce filter, enable overrides */ + gotgctl |= GOTGCTL_DBNCE_FLTR_BYPASS; + gotgctl |= GOTGCTL_BVALOEN | GOTGCTL_AVALOEN; + /* Force A / B session if needed */ + if (gotgctl & GOTGCTL_ASESVLD) + gotgctl |= GOTGCTL_AVALOVAL; + if (gotgctl & GOTGCTL_BSESVLD) + gotgctl |= GOTGCTL_BVALOVAL; + dwc2_writel(dwc2, gotgctl, GOTGCTL); + spin_unlock_irqrestore(&dwc2->lock, flags); + + ggpio = dwc2_readl(dwc2, GGPIO); + ggpio &= ~GGPIO_STM32_OTG_GCCFG_IDEN; + ggpio &= ~GGPIO_STM32_OTG_GCCFG_VBDEN; + dwc2_writel(dwc2, ggpio, GGPIO); + + regulator_disable(dwc2->usb33d); + } + if (dwc2->ll_hw_enabled && (is_device_mode || dwc2_host_can_poweroff_phy(dwc2))) { ret = __dwc2_lowlevel_hw_disable(dwc2); @@ -544,6 +607,34 @@ static int __maybe_unused dwc2_resume(struct device *dev) } dwc2->phy_off_for_suspend = false; + if (dwc2->params.activate_stm_id_vb_detection) { + unsigned long flags; + u32 ggpio, gotgctl; + + ret = regulator_enable(dwc2->usb33d); + if (ret) + return ret; + + ggpio = dwc2_readl(dwc2, GGPIO); + ggpio |= GGPIO_STM32_OTG_GCCFG_IDEN; + ggpio |= GGPIO_STM32_OTG_GCCFG_VBDEN; + dwc2_writel(dwc2, ggpio, GGPIO); + + /* ID/VBUS detection startup time */ + usleep_range(5000, 7000); + + spin_lock_irqsave(&dwc2->lock, flags); + gotgctl = dwc2_readl(dwc2, GOTGCTL); + gotgctl &= ~GOTGCTL_DBNCE_FLTR_BYPASS; + gotgctl &= ~(GOTGCTL_BVALOEN | GOTGCTL_AVALOEN | + GOTGCTL_BVALOVAL | GOTGCTL_AVALOVAL); + dwc2_writel(dwc2, gotgctl, GOTGCTL); + spin_unlock_irqrestore(&dwc2->lock, flags); + } + + /* Need to restore FORCEDEVMODE/FORCEHOSTMODE */ + dwc2_force_dr_mode(dwc2); + if (dwc2_is_device_mode(dwc2)) ret = dwc2_hsotg_resume(dwc2); diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 1d85c42b9c67..edc17155cb2b 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -289,12 +289,6 @@ done: return 0; } -static const struct clk_bulk_data dwc3_core_clks[] = { - { .id = "ref" }, - { .id = "bus_early" }, - { .id = "suspend" }, -}; - /* * dwc3_frame_length_adjustment - Adjusts frame length if required * @dwc3: Pointer to our controller context structure @@ -1029,6 +1023,9 @@ static int dwc3_core_init(struct dwc3 *dwc) if (dwc->dis_tx_ipgap_linecheck_quirk) reg |= DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS; + if (dwc->parkmode_disable_ss_quirk) + reg |= DWC3_GUCTL1_PARKMODE_DISABLE_SS; + dwc3_writel(dwc->regs, DWC3_GUCTL1, reg); } @@ -1342,6 +1339,8 @@ static void dwc3_get_properties(struct dwc3 *dwc) "snps,dis-del-phy-power-chg-quirk"); dwc->dis_tx_ipgap_linecheck_quirk = device_property_read_bool(dev, "snps,dis-tx-ipgap-linecheck-quirk"); + dwc->parkmode_disable_ss_quirk = device_property_read_bool(dev, + "snps,parkmode-disable-ss-quirk"); dwc->tx_de_emphasis_quirk = device_property_read_bool(dev, "snps,tx_de_emphasis_quirk"); @@ -1441,11 +1440,6 @@ static int dwc3_probe(struct platform_device *pdev) if (!dwc) return -ENOMEM; - dwc->clks = devm_kmemdup(dev, dwc3_core_clks, sizeof(dwc3_core_clks), - GFP_KERNEL); - if (!dwc->clks) - return -ENOMEM; - dwc->dev = dev; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -1476,22 +1470,23 @@ static int dwc3_probe(struct platform_device *pdev) dwc3_get_properties(dwc); - dwc->reset = devm_reset_control_get_optional_shared(dev, NULL); + dwc->reset = devm_reset_control_array_get(dev, true, true); if (IS_ERR(dwc->reset)) return PTR_ERR(dwc->reset); if (dev->of_node) { - dwc->num_clks = ARRAY_SIZE(dwc3_core_clks); - - ret = devm_clk_bulk_get(dev, dwc->num_clks, dwc->clks); + ret = devm_clk_bulk_get_all(dev, &dwc->clks); if (ret == -EPROBE_DEFER) return ret; /* * Clocks are optional, but new DT platforms should support all * clocks as required by the DT-binding. */ - if (ret) + if (ret < 0) dwc->num_clks = 0; + else + dwc->num_clks = ret; + } ret = reset_control_deassert(dwc->reset); @@ -1637,6 +1632,8 @@ static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg) switch (dwc->current_dr_role) { case DWC3_GCTL_PRTCAP_DEVICE: + if (pm_runtime_suspended(dwc->dev)) + break; spin_lock_irqsave(&dwc->lock, flags); dwc3_gadget_suspend(dwc); spin_unlock_irqrestore(&dwc->lock, flags); diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 77c4a9abe365..6846eb0cba13 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -25,6 +25,7 @@ #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> #include <linux/usb/otg.h> +#include <linux/usb/role.h> #include <linux/ulpi/interface.h> #include <linux/phy/phy.h> @@ -249,6 +250,7 @@ #define DWC3_GUCTL_HSTINAUTORETRY BIT(14) /* Global User Control 1 Register */ +#define DWC3_GUCTL1_PARKMODE_DISABLE_SS BIT(17) #define DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS BIT(28) #define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW BIT(24) @@ -953,6 +955,9 @@ struct dwc3_scratchpad_array { * @hsphy_mode: UTMI phy mode, one of following: * - USBPHY_INTERFACE_MODE_UTMI * - USBPHY_INTERFACE_MODE_UTMIW + * @role_sw: usb_role_switch handle + * @role_switch_default_mode: default operation mode of controller while + * usb role is USB_ROLE_NONE. * @usb2_phy: pointer to USB2 PHY * @usb3_phy: pointer to USB3 PHY * @usb2_generic_phy: pointer to USB2 PHY @@ -1024,6 +1029,8 @@ struct dwc3_scratchpad_array { * change quirk. * @dis_tx_ipgap_linecheck_quirk: set if we disable u2mac linestate * check during HS transmit. + * @parkmode_disable_ss_quirk: set if we need to disable all SuperSpeed + * instances in park mode. * @tx_de_emphasis_quirk: set if we enable Tx de-emphasis quirk * @tx_de_emphasis: Tx de-emphasis value * 0 - -6dB de-emphasis @@ -1086,6 +1093,8 @@ struct dwc3 { struct extcon_dev *edev; struct notifier_block edev_nb; enum usb_phy_interface hsphy_mode; + struct usb_role_switch *role_sw; + enum usb_dr_mode role_switch_default_mode; u32 fladj; u32 irq_gadget; @@ -1215,6 +1224,7 @@ struct dwc3 { unsigned dis_u2_freeclk_exists_quirk:1; unsigned dis_del_phy_power_chg_quirk:1; unsigned dis_tx_ipgap_linecheck_quirk:1; + unsigned parkmode_disable_ss_quirk:1; unsigned tx_de_emphasis_quirk:1; unsigned tx_de_emphasis:2; diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c index c946d64142ad..7db1ffc92bbd 100644 --- a/drivers/usb/dwc3/drd.c +++ b/drivers/usb/dwc3/drd.c @@ -476,6 +476,94 @@ static struct extcon_dev *dwc3_get_extcon(struct dwc3 *dwc) return edev; } +#if IS_ENABLED(CONFIG_USB_ROLE_SWITCH) +#define ROLE_SWITCH 1 +static int dwc3_usb_role_switch_set(struct usb_role_switch *sw, + enum usb_role role) +{ + struct dwc3 *dwc = usb_role_switch_get_drvdata(sw); + u32 mode; + + switch (role) { + case USB_ROLE_HOST: + mode = DWC3_GCTL_PRTCAP_HOST; + break; + case USB_ROLE_DEVICE: + mode = DWC3_GCTL_PRTCAP_DEVICE; + break; + default: + if (dwc->role_switch_default_mode == USB_DR_MODE_HOST) + mode = DWC3_GCTL_PRTCAP_HOST; + else + mode = DWC3_GCTL_PRTCAP_DEVICE; + break; + } + + dwc3_set_mode(dwc, mode); + return 0; +} + +static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw) +{ + struct dwc3 *dwc = usb_role_switch_get_drvdata(sw); + unsigned long flags; + enum usb_role role; + + spin_lock_irqsave(&dwc->lock, flags); + switch (dwc->current_dr_role) { + case DWC3_GCTL_PRTCAP_HOST: + role = USB_ROLE_HOST; + break; + case DWC3_GCTL_PRTCAP_DEVICE: + role = USB_ROLE_DEVICE; + break; + case DWC3_GCTL_PRTCAP_OTG: + role = dwc->current_otg_role; + break; + default: + if (dwc->role_switch_default_mode == USB_DR_MODE_HOST) + role = USB_ROLE_HOST; + else + role = USB_ROLE_DEVICE; + break; + } + spin_unlock_irqrestore(&dwc->lock, flags); + return role; +} + +static int dwc3_setup_role_switch(struct dwc3 *dwc) +{ + struct usb_role_switch_desc dwc3_role_switch = {NULL}; + const char *str; + u32 mode; + int ret; + + ret = device_property_read_string(dwc->dev, "role-switch-default-mode", + &str); + if (ret >= 0 && !strncmp(str, "host", strlen("host"))) { + dwc->role_switch_default_mode = USB_DR_MODE_HOST; + mode = DWC3_GCTL_PRTCAP_HOST; + } else { + dwc->role_switch_default_mode = USB_DR_MODE_PERIPHERAL; + mode = DWC3_GCTL_PRTCAP_DEVICE; + } + + dwc3_role_switch.fwnode = dev_fwnode(dwc->dev); + dwc3_role_switch.set = dwc3_usb_role_switch_set; + dwc3_role_switch.get = dwc3_usb_role_switch_get; + dwc3_role_switch.driver_data = dwc; + dwc->role_sw = usb_role_switch_register(dwc->dev, &dwc3_role_switch); + if (IS_ERR(dwc->role_sw)) + return PTR_ERR(dwc->role_sw); + + dwc3_set_mode(dwc, mode); + return 0; +} +#else +#define ROLE_SWITCH 0 +#define dwc3_setup_role_switch(x) 0 +#endif + int dwc3_drd_init(struct dwc3 *dwc) { int ret, irq; @@ -484,7 +572,12 @@ int dwc3_drd_init(struct dwc3 *dwc) if (IS_ERR(dwc->edev)) return PTR_ERR(dwc->edev); - if (dwc->edev) { + if (ROLE_SWITCH && + device_property_read_bool(dwc->dev, "usb-role-switch")) { + ret = dwc3_setup_role_switch(dwc); + if (ret < 0) + return ret; + } else if (dwc->edev) { dwc->edev_nb.notifier_call = dwc3_drd_notifier; ret = extcon_register_notifier(dwc->edev, EXTCON_USB_HOST, &dwc->edev_nb); @@ -531,6 +624,9 @@ void dwc3_drd_exit(struct dwc3 *dwc) { unsigned long flags; + if (dwc->role_sw) + usb_role_switch_unregister(dwc->role_sw); + if (dwc->edev) extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST, &dwc->edev_nb); diff --git a/drivers/usb/dwc3/dwc3-exynos.c b/drivers/usb/dwc3/dwc3-exynos.c index 90bb022737da..48b68b6f0dc8 100644 --- a/drivers/usb/dwc3/dwc3-exynos.c +++ b/drivers/usb/dwc3/dwc3-exynos.c @@ -162,6 +162,12 @@ static const struct dwc3_exynos_driverdata exynos5250_drvdata = { .suspend_clk_idx = -1, }; +static const struct dwc3_exynos_driverdata exynos5420_drvdata = { + .clk_names = { "usbdrd30", "usbdrd30_susp_clk"}, + .num_clks = 2, + .suspend_clk_idx = 1, +}; + static const struct dwc3_exynos_driverdata exynos5433_drvdata = { .clk_names = { "aclk", "susp_clk", "pipe_pclk", "phyclk" }, .num_clks = 4, @@ -179,6 +185,9 @@ static const struct of_device_id exynos_dwc3_match[] = { .compatible = "samsung,exynos5250-dwusb3", .data = &exynos5250_drvdata, }, { + .compatible = "samsung,exynos5420-dwusb3", + .data = &exynos5420_drvdata, + }, { .compatible = "samsung,exynos5433-dwusb3", .data = &exynos5433_drvdata, }, { diff --git a/drivers/usb/dwc3/dwc3-meson-g12a.c b/drivers/usb/dwc3/dwc3-meson-g12a.c index 8a3ec1a951fe..b81d085bc534 100644 --- a/drivers/usb/dwc3/dwc3-meson-g12a.c +++ b/drivers/usb/dwc3/dwc3-meson-g12a.c @@ -107,10 +107,37 @@ static const char *phy_names[PHY_COUNT] = { "usb2-phy0", "usb2-phy1", "usb3-phy0", }; +static struct clk_bulk_data meson_g12a_clocks[] = { + { .id = NULL }, +}; + +static struct clk_bulk_data meson_a1_clocks[] = { + { .id = "usb_ctrl" }, + { .id = "usb_bus" }, + { .id = "xtal_usb_ctrl" }, +}; + +struct dwc3_meson_g12a_drvdata { + bool otg_switch_supported; + struct clk_bulk_data *clks; + int num_clks; +}; + +static struct dwc3_meson_g12a_drvdata g12a_drvdata = { + .otg_switch_supported = true, + .clks = meson_g12a_clocks, + .num_clks = ARRAY_SIZE(meson_g12a_clocks), +}; + +static struct dwc3_meson_g12a_drvdata a1_drvdata = { + .otg_switch_supported = false, + .clks = meson_a1_clocks, + .num_clks = ARRAY_SIZE(meson_a1_clocks), +}; + struct dwc3_meson_g12a { struct device *dev; struct regmap *regmap; - struct clk *clk; struct reset_control *reset; struct phy *phys[PHY_COUNT]; enum usb_dr_mode otg_mode; @@ -120,6 +147,7 @@ struct dwc3_meson_g12a { struct regulator *vbus; struct usb_role_switch_desc switch_desc; struct usb_role_switch *role_switch; + const struct dwc3_meson_g12a_drvdata *drvdata; }; static void dwc3_meson_g12a_usb2_set_mode(struct dwc3_meson_g12a *priv, @@ -151,7 +179,7 @@ static int dwc3_meson_g12a_usb2_init(struct dwc3_meson_g12a *priv) U2P_R0_POWER_ON_RESET, U2P_R0_POWER_ON_RESET); - if (i == USB2_OTG_PHY) { + if (priv->drvdata->otg_switch_supported && i == USB2_OTG_PHY) { regmap_update_bits(priv->regmap, U2P_R0 + (U2P_REG_SIZE * i), U2P_R0_ID_PULLUP | U2P_R0_DRV_VBUS, @@ -295,7 +323,7 @@ static int dwc3_meson_g12a_otg_mode_set(struct dwc3_meson_g12a *priv, { int ret; - if (!priv->phys[USB2_OTG_PHY]) + if (!priv->drvdata->otg_switch_supported || !priv->phys[USB2_OTG_PHY]) return -EINVAL; if (mode == PHY_MODE_USB_HOST) @@ -321,9 +349,10 @@ static int dwc3_meson_g12a_otg_mode_set(struct dwc3_meson_g12a *priv, return 0; } -static int dwc3_meson_g12a_role_set(struct device *dev, enum usb_role role) +static int dwc3_meson_g12a_role_set(struct usb_role_switch *sw, + enum usb_role role) { - struct dwc3_meson_g12a *priv = dev_get_drvdata(dev); + struct dwc3_meson_g12a *priv = usb_role_switch_get_drvdata(sw); enum phy_mode mode; if (role == USB_ROLE_NONE) @@ -338,9 +367,9 @@ static int dwc3_meson_g12a_role_set(struct device *dev, enum usb_role role) return dwc3_meson_g12a_otg_mode_set(priv, mode); } -static enum usb_role dwc3_meson_g12a_role_get(struct device *dev) +static enum usb_role dwc3_meson_g12a_role_get(struct usb_role_switch *sw) { - struct dwc3_meson_g12a *priv = dev_get_drvdata(dev); + struct dwc3_meson_g12a *priv = usb_role_switch_get_drvdata(sw); return priv->otg_phy_mode == PHY_MODE_USB_HOST ? USB_ROLE_HOST : USB_ROLE_DEVICE; @@ -380,14 +409,61 @@ static struct device *dwc3_meson_g12_find_child(struct device *dev, return &pdev->dev; } +static int dwc3_meson_g12a_otg_init(struct platform_device *pdev, + struct dwc3_meson_g12a *priv) +{ + enum phy_mode otg_id; + 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->regmap, USB_R5, + USB_R5_ID_DIG_IRQ, 0); + + irq = platform_get_irq(pdev, 0); + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + dwc3_meson_g12a_irq_thread, + IRQF_ONESHOT, pdev->name, priv); + if (ret) + return ret; + } + + /* Setup OTG mode corresponding to the ID pin */ + if (priv->otg_mode == USB_DR_MODE_OTG) { + otg_id = dwc3_meson_g12a_get_id(priv); + if (otg_id != priv->otg_phy_mode) { + if (dwc3_meson_g12a_otg_mode_set(priv, otg_id)) + dev_warn(dev, "Failed to switch OTG mode\n"); + } + } + + /* Setup role switcher */ + priv->switch_desc.usb2_port = dwc3_meson_g12_find_child(dev, + "snps,dwc3"); + priv->switch_desc.udc = dwc3_meson_g12_find_child(dev, "snps,dwc2"); + priv->switch_desc.allow_userspace_control = true; + priv->switch_desc.set = dwc3_meson_g12a_role_set; + priv->switch_desc.get = dwc3_meson_g12a_role_get; + priv->switch_desc.driver_data = priv; + + priv->role_switch = usb_role_switch_register(dev, &priv->switch_desc); + if (IS_ERR(priv->role_switch)) + dev_warn(dev, "Unable to register Role Switch\n"); + + return 0; +} + static int dwc3_meson_g12a_probe(struct platform_device *pdev) { struct dwc3_meson_g12a *priv; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; void __iomem *base; - enum phy_mode otg_id; - int ret, i, irq; + int ret, i; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -409,17 +485,18 @@ static int dwc3_meson_g12a_probe(struct platform_device *pdev) priv->vbus = NULL; } - priv->clk = devm_clk_get(dev, NULL); - if (IS_ERR(priv->clk)) - return PTR_ERR(priv->clk); + priv->drvdata = of_device_get_match_data(&pdev->dev); - ret = clk_prepare_enable(priv->clk); + ret = devm_clk_bulk_get(dev, + priv->drvdata->num_clks, + priv->drvdata->clks); if (ret) return ret; - devm_add_action_or_reset(dev, - (void(*)(void *))clk_disable_unprepare, - priv->clk); + ret = clk_bulk_prepare_enable(priv->drvdata->num_clks, + priv->drvdata->clks); + if (ret) + return ret; platform_set_drvdata(pdev, priv); priv->dev = dev; @@ -433,41 +510,28 @@ static int dwc3_meson_g12a_probe(struct platform_device *pdev) ret = reset_control_reset(priv->reset); if (ret) - return ret; + goto err_disable_clks; ret = dwc3_meson_g12a_get_phys(priv); if (ret) - return ret; + goto err_disable_clks; if (priv->vbus) { ret = regulator_enable(priv->vbus); if (ret) - return ret; + goto err_disable_clks; } /* Get dr_mode */ priv->otg_mode = usb_get_dr_mode(dev); - if (priv->otg_mode == USB_DR_MODE_OTG) { - /* Ack irq before registering */ - regmap_update_bits(priv->regmap, USB_R5, - USB_R5_ID_DIG_IRQ, 0); - - irq = platform_get_irq(pdev, 0); - ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, - dwc3_meson_g12a_irq_thread, - IRQF_ONESHOT, pdev->name, priv); - if (ret) - return ret; - } - dwc3_meson_g12a_usb_init(priv); /* Init PHYs */ for (i = 0 ; i < PHY_COUNT ; ++i) { ret = phy_init(priv->phys[i]); if (ret) - return ret; + goto err_disable_clks; } /* Set PHY Power */ @@ -478,31 +542,12 @@ static int dwc3_meson_g12a_probe(struct platform_device *pdev) } ret = of_platform_populate(np, NULL, NULL, dev); - if (ret) { - clk_disable_unprepare(priv->clk); + if (ret) goto err_phys_power; - } - /* Setup OTG mode corresponding to the ID pin */ - if (priv->otg_mode == USB_DR_MODE_OTG) { - otg_id = dwc3_meson_g12a_get_id(priv); - if (otg_id != priv->otg_phy_mode) { - if (dwc3_meson_g12a_otg_mode_set(priv, otg_id)) - dev_warn(dev, "Failed to switch OTG mode\n"); - } - } - - /* Setup role switcher */ - priv->switch_desc.usb2_port = dwc3_meson_g12_find_child(dev, - "snps,dwc3"); - priv->switch_desc.udc = dwc3_meson_g12_find_child(dev, "snps,dwc2"); - priv->switch_desc.allow_userspace_control = true; - priv->switch_desc.set = dwc3_meson_g12a_role_set; - priv->switch_desc.get = dwc3_meson_g12a_role_get; - - priv->role_switch = usb_role_switch_register(dev, &priv->switch_desc); - if (IS_ERR(priv->role_switch)) - dev_warn(dev, "Unable to register Role Switch\n"); + ret = dwc3_meson_g12a_otg_init(pdev, priv); + if (ret) + goto err_phys_power; pm_runtime_set_active(dev); pm_runtime_enable(dev); @@ -518,6 +563,10 @@ err_phys_exit: for (i = 0 ; i < PHY_COUNT ; ++i) phy_exit(priv->phys[i]); +err_disable_clks: + clk_bulk_disable_unprepare(priv->drvdata->num_clks, + priv->drvdata->clks); + return ret; } @@ -527,7 +576,8 @@ static int dwc3_meson_g12a_remove(struct platform_device *pdev) struct device *dev = &pdev->dev; int i; - usb_role_switch_unregister(priv->role_switch); + if (priv->drvdata->otg_switch_supported) + usb_role_switch_unregister(priv->role_switch); of_platform_depopulate(dev); @@ -540,6 +590,9 @@ static int dwc3_meson_g12a_remove(struct platform_device *pdev) pm_runtime_put_noidle(dev); pm_runtime_set_suspended(dev); + clk_bulk_disable_unprepare(priv->drvdata->num_clks, + priv->drvdata->clks); + return 0; } @@ -547,7 +600,8 @@ static int __maybe_unused dwc3_meson_g12a_runtime_suspend(struct device *dev) { struct dwc3_meson_g12a *priv = dev_get_drvdata(dev); - clk_disable(priv->clk); + clk_bulk_disable_unprepare(priv->drvdata->num_clks, + priv->drvdata->clks); return 0; } @@ -556,7 +610,8 @@ static int __maybe_unused dwc3_meson_g12a_runtime_resume(struct device *dev) { struct dwc3_meson_g12a *priv = dev_get_drvdata(dev); - return clk_enable(priv->clk); + return clk_bulk_prepare_enable(priv->drvdata->num_clks, + priv->drvdata->clks); } static int __maybe_unused dwc3_meson_g12a_suspend(struct device *dev) @@ -619,7 +674,14 @@ static const struct dev_pm_ops dwc3_meson_g12a_dev_pm_ops = { }; static const struct of_device_id dwc3_meson_g12a_match[] = { - { .compatible = "amlogic,meson-g12a-usb-ctrl" }, + { + .compatible = "amlogic,meson-g12a-usb-ctrl", + .data = &g12a_drvdata, + }, + { + .compatible = "amlogic,meson-a1-usb-ctrl", + .data = &a1_drvdata, + }, { /* Sentinel */ } }; MODULE_DEVICE_TABLE(of, dwc3_meson_g12a_match); diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index 261af9e38ddd..1dfd024cd06b 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -9,7 +9,7 @@ #include <linux/of.h> #include <linux/clk.h> #include <linux/irq.h> -#include <linux/clk-provider.h> +#include <linux/of_clk.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/extcon.h> diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 1e00bf2d65a2..4d3c79d90a6e 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1521,7 +1521,7 @@ static void dwc3_gadget_ep_skip_trbs(struct dwc3_ep *dep, struct dwc3_request *r for (i = 0; i < req->num_trbs; i++) { struct dwc3_trb *trb; - trb = req->trb + i; + trb = &dep->trb_pool[dep->trb_dequeue]; trb->ctrl &= ~DWC3_TRB_CTRL_HWO; dwc3_ep_inc_deq(dep); } @@ -2570,10 +2570,8 @@ static void dwc3_gadget_endpoint_transfer_in_progress(struct dwc3_ep *dep, dwc3_gadget_ep_cleanup_completed_requests(dep, event, status); - if (stop) { + if (stop) dwc3_stop_active_transfer(dep, true, true); - dep->flags = DWC3_EP_ENABLED; - } /* * WORKAROUND: This is the 2nd half of U1/U2 -> U0 workaround. diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c index fa252870c926..86dbd012b984 100644 --- a/drivers/usb/dwc3/host.c +++ b/drivers/usb/dwc3/host.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 -/** +/* * host.c - DesignWare USB3 DRD Controller Host Glue * * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com @@ -7,6 +7,7 @@ * Authors: Felipe Balbi <balbi@ti.com>, */ +#include <linux/acpi.h> #include <linux/platform_device.h> #include "core.h" @@ -75,6 +76,7 @@ int dwc3_host_init(struct dwc3 *dwc) } xhci->dev.parent = dwc->dev; + ACPI_COMPANION_SET(&xhci->dev, ACPI_COMPANION(dwc->dev)); dwc->xhci = xhci; diff --git a/drivers/usb/dwc3/trace.h b/drivers/usb/dwc3/trace.h index 9edff17111f7..3054b89512ff 100644 --- a/drivers/usb/dwc3/trace.h +++ b/drivers/usb/dwc3/trace.h @@ -227,6 +227,8 @@ DECLARE_EVENT_CLASS(dwc3_log_trb, __field(u32, size) __field(u32, ctrl) __field(u32, type) + __field(u32, enqueue) + __field(u32, dequeue) ), TP_fast_assign( __assign_str(name, dep->name); @@ -236,9 +238,12 @@ DECLARE_EVENT_CLASS(dwc3_log_trb, __entry->size = trb->size; __entry->ctrl = trb->ctrl; __entry->type = usb_endpoint_type(dep->endpoint.desc); + __entry->enqueue = dep->trb_enqueue; + __entry->dequeue = dep->trb_dequeue; ), - TP_printk("%s: trb %p buf %08x%08x size %s%d ctrl %08x (%c%c%c%c:%c%c:%s)", - __get_str(name), __entry->trb, __entry->bph, __entry->bpl, + TP_printk("%s: trb %p (E%d:D%d) buf %08x%08x size %s%d ctrl %08x (%c%c%c%c:%c%c:%s)", + __get_str(name), __entry->trb, __entry->enqueue, + __entry->dequeue, __entry->bph, __entry->bpl, ({char *s; int pcm = ((__entry->size >> 24) & 3) + 1; switch (__entry->type) { diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 223f72d4d9ed..cb4950cf1cdc 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -861,6 +861,11 @@ static int set_config(struct usb_composite_dev *cdev, else power = min(power, 900U); done: + if (power <= USB_SELF_POWER_VBUS_MAX_DRAW) + usb_gadget_set_selfpowered(gadget); + else + usb_gadget_clear_selfpowered(gadget); + usb_gadget_vbus_draw(gadget, power); if (result >= 0 && cdev->delayed_status) result = USB_GADGET_DELAYED_STATUS; @@ -2279,6 +2284,7 @@ void composite_suspend(struct usb_gadget *gadget) cdev->suspended = 1; + usb_gadget_set_selfpowered(gadget); usb_gadget_vbus_draw(gadget, 2); } @@ -2307,6 +2313,9 @@ void composite_resume(struct usb_gadget *gadget) else maxpower = min(maxpower, 900U); + if (maxpower > USB_SELF_POWER_VBUS_MAX_DRAW) + usb_gadget_clear_selfpowered(gadget); + usb_gadget_vbus_draw(gadget, maxpower); } diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 571917677d35..767f30b86645 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -1120,6 +1120,7 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) ret = usb_ep_queue(ep->ep, req, GFP_ATOMIC); if (unlikely(ret)) { + io_data->req = NULL; usb_ep_free_request(ep->ep, req); goto error_lock; } diff --git a/drivers/usb/gadget/function/f_phonet.c b/drivers/usb/gadget/function/f_phonet.c index 8b72b192c747..d7f6cc51b7ec 100644 --- a/drivers/usb/gadget/function/f_phonet.c +++ b/drivers/usb/gadget/function/f_phonet.c @@ -48,7 +48,7 @@ struct f_phonet { struct usb_ep *in_ep, *out_ep; struct usb_request *in_req; - struct usb_request *out_reqv[0]; + struct usb_request *out_reqv[]; }; static int phonet_rxq_size = 17; diff --git a/drivers/usb/gadget/function/f_uac1_legacy.c b/drivers/usb/gadget/function/f_uac1_legacy.c index 6677ae932de0..349deae7cabd 100644 --- a/drivers/usb/gadget/function/f_uac1_legacy.c +++ b/drivers/usb/gadget/function/f_uac1_legacy.c @@ -752,8 +752,6 @@ f_audio_bind(struct usb_configuration *c, struct usb_function *f) audio->out_ep = ep; audio->out_ep->desc = &as_out_ep_desc; - status = -ENOMEM; - /* copy descriptors, and track endpoint copies */ status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL, NULL); diff --git a/drivers/usb/gadget/legacy/Kconfig b/drivers/usb/gadget/legacy/Kconfig index 6e7e1a9202e6..f02c38b32a2b 100644 --- a/drivers/usb/gadget/legacy/Kconfig +++ b/drivers/usb/gadget/legacy/Kconfig @@ -13,32 +13,28 @@ # With help from a special transceiver and a "Mini-AB" jack, systems with # both kinds of controller can also support "USB On-the-Go" (CONFIG_USB_OTG). # +# A Linux "Gadget Driver" talks to the USB Peripheral Controller +# driver through the abstract "gadget" API. Some other operating +# systems call these "client" drivers, of which "class drivers" +# are a subset (implementing a USB device class specification). +# A gadget driver implements one or more USB functions using +# the peripheral hardware. +# +# Gadget drivers are hardware-neutral, or "platform independent", +# except that they sometimes must understand quirks or limitations +# of the particular controllers they work with. For example, when +# a controller doesn't support alternate configurations or provide +# enough of the right types of endpoints, the gadget driver might +# not be able work with that controller, or might need to implement +# a less common variant of a device class protocol. +# +# The available choices each represent a single precomposed USB +# gadget configuration. In the device model, each option contains +# both the device instantiation as a child for a USB gadget +# controller, and the relevant drivers for each function declared +# by the device. -choice - tristate "USB Gadget precomposed configurations" - default USB_ETH - optional - help - A Linux "Gadget Driver" talks to the USB Peripheral Controller - driver through the abstract "gadget" API. Some other operating - systems call these "client" drivers, of which "class drivers" - are a subset (implementing a USB device class specification). - A gadget driver implements one or more USB functions using - the peripheral hardware. - - Gadget drivers are hardware-neutral, or "platform independent", - except that they sometimes must understand quirks or limitations - of the particular controllers they work with. For example, when - a controller doesn't support alternate configurations or provide - enough of the right types of endpoints, the gadget driver might - not be able work with that controller, or might need to implement - a less common variant of a device class protocol. - - The available choices each represent a single precomposed USB - gadget configuration. In the device model, each option contains - both the device instantiation as a child for a USB gadget - controller, and the relevant drivers for each function declared - by the device. +menu "USB Gadget precomposed configurations" config USB_ZERO tristate "Gadget Zero (DEVELOPMENT)" @@ -516,4 +512,15 @@ config USB_G_WEBCAM Say "y" to link the driver statically, or "m" to build a dynamically linked module called "g_webcam". -endchoice +config USB_RAW_GADGET + tristate "USB Raw Gadget" + help + USB Raw Gadget is a kernel module that provides a userspace interface + for the USB Gadget subsystem. Essentially it allows to emulate USB + devices from userspace. See Documentation/usb/raw-gadget.rst for + details. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "raw_gadget". + +endmenu diff --git a/drivers/usb/gadget/legacy/Makefile b/drivers/usb/gadget/legacy/Makefile index abd0c3e66a05..4d864bf82799 100644 --- a/drivers/usb/gadget/legacy/Makefile +++ b/drivers/usb/gadget/legacy/Makefile @@ -43,3 +43,4 @@ obj-$(CONFIG_USB_G_WEBCAM) += g_webcam.o obj-$(CONFIG_USB_G_NCM) += g_ncm.o obj-$(CONFIG_USB_G_ACM_MS) += g_acm_ms.o obj-$(CONFIG_USB_GADGET_TARGET) += tcm_usb_gadget.o +obj-$(CONFIG_USB_RAW_GADGET) += raw_gadget.o diff --git a/drivers/usb/gadget/legacy/gmidi.c b/drivers/usb/gadget/legacy/gmidi.c index 9eea2d18f2bf..265c392810d7 100644 --- a/drivers/usb/gadget/legacy/gmidi.c +++ b/drivers/usb/gadget/legacy/gmidi.c @@ -174,7 +174,7 @@ put: } static struct usb_composite_driver midi_driver = { - .name = (char *) longname, + .name = longname, .dev = &device_desc, .strings = dev_strings, .max_speed = USB_SPEED_HIGH, diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c index b47938dff1a2..e3dfc2180555 100644 --- a/drivers/usb/gadget/legacy/inode.c +++ b/drivers/usb/gadget/legacy/inode.c @@ -1736,7 +1736,7 @@ static struct usb_gadget_driver gadgetfs_driver = { .suspend = gadgetfs_suspend, .driver = { - .name = (char *) shortname, + .name = shortname, }, }; diff --git a/drivers/usb/gadget/legacy/raw_gadget.c b/drivers/usb/gadget/legacy/raw_gadget.c new file mode 100644 index 000000000000..76406343fbe5 --- /dev/null +++ b/drivers/usb/gadget/legacy/raw_gadget.c @@ -0,0 +1,1078 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Raw Gadget driver. + * See Documentation/usb/raw-gadget.rst for more details. + * + * Andrey Konovalov <andreyknvl@gmail.com> + */ + +#include <linux/compiler.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/kref.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/semaphore.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/wait.h> + +#include <linux/usb.h> +#include <linux/usb/ch9.h> +#include <linux/usb/ch11.h> +#include <linux/usb/gadget.h> + +#include <uapi/linux/usb/raw_gadget.h> + +#define DRIVER_DESC "USB Raw Gadget" +#define DRIVER_NAME "raw-gadget" + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Andrey Konovalov"); +MODULE_LICENSE("GPL"); + +/*----------------------------------------------------------------------*/ + +#define RAW_EVENT_QUEUE_SIZE 16 + +struct raw_event_queue { + /* See the comment in raw_event_queue_fetch() for locking details. */ + spinlock_t lock; + struct semaphore sema; + struct usb_raw_event *events[RAW_EVENT_QUEUE_SIZE]; + int size; +}; + +static void raw_event_queue_init(struct raw_event_queue *queue) +{ + spin_lock_init(&queue->lock); + sema_init(&queue->sema, 0); + queue->size = 0; +} + +static int raw_event_queue_add(struct raw_event_queue *queue, + enum usb_raw_event_type type, size_t length, const void *data) +{ + unsigned long flags; + struct usb_raw_event *event; + + spin_lock_irqsave(&queue->lock, flags); + if (WARN_ON(queue->size >= RAW_EVENT_QUEUE_SIZE)) { + spin_unlock_irqrestore(&queue->lock, flags); + return -ENOMEM; + } + event = kmalloc(sizeof(*event) + length, GFP_ATOMIC); + if (!event) { + spin_unlock_irqrestore(&queue->lock, flags); + return -ENOMEM; + } + event->type = type; + event->length = length; + if (event->length) + memcpy(&event->data[0], data, length); + queue->events[queue->size] = event; + queue->size++; + up(&queue->sema); + spin_unlock_irqrestore(&queue->lock, flags); + return 0; +} + +static struct usb_raw_event *raw_event_queue_fetch( + struct raw_event_queue *queue) +{ + unsigned long flags; + struct usb_raw_event *event; + + /* + * This function can be called concurrently. We first check that + * there's at least one event queued by decrementing the semaphore, + * and then take the lock to protect queue struct fields. + */ + if (down_interruptible(&queue->sema)) + return NULL; + spin_lock_irqsave(&queue->lock, flags); + if (WARN_ON(!queue->size)) + return NULL; + event = queue->events[0]; + queue->size--; + memmove(&queue->events[0], &queue->events[1], + queue->size * sizeof(queue->events[0])); + spin_unlock_irqrestore(&queue->lock, flags); + return event; +} + +static void raw_event_queue_destroy(struct raw_event_queue *queue) +{ + int i; + + for (i = 0; i < queue->size; i++) + kfree(queue->events[i]); + queue->size = 0; +} + +/*----------------------------------------------------------------------*/ + +struct raw_dev; + +#define USB_RAW_MAX_ENDPOINTS 32 + +enum ep_state { + STATE_EP_DISABLED, + STATE_EP_ENABLED, +}; + +struct raw_ep { + struct raw_dev *dev; + enum ep_state state; + struct usb_ep *ep; + struct usb_request *req; + bool urb_queued; + bool disabling; + ssize_t status; +}; + +enum dev_state { + STATE_DEV_INVALID = 0, + STATE_DEV_OPENED, + STATE_DEV_INITIALIZED, + STATE_DEV_RUNNING, + STATE_DEV_CLOSED, + STATE_DEV_FAILED +}; + +struct raw_dev { + struct kref count; + spinlock_t lock; + + const char *udc_name; + struct usb_gadget_driver driver; + + /* Reference to misc device: */ + struct device *dev; + + /* Protected by lock: */ + enum dev_state state; + bool gadget_registered; + struct usb_gadget *gadget; + struct usb_request *req; + bool ep0_in_pending; + bool ep0_out_pending; + bool ep0_urb_queued; + ssize_t ep0_status; + struct raw_ep eps[USB_RAW_MAX_ENDPOINTS]; + + struct completion ep0_done; + struct raw_event_queue queue; +}; + +static struct raw_dev *dev_new(void) +{ + struct raw_dev *dev; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return NULL; + /* Matches kref_put() in raw_release(). */ + kref_init(&dev->count); + spin_lock_init(&dev->lock); + init_completion(&dev->ep0_done); + raw_event_queue_init(&dev->queue); + return dev; +} + +static void dev_free(struct kref *kref) +{ + struct raw_dev *dev = container_of(kref, struct raw_dev, count); + int i; + + kfree(dev->udc_name); + kfree(dev->driver.udc_name); + if (dev->req) { + if (dev->ep0_urb_queued) + usb_ep_dequeue(dev->gadget->ep0, dev->req); + usb_ep_free_request(dev->gadget->ep0, dev->req); + } + raw_event_queue_destroy(&dev->queue); + for (i = 0; i < USB_RAW_MAX_ENDPOINTS; i++) { + if (dev->eps[i].state != STATE_EP_ENABLED) + continue; + usb_ep_disable(dev->eps[i].ep); + usb_ep_free_request(dev->eps[i].ep, dev->eps[i].req); + kfree(dev->eps[i].ep->desc); + dev->eps[i].state = STATE_EP_DISABLED; + } + kfree(dev); +} + +/*----------------------------------------------------------------------*/ + +static int raw_queue_event(struct raw_dev *dev, + enum usb_raw_event_type type, size_t length, const void *data) +{ + int ret = 0; + unsigned long flags; + + ret = raw_event_queue_add(&dev->queue, type, length, data); + if (ret < 0) { + spin_lock_irqsave(&dev->lock, flags); + dev->state = STATE_DEV_FAILED; + spin_unlock_irqrestore(&dev->lock, flags); + } + return ret; +} + +static void gadget_ep0_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct raw_dev *dev = req->context; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (req->status) + dev->ep0_status = req->status; + else + dev->ep0_status = req->actual; + if (dev->ep0_in_pending) + dev->ep0_in_pending = false; + else + dev->ep0_out_pending = false; + spin_unlock_irqrestore(&dev->lock, flags); + + complete(&dev->ep0_done); +} + +static int gadget_bind(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + int ret = 0; + struct raw_dev *dev = container_of(driver, struct raw_dev, driver); + struct usb_request *req; + unsigned long flags; + + if (strcmp(gadget->name, dev->udc_name) != 0) + return -ENODEV; + + set_gadget_data(gadget, dev); + req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL); + if (!req) { + dev_err(&gadget->dev, "usb_ep_alloc_request failed\n"); + set_gadget_data(gadget, NULL); + return -ENOMEM; + } + + spin_lock_irqsave(&dev->lock, flags); + dev->req = req; + dev->req->context = dev; + dev->req->complete = gadget_ep0_complete; + dev->gadget = gadget; + spin_unlock_irqrestore(&dev->lock, flags); + + /* Matches kref_put() in gadget_unbind(). */ + kref_get(&dev->count); + + ret = raw_queue_event(dev, USB_RAW_EVENT_CONNECT, 0, NULL); + if (ret < 0) + dev_err(&gadget->dev, "failed to queue event\n"); + + return ret; +} + +static void gadget_unbind(struct usb_gadget *gadget) +{ + struct raw_dev *dev = get_gadget_data(gadget); + + set_gadget_data(gadget, NULL); + /* Matches kref_get() in gadget_bind(). */ + kref_put(&dev->count, dev_free); +} + +static int gadget_setup(struct usb_gadget *gadget, + const struct usb_ctrlrequest *ctrl) +{ + int ret = 0; + struct raw_dev *dev = get_gadget_data(gadget); + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_err(&gadget->dev, "ignoring, device is not running\n"); + ret = -ENODEV; + goto out_unlock; + } + if (dev->ep0_in_pending || dev->ep0_out_pending) { + dev_dbg(&gadget->dev, "stalling, request already pending\n"); + ret = -EBUSY; + goto out_unlock; + } + if ((ctrl->bRequestType & USB_DIR_IN) && ctrl->wLength) + dev->ep0_in_pending = true; + else + dev->ep0_out_pending = true; + spin_unlock_irqrestore(&dev->lock, flags); + + ret = raw_queue_event(dev, USB_RAW_EVENT_CONTROL, sizeof(*ctrl), ctrl); + if (ret < 0) + dev_err(&gadget->dev, "failed to queue event\n"); + goto out; + +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); +out: + return ret; +} + +/* These are currently unused but present in case UDC driver requires them. */ +static void gadget_disconnect(struct usb_gadget *gadget) { } +static void gadget_suspend(struct usb_gadget *gadget) { } +static void gadget_resume(struct usb_gadget *gadget) { } +static void gadget_reset(struct usb_gadget *gadget) { } + +/*----------------------------------------------------------------------*/ + +static struct miscdevice raw_misc_device; + +static int raw_open(struct inode *inode, struct file *fd) +{ + struct raw_dev *dev; + + /* Nonblocking I/O is not supported yet. */ + if (fd->f_flags & O_NONBLOCK) + return -EINVAL; + + dev = dev_new(); + if (!dev) + return -ENOMEM; + fd->private_data = dev; + dev->state = STATE_DEV_OPENED; + dev->dev = raw_misc_device.this_device; + return 0; +} + +static int raw_release(struct inode *inode, struct file *fd) +{ + int ret = 0; + struct raw_dev *dev = fd->private_data; + unsigned long flags; + bool unregister = false; + + spin_lock_irqsave(&dev->lock, flags); + dev->state = STATE_DEV_CLOSED; + if (!dev->gadget) { + spin_unlock_irqrestore(&dev->lock, flags); + goto out_put; + } + if (dev->gadget_registered) + unregister = true; + dev->gadget_registered = false; + spin_unlock_irqrestore(&dev->lock, flags); + + if (unregister) { + ret = usb_gadget_unregister_driver(&dev->driver); + if (ret != 0) + dev_err(dev->dev, + "usb_gadget_unregister_driver() failed with %d\n", + ret); + /* Matches kref_get() in raw_ioctl_run(). */ + kref_put(&dev->count, dev_free); + } + +out_put: + /* Matches dev_new() in raw_open(). */ + kref_put(&dev->count, dev_free); + return ret; +} + +/*----------------------------------------------------------------------*/ + +static int raw_ioctl_init(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + struct usb_raw_init arg; + char *udc_driver_name; + char *udc_device_name; + unsigned long flags; + + ret = copy_from_user(&arg, (void __user *)value, sizeof(arg)); + if (ret) + return ret; + + switch (arg.speed) { + case USB_SPEED_UNKNOWN: + arg.speed = USB_SPEED_HIGH; + break; + case USB_SPEED_LOW: + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + case USB_SPEED_SUPER: + break; + default: + return -EINVAL; + } + + udc_driver_name = kmalloc(UDC_NAME_LENGTH_MAX, GFP_KERNEL); + if (!udc_driver_name) + return -ENOMEM; + ret = strscpy(udc_driver_name, &arg.driver_name[0], + UDC_NAME_LENGTH_MAX); + if (ret < 0) { + kfree(udc_driver_name); + return ret; + } + ret = 0; + + udc_device_name = kmalloc(UDC_NAME_LENGTH_MAX, GFP_KERNEL); + if (!udc_device_name) { + kfree(udc_driver_name); + return -ENOMEM; + } + ret = strscpy(udc_device_name, &arg.device_name[0], + UDC_NAME_LENGTH_MAX); + if (ret < 0) { + kfree(udc_driver_name); + kfree(udc_device_name); + return ret; + } + ret = 0; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_OPENED) { + dev_dbg(dev->dev, "fail, device is not opened\n"); + kfree(udc_driver_name); + kfree(udc_device_name); + ret = -EINVAL; + goto out_unlock; + } + dev->udc_name = udc_driver_name; + + dev->driver.function = DRIVER_DESC; + dev->driver.max_speed = arg.speed; + dev->driver.setup = gadget_setup; + dev->driver.disconnect = gadget_disconnect; + dev->driver.bind = gadget_bind; + dev->driver.unbind = gadget_unbind; + dev->driver.suspend = gadget_suspend; + dev->driver.resume = gadget_resume; + dev->driver.reset = gadget_reset; + dev->driver.driver.name = DRIVER_NAME; + dev->driver.udc_name = udc_device_name; + dev->driver.match_existing_only = 1; + + dev->state = STATE_DEV_INITIALIZED; + +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static int raw_ioctl_run(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + unsigned long flags; + + if (value) + return -EINVAL; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_INITIALIZED) { + dev_dbg(dev->dev, "fail, device is not initialized\n"); + ret = -EINVAL; + goto out_unlock; + } + spin_unlock_irqrestore(&dev->lock, flags); + + ret = usb_gadget_probe_driver(&dev->driver); + + spin_lock_irqsave(&dev->lock, flags); + if (ret) { + dev_err(dev->dev, + "fail, usb_gadget_probe_driver returned %d\n", ret); + dev->state = STATE_DEV_FAILED; + goto out_unlock; + } + dev->gadget_registered = true; + dev->state = STATE_DEV_RUNNING; + /* Matches kref_put() in raw_release(). */ + kref_get(&dev->count); + +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static int raw_ioctl_event_fetch(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + struct usb_raw_event arg; + unsigned long flags; + struct usb_raw_event *event; + uint32_t length; + + ret = copy_from_user(&arg, (void __user *)value, sizeof(arg)); + if (ret) + return ret; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + spin_unlock_irqrestore(&dev->lock, flags); + return -EINVAL; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + spin_unlock_irqrestore(&dev->lock, flags); + return -EBUSY; + } + spin_unlock_irqrestore(&dev->lock, flags); + + event = raw_event_queue_fetch(&dev->queue); + if (!event) { + dev_dbg(&dev->gadget->dev, "event fetching interrupted\n"); + return -EINTR; + } + length = min(arg.length, event->length); + ret = copy_to_user((void __user *)value, event, + sizeof(*event) + length); + return ret; +} + +static void *raw_alloc_io_data(struct usb_raw_ep_io *io, void __user *ptr, + bool get_from_user) +{ + int ret; + void *data; + + ret = copy_from_user(io, ptr, sizeof(*io)); + if (ret) + return ERR_PTR(ret); + if (io->ep >= USB_RAW_MAX_ENDPOINTS) + return ERR_PTR(-EINVAL); + if (!usb_raw_io_flags_valid(io->flags)) + return ERR_PTR(-EINVAL); + if (io->length > PAGE_SIZE) + return ERR_PTR(-EINVAL); + if (get_from_user) + data = memdup_user(ptr + sizeof(*io), io->length); + else { + data = kmalloc(io->length, GFP_KERNEL); + if (!data) + data = ERR_PTR(-ENOMEM); + } + return data; +} + +static int raw_process_ep0_io(struct raw_dev *dev, struct usb_raw_ep_io *io, + void *data, bool in) +{ + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + ret = -EINVAL; + goto out_unlock; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + ret = -EBUSY; + goto out_unlock; + } + if (dev->ep0_urb_queued) { + dev_dbg(&dev->gadget->dev, "fail, urb already queued\n"); + ret = -EBUSY; + goto out_unlock; + } + if ((in && !dev->ep0_in_pending) || + (!in && !dev->ep0_out_pending)) { + dev_dbg(&dev->gadget->dev, "fail, wrong direction\n"); + ret = -EBUSY; + goto out_unlock; + } + if (WARN_ON(in && dev->ep0_out_pending)) { + ret = -ENODEV; + dev->state = STATE_DEV_FAILED; + goto out_done; + } + if (WARN_ON(!in && dev->ep0_in_pending)) { + ret = -ENODEV; + dev->state = STATE_DEV_FAILED; + goto out_done; + } + + dev->req->buf = data; + dev->req->length = io->length; + dev->req->zero = usb_raw_io_flags_zero(io->flags); + dev->ep0_urb_queued = true; + spin_unlock_irqrestore(&dev->lock, flags); + + ret = usb_ep_queue(dev->gadget->ep0, dev->req, GFP_KERNEL); + if (ret) { + dev_err(&dev->gadget->dev, + "fail, usb_ep_queue returned %d\n", ret); + spin_lock_irqsave(&dev->lock, flags); + dev->state = STATE_DEV_FAILED; + goto out_done; + } + + ret = wait_for_completion_interruptible(&dev->ep0_done); + if (ret) { + dev_dbg(&dev->gadget->dev, "wait interrupted\n"); + usb_ep_dequeue(dev->gadget->ep0, dev->req); + wait_for_completion(&dev->ep0_done); + spin_lock_irqsave(&dev->lock, flags); + goto out_done; + } + + spin_lock_irqsave(&dev->lock, flags); + ret = dev->ep0_status; + +out_done: + dev->ep0_urb_queued = false; +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static int raw_ioctl_ep0_write(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + void *data; + struct usb_raw_ep_io io; + + data = raw_alloc_io_data(&io, (void __user *)value, true); + if (IS_ERR(data)) + return PTR_ERR(data); + ret = raw_process_ep0_io(dev, &io, data, true); + kfree(data); + return ret; +} + +static int raw_ioctl_ep0_read(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + void *data; + struct usb_raw_ep_io io; + unsigned int length; + + data = raw_alloc_io_data(&io, (void __user *)value, false); + if (IS_ERR(data)) + return PTR_ERR(data); + ret = raw_process_ep0_io(dev, &io, data, false); + if (ret < 0) { + kfree(data); + return ret; + } + length = min(io.length, (unsigned int)ret); + ret = copy_to_user((void __user *)(value + sizeof(io)), data, length); + kfree(data); + return ret; +} + +static bool check_ep_caps(struct usb_ep *ep, + struct usb_endpoint_descriptor *desc) +{ + switch (usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_ISOC: + if (!ep->caps.type_iso) + return false; + break; + case USB_ENDPOINT_XFER_BULK: + if (!ep->caps.type_bulk) + return false; + break; + case USB_ENDPOINT_XFER_INT: + if (!ep->caps.type_int) + return false; + break; + default: + return false; + } + + if (usb_endpoint_dir_in(desc) && !ep->caps.dir_in) + return false; + if (usb_endpoint_dir_out(desc) && !ep->caps.dir_out) + return false; + + return true; +} + +static int raw_ioctl_ep_enable(struct raw_dev *dev, unsigned long value) +{ + int ret = 0, i; + unsigned long flags; + struct usb_endpoint_descriptor *desc; + struct usb_ep *ep = NULL; + + desc = memdup_user((void __user *)value, sizeof(*desc)); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + /* + * Endpoints with a maxpacket length of 0 can cause crashes in UDC + * drivers. + */ + if (usb_endpoint_maxp(desc) == 0) { + dev_dbg(dev->dev, "fail, bad endpoint maxpacket\n"); + kfree(desc); + return -EINVAL; + } + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + ret = -EINVAL; + goto out_free; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + ret = -EBUSY; + goto out_free; + } + + for (i = 0; i < USB_RAW_MAX_ENDPOINTS; i++) { + if (dev->eps[i].state == STATE_EP_ENABLED) + continue; + break; + } + if (i == USB_RAW_MAX_ENDPOINTS) { + dev_dbg(&dev->gadget->dev, + "fail, no device endpoints available\n"); + ret = -EBUSY; + goto out_free; + } + + gadget_for_each_ep(ep, dev->gadget) { + if (ep->enabled) + continue; + if (!check_ep_caps(ep, desc)) + continue; + ep->desc = desc; + ret = usb_ep_enable(ep); + if (ret < 0) { + dev_err(&dev->gadget->dev, + "fail, usb_ep_enable returned %d\n", ret); + goto out_free; + } + dev->eps[i].req = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (!dev->eps[i].req) { + dev_err(&dev->gadget->dev, + "fail, usb_ep_alloc_request failed\n"); + usb_ep_disable(ep); + ret = -ENOMEM; + goto out_free; + } + dev->eps[i].ep = ep; + dev->eps[i].state = STATE_EP_ENABLED; + ep->driver_data = &dev->eps[i]; + ret = i; + goto out_unlock; + } + + dev_dbg(&dev->gadget->dev, "fail, no gadget endpoints available\n"); + ret = -EBUSY; + +out_free: + kfree(desc); +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static int raw_ioctl_ep_disable(struct raw_dev *dev, unsigned long value) +{ + int ret = 0, i = value; + unsigned long flags; + const void *desc; + + if (i < 0 || i >= USB_RAW_MAX_ENDPOINTS) + return -EINVAL; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + ret = -EINVAL; + goto out_unlock; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + ret = -EBUSY; + goto out_unlock; + } + if (dev->eps[i].state != STATE_EP_ENABLED) { + dev_dbg(&dev->gadget->dev, "fail, endpoint is not enabled\n"); + ret = -EINVAL; + goto out_unlock; + } + if (dev->eps[i].disabling) { + dev_dbg(&dev->gadget->dev, + "fail, disable already in progress\n"); + ret = -EINVAL; + goto out_unlock; + } + if (dev->eps[i].urb_queued) { + dev_dbg(&dev->gadget->dev, + "fail, waiting for urb completion\n"); + ret = -EINVAL; + goto out_unlock; + } + dev->eps[i].disabling = true; + spin_unlock_irqrestore(&dev->lock, flags); + + usb_ep_disable(dev->eps[i].ep); + + spin_lock_irqsave(&dev->lock, flags); + usb_ep_free_request(dev->eps[i].ep, dev->eps[i].req); + desc = dev->eps[i].ep->desc; + dev->eps[i].ep = NULL; + dev->eps[i].state = STATE_EP_DISABLED; + kfree(desc); + dev->eps[i].disabling = false; + +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static void gadget_ep_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct raw_ep *r_ep = (struct raw_ep *)ep->driver_data; + struct raw_dev *dev = r_ep->dev; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (req->status) + r_ep->status = req->status; + else + r_ep->status = req->actual; + spin_unlock_irqrestore(&dev->lock, flags); + + complete((struct completion *)req->context); +} + +static int raw_process_ep_io(struct raw_dev *dev, struct usb_raw_ep_io *io, + void *data, bool in) +{ + int ret = 0; + unsigned long flags; + struct raw_ep *ep = &dev->eps[io->ep]; + DECLARE_COMPLETION_ONSTACK(done); + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + ret = -EINVAL; + goto out_unlock; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + ret = -EBUSY; + goto out_unlock; + } + if (ep->state != STATE_EP_ENABLED) { + dev_dbg(&dev->gadget->dev, "fail, endpoint is not enabled\n"); + ret = -EBUSY; + goto out_unlock; + } + if (ep->disabling) { + dev_dbg(&dev->gadget->dev, + "fail, endpoint is already being disabled\n"); + ret = -EBUSY; + goto out_unlock; + } + if (ep->urb_queued) { + dev_dbg(&dev->gadget->dev, "fail, urb already queued\n"); + ret = -EBUSY; + goto out_unlock; + } + if ((in && !ep->ep->caps.dir_in) || (!in && ep->ep->caps.dir_in)) { + dev_dbg(&dev->gadget->dev, "fail, wrong direction\n"); + ret = -EINVAL; + goto out_unlock; + } + + ep->dev = dev; + ep->req->context = &done; + ep->req->complete = gadget_ep_complete; + ep->req->buf = data; + ep->req->length = io->length; + ep->req->zero = usb_raw_io_flags_zero(io->flags); + ep->urb_queued = true; + spin_unlock_irqrestore(&dev->lock, flags); + + ret = usb_ep_queue(ep->ep, ep->req, GFP_KERNEL); + if (ret) { + dev_err(&dev->gadget->dev, + "fail, usb_ep_queue returned %d\n", ret); + spin_lock_irqsave(&dev->lock, flags); + dev->state = STATE_DEV_FAILED; + goto out_done; + } + + ret = wait_for_completion_interruptible(&done); + if (ret) { + dev_dbg(&dev->gadget->dev, "wait interrupted\n"); + usb_ep_dequeue(ep->ep, ep->req); + wait_for_completion(&done); + spin_lock_irqsave(&dev->lock, flags); + goto out_done; + } + + spin_lock_irqsave(&dev->lock, flags); + ret = ep->status; + +out_done: + ep->urb_queued = false; +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static int raw_ioctl_ep_write(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + char *data; + struct usb_raw_ep_io io; + + data = raw_alloc_io_data(&io, (void __user *)value, true); + if (IS_ERR(data)) + return PTR_ERR(data); + ret = raw_process_ep_io(dev, &io, data, true); + kfree(data); + return ret; +} + +static int raw_ioctl_ep_read(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + char *data; + struct usb_raw_ep_io io; + unsigned int length; + + data = raw_alloc_io_data(&io, (void __user *)value, false); + if (IS_ERR(data)) + return PTR_ERR(data); + ret = raw_process_ep_io(dev, &io, data, false); + if (ret < 0) { + kfree(data); + return ret; + } + length = min(io.length, (unsigned int)ret); + ret = copy_to_user((void __user *)(value + sizeof(io)), data, length); + kfree(data); + return ret; +} + +static int raw_ioctl_configure(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + unsigned long flags; + + if (value) + return -EINVAL; + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + ret = -EINVAL; + goto out_unlock; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + ret = -EBUSY; + goto out_unlock; + } + usb_gadget_set_state(dev->gadget, USB_STATE_CONFIGURED); + +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static int raw_ioctl_vbus_draw(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + ret = -EINVAL; + goto out_unlock; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + ret = -EBUSY; + goto out_unlock; + } + usb_gadget_vbus_draw(dev->gadget, 2 * value); + +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static long raw_ioctl(struct file *fd, unsigned int cmd, unsigned long value) +{ + struct raw_dev *dev = fd->private_data; + int ret = 0; + + if (!dev) + return -EBUSY; + + switch (cmd) { + case USB_RAW_IOCTL_INIT: + ret = raw_ioctl_init(dev, value); + break; + case USB_RAW_IOCTL_RUN: + ret = raw_ioctl_run(dev, value); + break; + case USB_RAW_IOCTL_EVENT_FETCH: + ret = raw_ioctl_event_fetch(dev, value); + break; + case USB_RAW_IOCTL_EP0_WRITE: + ret = raw_ioctl_ep0_write(dev, value); + break; + case USB_RAW_IOCTL_EP0_READ: + ret = raw_ioctl_ep0_read(dev, value); + break; + case USB_RAW_IOCTL_EP_ENABLE: + ret = raw_ioctl_ep_enable(dev, value); + break; + case USB_RAW_IOCTL_EP_DISABLE: + ret = raw_ioctl_ep_disable(dev, value); + break; + case USB_RAW_IOCTL_EP_WRITE: + ret = raw_ioctl_ep_write(dev, value); + break; + case USB_RAW_IOCTL_EP_READ: + ret = raw_ioctl_ep_read(dev, value); + break; + case USB_RAW_IOCTL_CONFIGURE: + ret = raw_ioctl_configure(dev, value); + break; + case USB_RAW_IOCTL_VBUS_DRAW: + ret = raw_ioctl_vbus_draw(dev, value); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +/*----------------------------------------------------------------------*/ + +static const struct file_operations raw_fops = { + .open = raw_open, + .unlocked_ioctl = raw_ioctl, + .compat_ioctl = raw_ioctl, + .release = raw_release, + .llseek = no_llseek, +}; + +static struct miscdevice raw_misc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = DRIVER_NAME, + .fops = &raw_fops, +}; + +module_misc_device(raw_misc_device); diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index 797d6ace8994..3a7179e90f4e 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -441,11 +441,20 @@ config USB_GADGET_XILINX dynamically linked module called "udc-xilinx" and force all gadget drivers to also be dynamically linked. +config USB_MAX3420_UDC + tristate "MAX3420 (USB-over-SPI) support" + depends on SPI + help + The Maxim MAX3420 chip supports USB2.0 full-speed peripheral mode. + The MAX3420 is run by SPI interface, and hence the dependency. + + To compile this driver as a module, choose M here: the module will + be called max3420_udc + config USB_TEGRA_XUDC tristate "NVIDIA Tegra Superspeed USB 3.0 Device Controller" depends on ARCH_TEGRA || COMPILE_TEST depends on PHY_TEGRA_XUSB - select USB_ROLE_SWITCH help Enables NVIDIA Tegra USB 3.0 device mode controller driver. diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile index f6777e654a8e..f5a7ce28aecd 100644 --- a/drivers/usb/gadget/udc/Makefile +++ b/drivers/usb/gadget/udc/Makefile @@ -42,3 +42,4 @@ obj-$(CONFIG_USB_GADGET_XILINX) += udc-xilinx.o obj-$(CONFIG_USB_SNP_UDC_PLAT) += snps_udc_plat.o obj-$(CONFIG_USB_ASPEED_VHUB) += aspeed-vhub/ obj-$(CONFIG_USB_BDC_UDC) += bdc/ +obj-$(CONFIG_USB_MAX3420_UDC) += max3420_udc.o diff --git a/drivers/usb/gadget/udc/amd5536udc.h b/drivers/usb/gadget/udc/amd5536udc.h index dfdef6a28904..0262383f8c79 100644 --- a/drivers/usb/gadget/udc/amd5536udc.h +++ b/drivers/usb/gadget/udc/amd5536udc.h @@ -440,7 +440,7 @@ struct udc_ep_regs { /* endpoint data descriptor pointer */ u32 desptr; - /* reserverd */ + /* reserved */ u32 reserved; /* write/read confirmation */ diff --git a/drivers/usb/gadget/udc/amd5536udc_pci.c b/drivers/usb/gadget/udc/amd5536udc_pci.c index bfd1c9e80a1f..80685e4306f3 100644 --- a/drivers/usb/gadget/udc/amd5536udc_pci.c +++ b/drivers/usb/gadget/udc/amd5536udc_pci.c @@ -202,7 +202,7 @@ MODULE_DEVICE_TABLE(pci, pci_id); /* PCI functions */ static struct pci_driver udc_pci_driver = { - .name = (char *) name, + .name = name, .id_table = pci_id, .probe = udc_pci_probe, .remove = udc_pci_remove, diff --git a/drivers/usb/gadget/udc/aspeed-vhub/Kconfig b/drivers/usb/gadget/udc/aspeed-vhub/Kconfig index 83ba8a2eb6af..605500b19cf3 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/Kconfig +++ b/drivers/usb/gadget/udc/aspeed-vhub/Kconfig @@ -4,5 +4,5 @@ config USB_ASPEED_VHUB depends on ARCH_ASPEED || COMPILE_TEST depends on USB_LIBCOMPOSITE help - USB peripheral controller for the Aspeed AST2500 family - SoCs supporting the "vHub" functionality and USB2.0 + USB peripheral controller for the Aspeed AST2400, AST2500 and + AST2600 family SoCs supporting the "vHub" functionality and USB2.0 diff --git a/drivers/usb/gadget/udc/aspeed-vhub/core.c b/drivers/usb/gadget/udc/aspeed-vhub/core.c index 90b134d5dca9..f8d35dd60c34 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/core.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/core.c @@ -99,7 +99,7 @@ static irqreturn_t ast_vhub_irq(int irq, void *data) { struct ast_vhub *vhub = data; irqreturn_t iret = IRQ_NONE; - u32 istat; + u32 i, istat; /* Stale interrupt while tearing down */ if (!vhub->ep0_bufs) @@ -121,10 +121,10 @@ static irqreturn_t ast_vhub_irq(int irq, void *data) /* Handle generic EPs first */ if (istat & VHUB_IRQ_EP_POOL_ACK_STALL) { - u32 i, ep_acks = readl(vhub->regs + AST_VHUB_EP_ACK_ISR); + u32 ep_acks = readl(vhub->regs + AST_VHUB_EP_ACK_ISR); writel(ep_acks, vhub->regs + AST_VHUB_EP_ACK_ISR); - for (i = 0; ep_acks && i < AST_VHUB_NUM_GEN_EPs; i++) { + for (i = 0; ep_acks && i < vhub->max_epns; i++) { u32 mask = VHUB_EP_IRQ(i); if (ep_acks & mask) { ast_vhub_epn_ack_irq(&vhub->epns[i]); @@ -134,21 +134,11 @@ static irqreturn_t ast_vhub_irq(int irq, void *data) } /* Handle device interrupts */ - if (istat & (VHUB_IRQ_DEVICE1 | - VHUB_IRQ_DEVICE2 | - VHUB_IRQ_DEVICE3 | - VHUB_IRQ_DEVICE4 | - VHUB_IRQ_DEVICE5)) { - if (istat & VHUB_IRQ_DEVICE1) - ast_vhub_dev_irq(&vhub->ports[0].dev); - if (istat & VHUB_IRQ_DEVICE2) - ast_vhub_dev_irq(&vhub->ports[1].dev); - if (istat & VHUB_IRQ_DEVICE3) - ast_vhub_dev_irq(&vhub->ports[2].dev); - if (istat & VHUB_IRQ_DEVICE4) - ast_vhub_dev_irq(&vhub->ports[3].dev); - if (istat & VHUB_IRQ_DEVICE5) - ast_vhub_dev_irq(&vhub->ports[4].dev); + for (i = 0; i < vhub->max_ports; i++) { + u32 dev_mask = VHUB_IRQ_DEVICE1 << i; + + if (istat & dev_mask) + ast_vhub_dev_irq(&vhub->ports[i].dev); } /* Handle top-level vHub EP0 interrupts */ @@ -182,7 +172,7 @@ static irqreturn_t ast_vhub_irq(int irq, void *data) void ast_vhub_init_hw(struct ast_vhub *vhub) { - u32 ctrl; + u32 ctrl, port_mask, epn_mask; UDCDBG(vhub,"(Re)Starting HW ...\n"); @@ -222,15 +212,20 @@ void ast_vhub_init_hw(struct ast_vhub *vhub) } /* Reset all devices */ - writel(VHUB_SW_RESET_ALL, vhub->regs + AST_VHUB_SW_RESET); + port_mask = GENMASK(vhub->max_ports, 1); + writel(VHUB_SW_RESET_ROOT_HUB | + VHUB_SW_RESET_DMA_CONTROLLER | + VHUB_SW_RESET_EP_POOL | + port_mask, vhub->regs + AST_VHUB_SW_RESET); udelay(1); writel(0, vhub->regs + AST_VHUB_SW_RESET); /* Disable and cleanup EP ACK/NACK interrupts */ + epn_mask = GENMASK(vhub->max_epns - 1, 0); writel(0, vhub->regs + AST_VHUB_EP_ACK_IER); writel(0, vhub->regs + AST_VHUB_EP_NACK_IER); - writel(VHUB_EP_IRQ_ALL, vhub->regs + AST_VHUB_EP_ACK_ISR); - writel(VHUB_EP_IRQ_ALL, vhub->regs + AST_VHUB_EP_NACK_ISR); + writel(epn_mask, vhub->regs + AST_VHUB_EP_ACK_ISR); + writel(epn_mask, vhub->regs + AST_VHUB_EP_NACK_ISR); /* Default settings for EP0, enable HW hub EP1 */ writel(0, vhub->regs + AST_VHUB_EP0_CTRL); @@ -273,7 +268,7 @@ static int ast_vhub_remove(struct platform_device *pdev) return 0; /* Remove devices */ - for (i = 0; i < AST_VHUB_NUM_PORTS; i++) + for (i = 0; i < vhub->max_ports; i++) ast_vhub_del_dev(&vhub->ports[i].dev); spin_lock_irqsave(&vhub->lock, flags); @@ -295,7 +290,7 @@ static int ast_vhub_remove(struct platform_device *pdev) if (vhub->ep0_bufs) dma_free_coherent(&pdev->dev, AST_VHUB_EP0_MAX_PACKET * - (AST_VHUB_NUM_PORTS + 1), + (vhub->max_ports + 1), vhub->ep0_bufs, vhub->ep0_bufs_dma); vhub->ep0_bufs = NULL; @@ -309,11 +304,32 @@ static int ast_vhub_probe(struct platform_device *pdev) struct ast_vhub *vhub; struct resource *res; int i, rc = 0; + const struct device_node *np = pdev->dev.of_node; vhub = devm_kzalloc(&pdev->dev, sizeof(*vhub), GFP_KERNEL); if (!vhub) return -ENOMEM; + rc = of_property_read_u32(np, "aspeed,vhub-downstream-ports", + &vhub->max_ports); + if (rc < 0) + vhub->max_ports = AST_VHUB_NUM_PORTS; + + vhub->ports = devm_kcalloc(&pdev->dev, vhub->max_ports, + sizeof(*vhub->ports), GFP_KERNEL); + if (!vhub->ports) + return -ENOMEM; + + rc = of_property_read_u32(np, "aspeed,vhub-generic-endpoints", + &vhub->max_epns); + if (rc < 0) + vhub->max_epns = AST_VHUB_NUM_GEN_EPs; + + vhub->epns = devm_kcalloc(&pdev->dev, vhub->max_epns, + sizeof(*vhub->epns), GFP_KERNEL); + if (!vhub->epns) + return -ENOMEM; + spin_lock_init(&vhub->lock); vhub->pdev = pdev; @@ -366,7 +382,7 @@ static int ast_vhub_probe(struct platform_device *pdev) */ vhub->ep0_bufs = dma_alloc_coherent(&pdev->dev, AST_VHUB_EP0_MAX_PACKET * - (AST_VHUB_NUM_PORTS + 1), + (vhub->max_ports + 1), &vhub->ep0_bufs_dma, GFP_KERNEL); if (!vhub->ep0_bufs) { dev_err(&pdev->dev, "Failed to allocate EP0 DMA buffers\n"); @@ -380,7 +396,7 @@ static int ast_vhub_probe(struct platform_device *pdev) ast_vhub_init_ep0(vhub, &vhub->ep0, NULL); /* Init devices */ - for (i = 0; i < AST_VHUB_NUM_PORTS && rc == 0; i++) + for (i = 0; i < vhub->max_ports && rc == 0; i++) rc = ast_vhub_init_dev(vhub, i); if (rc) goto err; @@ -407,6 +423,9 @@ static const struct of_device_id ast_vhub_dt_ids[] = { { .compatible = "aspeed,ast2500-usb-vhub", }, + { + .compatible = "aspeed,ast2600-usb-vhub", + }, { } }; MODULE_DEVICE_TABLE(of, ast_vhub_dt_ids); diff --git a/drivers/usb/gadget/udc/aspeed-vhub/dev.c b/drivers/usb/gadget/udc/aspeed-vhub/dev.c index 4008e7a51188..d268306a7bfe 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/dev.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/dev.c @@ -77,7 +77,7 @@ static void ast_vhub_dev_enable(struct ast_vhub_dev *d) writel(d->ep0.buf_dma, d->regs + AST_VHUB_DEV_EP0_DATA); /* Clear stall on all EPs */ - for (i = 0; i < AST_VHUB_NUM_GEN_EPs; i++) { + for (i = 0; i < d->max_epns; i++) { struct ast_vhub_ep *ep = d->epns[i]; if (ep && (ep->epn.stalled || ep->epn.wedged)) { @@ -137,7 +137,7 @@ static int ast_vhub_ep_feature(struct ast_vhub_dev *d, is_set ? "SET" : "CLEAR", ep_num, wValue); if (ep_num == 0) return std_req_complete; - if (ep_num >= AST_VHUB_NUM_GEN_EPs || !d->epns[ep_num - 1]) + if (ep_num >= d->max_epns || !d->epns[ep_num - 1]) return std_req_stall; if (wValue != USB_ENDPOINT_HALT) return std_req_driver; @@ -181,7 +181,7 @@ static int ast_vhub_ep_status(struct ast_vhub_dev *d, DDBG(d, "GET_STATUS(ep%d)\n", ep_num); - if (ep_num >= AST_VHUB_NUM_GEN_EPs) + if (ep_num >= d->max_epns) return std_req_stall; if (ep_num != 0) { ep = d->epns[ep_num - 1]; @@ -299,7 +299,7 @@ static void ast_vhub_dev_nuke(struct ast_vhub_dev *d) { unsigned int i; - for (i = 0; i < AST_VHUB_NUM_GEN_EPs; i++) { + for (i = 0; i < d->max_epns; i++) { if (!d->epns[i]) continue; ast_vhub_nuke(d->epns[i], -ESHUTDOWN); @@ -416,10 +416,10 @@ static struct usb_ep *ast_vhub_udc_match_ep(struct usb_gadget *gadget, * that will allow the generic code to use our * assigned address. */ - for (i = 0; i < AST_VHUB_NUM_GEN_EPs; i++) + for (i = 0; i < d->max_epns; i++) if (d->epns[i] == NULL) break; - if (i >= AST_VHUB_NUM_GEN_EPs) + if (i >= d->max_epns) return NULL; addr = i + 1; @@ -526,6 +526,7 @@ void ast_vhub_del_dev(struct ast_vhub_dev *d) usb_del_gadget_udc(&d->gadget); device_unregister(d->port_dev); + kfree(d->epns); } static void ast_vhub_dev_release(struct device *dev) @@ -547,13 +548,24 @@ int ast_vhub_init_dev(struct ast_vhub *vhub, unsigned int idx) ast_vhub_init_ep0(vhub, &d->ep0, d); /* + * A USB device can have up to 30 endpoints besides control + * endpoint 0. + */ + d->max_epns = min_t(u32, vhub->max_epns, 30); + d->epns = kcalloc(d->max_epns, sizeof(*d->epns), GFP_KERNEL); + if (!d->epns) + return -ENOMEM; + + /* * The UDC core really needs us to have separate and uniquely * named "parent" devices for each port so we create a sub device * here for that purpose */ d->port_dev = kzalloc(sizeof(struct device), GFP_KERNEL); - if (!d->port_dev) - return -ENOMEM; + if (!d->port_dev) { + rc = -ENOMEM; + goto fail_alloc; + } device_initialize(d->port_dev); d->port_dev->release = ast_vhub_dev_release; d->port_dev->parent = parent; @@ -584,6 +596,8 @@ int ast_vhub_init_dev(struct ast_vhub *vhub, unsigned int idx) device_del(d->port_dev); fail_add: put_device(d->port_dev); + fail_alloc: + kfree(d->epns); return rc; } diff --git a/drivers/usb/gadget/udc/aspeed-vhub/epn.c b/drivers/usb/gadget/udc/aspeed-vhub/epn.c index 7475c74aa5c5..0bd6b20435b8 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/epn.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/epn.c @@ -800,10 +800,10 @@ struct ast_vhub_ep *ast_vhub_alloc_epn(struct ast_vhub_dev *d, u8 addr) /* Find a free one (no device) */ spin_lock_irqsave(&vhub->lock, flags); - for (i = 0; i < AST_VHUB_NUM_GEN_EPs; i++) + for (i = 0; i < vhub->max_epns; i++) if (vhub->epns[i].dev == NULL) break; - if (i >= AST_VHUB_NUM_GEN_EPs) { + if (i >= vhub->max_epns) { spin_unlock_irqrestore(&vhub->lock, flags); return NULL; } diff --git a/drivers/usb/gadget/udc/aspeed-vhub/hub.c b/drivers/usb/gadget/udc/aspeed-vhub/hub.c index 19b3517e04c0..6e565c3dbb5b 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/hub.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/hub.c @@ -93,11 +93,7 @@ static void ast_vhub_patch_dev_desc_usb1(struct usb_device_descriptor *desc) USB_DT_INTERFACE_SIZE + \ USB_DT_ENDPOINT_SIZE) -static const struct ast_vhub_full_cdesc { - struct usb_config_descriptor cfg; - struct usb_interface_descriptor intf; - struct usb_endpoint_descriptor ep; -} __attribute__ ((packed)) ast_vhub_conf_desc = { +static const struct ast_vhub_full_cdesc ast_vhub_conf_desc = { .cfg = { .bLength = USB_DT_CONFIG_SIZE, .bDescriptorType = USB_DT_CONFIG, @@ -266,6 +262,7 @@ static int ast_vhub_rep_desc(struct ast_vhub_ep *ep, u8 desc_type, u16 len) { size_t dsize; + struct ast_vhub *vhub = ep->vhub; EPDBG(ep, "GET_DESCRIPTOR(type:%d)\n", desc_type); @@ -281,20 +278,20 @@ static int ast_vhub_rep_desc(struct ast_vhub_ep *ep, switch(desc_type) { case USB_DT_DEVICE: dsize = USB_DT_DEVICE_SIZE; - memcpy(ep->buf, &ast_vhub_dev_desc, dsize); - BUILD_BUG_ON(dsize > sizeof(ast_vhub_dev_desc)); + memcpy(ep->buf, &vhub->vhub_dev_desc, dsize); + BUILD_BUG_ON(dsize > sizeof(vhub->vhub_dev_desc)); BUILD_BUG_ON(USB_DT_DEVICE_SIZE >= AST_VHUB_EP0_MAX_PACKET); break; case USB_DT_CONFIG: dsize = AST_VHUB_CONF_DESC_SIZE; - memcpy(ep->buf, &ast_vhub_conf_desc, dsize); - BUILD_BUG_ON(dsize > sizeof(ast_vhub_conf_desc)); + memcpy(ep->buf, &vhub->vhub_conf_desc, dsize); + BUILD_BUG_ON(dsize > sizeof(vhub->vhub_conf_desc)); BUILD_BUG_ON(AST_VHUB_CONF_DESC_SIZE >= AST_VHUB_EP0_MAX_PACKET); break; case USB_DT_HUB: dsize = AST_VHUB_HUB_DESC_SIZE; - memcpy(ep->buf, &ast_vhub_hub_desc, dsize); - BUILD_BUG_ON(dsize > sizeof(ast_vhub_hub_desc)); + memcpy(ep->buf, &vhub->vhub_hub_desc, dsize); + BUILD_BUG_ON(dsize > sizeof(vhub->vhub_hub_desc)); BUILD_BUG_ON(AST_VHUB_HUB_DESC_SIZE >= AST_VHUB_EP0_MAX_PACKET); break; default: @@ -317,7 +314,8 @@ static int ast_vhub_rep_string(struct ast_vhub_ep *ep, u8 string_id, u16 lang_id, u16 len) { - int rc = usb_gadget_get_string (&ast_vhub_strings, string_id, ep->buf); + int rc = usb_gadget_get_string(&ep->vhub->vhub_str_desc, + string_id, ep->buf); /* * This should never happen unless we put too big strings in @@ -504,7 +502,7 @@ static void ast_vhub_wake_work(struct work_struct *work) * we let the normal host wake path deal with it later. */ spin_lock_irqsave(&vhub->lock, flags); - for (i = 0; i < AST_VHUB_NUM_PORTS; i++) { + for (i = 0; i < vhub->max_ports; i++) { struct ast_vhub_port *p = &vhub->ports[i]; if (!(p->status & USB_PORT_STAT_SUSPEND)) @@ -587,7 +585,7 @@ static enum std_req_rc ast_vhub_set_port_feature(struct ast_vhub_ep *ep, struct ast_vhub *vhub = ep->vhub; struct ast_vhub_port *p; - if (port == 0 || port > AST_VHUB_NUM_PORTS) + if (port == 0 || port > vhub->max_ports) return std_req_stall; port--; p = &vhub->ports[port]; @@ -630,7 +628,7 @@ static enum std_req_rc ast_vhub_clr_port_feature(struct ast_vhub_ep *ep, struct ast_vhub *vhub = ep->vhub; struct ast_vhub_port *p; - if (port == 0 || port > AST_VHUB_NUM_PORTS) + if (port == 0 || port > vhub->max_ports) return std_req_stall; port--; p = &vhub->ports[port]; @@ -676,7 +674,7 @@ static enum std_req_rc ast_vhub_get_port_stat(struct ast_vhub_ep *ep, struct ast_vhub *vhub = ep->vhub; u16 stat, chg; - if (port == 0 || port > AST_VHUB_NUM_PORTS) + if (port == 0 || port > vhub->max_ports) return std_req_stall; port--; @@ -757,7 +755,7 @@ void ast_vhub_hub_suspend(struct ast_vhub *vhub) * Forward to unsuspended ports without changing * their connection status. */ - for (i = 0; i < AST_VHUB_NUM_PORTS; i++) { + for (i = 0; i < vhub->max_ports; i++) { struct ast_vhub_port *p = &vhub->ports[i]; if (!(p->status & USB_PORT_STAT_SUSPEND)) @@ -780,7 +778,7 @@ void ast_vhub_hub_resume(struct ast_vhub *vhub) * Forward to unsuspended ports without changing * their connection status. */ - for (i = 0; i < AST_VHUB_NUM_PORTS; i++) { + for (i = 0; i < vhub->max_ports; i++) { struct ast_vhub_port *p = &vhub->ports[i]; if (!(p->status & USB_PORT_STAT_SUSPEND)) @@ -814,7 +812,7 @@ void ast_vhub_hub_reset(struct ast_vhub *vhub) * Clear all port status, disable gadgets and "suspend" * them. They will be woken up by a port reset. */ - for (i = 0; i < AST_VHUB_NUM_PORTS; i++) { + for (i = 0; i < vhub->max_ports; i++) { struct ast_vhub_port *p = &vhub->ports[i]; /* Only keep the connected flag */ @@ -834,9 +832,31 @@ void ast_vhub_hub_reset(struct ast_vhub *vhub) writel(0, vhub->regs + AST_VHUB_EP1_STS_CHG); } +static void ast_vhub_init_desc(struct ast_vhub *vhub) +{ + /* Initialize vhub Device Descriptor. */ + memcpy(&vhub->vhub_dev_desc, &ast_vhub_dev_desc, + sizeof(vhub->vhub_dev_desc)); + + /* Initialize vhub Configuration Descriptor. */ + memcpy(&vhub->vhub_conf_desc, &ast_vhub_conf_desc, + sizeof(vhub->vhub_conf_desc)); + + /* Initialize vhub Hub Descriptor. */ + memcpy(&vhub->vhub_hub_desc, &ast_vhub_hub_desc, + sizeof(vhub->vhub_hub_desc)); + vhub->vhub_hub_desc.bNbrPorts = vhub->max_ports; + + /* Initialize vhub String Descriptors. */ + memcpy(&vhub->vhub_str_desc, &ast_vhub_strings, + sizeof(vhub->vhub_str_desc)); +} + void ast_vhub_init_hub(struct ast_vhub *vhub) { vhub->speed = USB_SPEED_UNKNOWN; INIT_WORK(&vhub->wake_work, ast_vhub_wake_work); + + ast_vhub_init_desc(vhub); } diff --git a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h index 761919e220d3..fac79ef6d669 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h +++ b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h @@ -2,6 +2,9 @@ #ifndef __ASPEED_VHUB_H #define __ASPEED_VHUB_H +#include <linux/usb.h> +#include <linux/usb/ch11.h> + /***************************** * * * VHUB register definitions * @@ -76,17 +79,9 @@ #define VHUB_SW_RESET_DEVICE2 (1 << 2) #define VHUB_SW_RESET_DEVICE1 (1 << 1) #define VHUB_SW_RESET_ROOT_HUB (1 << 0) -#define VHUB_SW_RESET_ALL (VHUB_SW_RESET_EP_POOL | \ - VHUB_SW_RESET_DMA_CONTROLLER | \ - VHUB_SW_RESET_DEVICE5 | \ - VHUB_SW_RESET_DEVICE4 | \ - VHUB_SW_RESET_DEVICE3 | \ - VHUB_SW_RESET_DEVICE2 | \ - VHUB_SW_RESET_DEVICE1 | \ - VHUB_SW_RESET_ROOT_HUB) + /* EP ACK/NACK IRQ masks */ #define VHUB_EP_IRQ(n) (1 << (n)) -#define VHUB_EP_IRQ_ALL 0x7fff /* 15 EPs */ /* USB status reg */ #define VHUB_USBSTS_HISPEED (1 << 27) @@ -210,6 +205,11 @@ * * ****************************************/ +/* + * AST_VHUB_NUM_GEN_EPs and AST_VHUB_NUM_PORTS are kept to avoid breaking + * existing AST2400/AST2500 platforms. AST2600 and future vhub revisions + * should define number of downstream ports and endpoints in device tree. + */ #define AST_VHUB_NUM_GEN_EPs 15 /* Generic non-0 EPs */ #define AST_VHUB_NUM_PORTS 5 /* vHub ports */ #define AST_VHUB_EP0_MAX_PACKET 64 /* EP0's max packet size */ @@ -312,7 +312,7 @@ struct ast_vhub_ep { /* Registers */ void __iomem *regs; - /* Index in global pool (0..14) */ + /* Index in global pool (zero-based) */ unsigned int g_idx; /* DMA Descriptors */ @@ -342,7 +342,7 @@ struct ast_vhub_dev { struct ast_vhub *vhub; void __iomem *regs; - /* Device index (0...4) and name string */ + /* Device index (zero-based) and name string */ unsigned int index; const char *name; @@ -358,7 +358,8 @@ struct ast_vhub_dev { /* Endpoint structures */ struct ast_vhub_ep ep0; - struct ast_vhub_ep *epns[AST_VHUB_NUM_GEN_EPs]; + struct ast_vhub_ep **epns; + u32 max_epns; }; #define to_ast_dev(__g) container_of(__g, struct ast_vhub_dev, gadget) @@ -373,6 +374,12 @@ struct ast_vhub_port { struct ast_vhub_dev dev; }; +struct ast_vhub_full_cdesc { + struct usb_config_descriptor cfg; + struct usb_interface_descriptor intf; + struct usb_endpoint_descriptor ep; +} __packed; + /* Global vhub structure */ struct ast_vhub { struct platform_device *pdev; @@ -393,10 +400,12 @@ struct ast_vhub { bool ep1_stalled : 1; /* Per-port info */ - struct ast_vhub_port ports[AST_VHUB_NUM_PORTS]; + struct ast_vhub_port *ports; + u32 max_ports; /* Generic EP data structures */ - struct ast_vhub_ep epns[AST_VHUB_NUM_GEN_EPs]; + struct ast_vhub_ep *epns; + u32 max_epns; /* Upstream bus is suspended ? */ bool suspended : 1; @@ -409,6 +418,12 @@ struct ast_vhub { /* Upstream bus speed captured at bus reset */ unsigned int speed; + + /* Standard USB Descriptors of the vhub. */ + struct usb_device_descriptor vhub_dev_desc; + struct ast_vhub_full_cdesc vhub_conf_desc; + struct usb_hub_descriptor vhub_hub_desc; + struct usb_gadget_strings vhub_str_desc; }; /* Standard request handlers result codes */ diff --git a/drivers/usb/gadget/udc/at91_udc.c b/drivers/usb/gadget/udc/at91_udc.c index 1b2b548c59a0..eede5cedacb4 100644 --- a/drivers/usb/gadget/udc/at91_udc.c +++ b/drivers/usb/gadget/udc/at91_udc.c @@ -2021,7 +2021,7 @@ static struct platform_driver at91_udc_driver = { .suspend = at91udc_suspend, .resume = at91udc_resume, .driver = { - .name = (char *) driver_name, + .name = driver_name, .of_match_table = at91_udc_dt_ids, }, }; diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index 4c9d1e49d5ed..6e3e3ebf715f 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -1134,7 +1134,7 @@ static struct platform_driver dummy_udc_driver = { .suspend = dummy_udc_suspend, .resume = dummy_udc_resume, .driver = { - .name = (char *) gadget_name, + .name = gadget_name, }, }; @@ -2720,7 +2720,7 @@ static struct platform_driver dummy_hcd_driver = { .suspend = dummy_hcd_suspend, .resume = dummy_hcd_resume, .driver = { - .name = (char *) driver_name, + .name = driver_name, }, }; diff --git a/drivers/usb/gadget/udc/fotg210-udc.c b/drivers/usb/gadget/udc/fotg210-udc.c index 21f3e6c4e4d6..d6ca50f01985 100644 --- a/drivers/usb/gadget/udc/fotg210-udc.c +++ b/drivers/usb/gadget/udc/fotg210-udc.c @@ -1199,7 +1199,7 @@ err: static struct platform_driver fotg210_driver = { .driver = { - .name = (char *)udc_name, + .name = udc_name, }, .probe = fotg210_udc_probe, .remove = fotg210_udc_remove, diff --git a/drivers/usb/gadget/udc/fsl_udc_core.c b/drivers/usb/gadget/udc/fsl_udc_core.c index ec6eda426223..febabde62f71 100644 --- a/drivers/usb/gadget/udc/fsl_udc_core.c +++ b/drivers/usb/gadget/udc/fsl_udc_core.c @@ -53,7 +53,6 @@ #define DMA_ADDR_INVALID (~(dma_addr_t)0) static const char driver_name[] = "fsl-usb2-udc"; -static const char driver_desc[] = DRIVER_DESC; static struct usb_dr_device __iomem *dr_regs; diff --git a/drivers/usb/gadget/udc/fusb300_udc.c b/drivers/usb/gadget/udc/fusb300_udc.c index 00e3f66836a9..9af8b415f303 100644 --- a/drivers/usb/gadget/udc/fusb300_udc.c +++ b/drivers/usb/gadget/udc/fusb300_udc.c @@ -1507,7 +1507,7 @@ clean_up: static struct platform_driver fusb300_driver = { .remove = fusb300_remove, .driver = { - .name = (char *) udc_name, + .name = udc_name, }, }; diff --git a/drivers/usb/gadget/udc/goku_udc.c b/drivers/usb/gadget/udc/goku_udc.c index 4a46f661d0e4..91dcb1995c27 100644 --- a/drivers/usb/gadget/udc/goku_udc.c +++ b/drivers/usb/gadget/udc/goku_udc.c @@ -1844,7 +1844,7 @@ static const struct pci_device_id pci_ids[] = { { MODULE_DEVICE_TABLE (pci, pci_ids); static struct pci_driver goku_pci_driver = { - .name = (char *) driver_name, + .name = driver_name, .id_table = pci_ids, .probe = goku_probe, diff --git a/drivers/usb/gadget/udc/lpc32xx_udc.c b/drivers/usb/gadget/udc/lpc32xx_udc.c index d14b2bb3f67c..cb997b82c008 100644 --- a/drivers/usb/gadget/udc/lpc32xx_udc.c +++ b/drivers/usb/gadget/udc/lpc32xx_udc.c @@ -3267,7 +3267,7 @@ static struct platform_driver lpc32xx_udc_driver = { .suspend = lpc32xx_udc_suspend, .resume = lpc32xx_udc_resume, .driver = { - .name = (char *) driver_name, + .name = driver_name, .of_match_table = of_match_ptr(lpc32xx_udc_of_match), }, }; diff --git a/drivers/usb/gadget/udc/m66592-udc.c b/drivers/usb/gadget/udc/m66592-udc.c index a8288df6aadf..75d16a8902e6 100644 --- a/drivers/usb/gadget/udc/m66592-udc.c +++ b/drivers/usb/gadget/udc/m66592-udc.c @@ -1691,7 +1691,7 @@ clean_up: static struct platform_driver m66592_driver = { .remove = m66592_remove, .driver = { - .name = (char *) udc_name, + .name = udc_name, }, }; diff --git a/drivers/usb/gadget/udc/max3420_udc.c b/drivers/usb/gadget/udc/max3420_udc.c new file mode 100644 index 000000000000..8fbc083b6732 --- /dev/null +++ b/drivers/usb/gadget/udc/max3420_udc.c @@ -0,0 +1,1331 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * MAX3420 Device Controller driver for USB. + * + * Author: Jaswinder Singh Brar <jaswinder.singh@linaro.org> + * (C) Copyright 2019-2020 Linaro Ltd + * + * Based on: + * o MAX3420E datasheet + * http://datasheets.maximintegrated.com/en/ds/MAX3420E.pdf + * o MAX342{0,1}E Programming Guides + * https://pdfserv.maximintegrated.com/en/an/AN3598.pdf + * https://pdfserv.maximintegrated.com/en/an/AN3785.pdf + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/bitfield.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/of_irq.h> +#include <linux/prefetch.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/spi/spi.h> +#include <linux/gpio/consumer.h> + +#define MAX3420_MAX_EPS 4 +#define MAX3420_EP_MAX_PACKET 64 /* Same for all Endpoints */ +#define MAX3420_EPNAME_SIZE 16 /* Buffer size for endpoint name */ + +#define MAX3420_ACKSTAT BIT(0) + +#define MAX3420_SPI_DIR_RD 0 /* read register from MAX3420 */ +#define MAX3420_SPI_DIR_WR 1 /* write register to MAX3420 */ + +/* SPI commands: */ +#define MAX3420_SPI_DIR_SHIFT 1 +#define MAX3420_SPI_REG_SHIFT 3 + +#define MAX3420_REG_EP0FIFO 0 +#define MAX3420_REG_EP1FIFO 1 +#define MAX3420_REG_EP2FIFO 2 +#define MAX3420_REG_EP3FIFO 3 +#define MAX3420_REG_SUDFIFO 4 +#define MAX3420_REG_EP0BC 5 +#define MAX3420_REG_EP1BC 6 +#define MAX3420_REG_EP2BC 7 +#define MAX3420_REG_EP3BC 8 + +#define MAX3420_REG_EPSTALLS 9 + #define ACKSTAT BIT(6) + #define STLSTAT BIT(5) + #define STLEP3IN BIT(4) + #define STLEP2IN BIT(3) + #define STLEP1OUT BIT(2) + #define STLEP0OUT BIT(1) + #define STLEP0IN BIT(0) + +#define MAX3420_REG_CLRTOGS 10 + #define EP3DISAB BIT(7) + #define EP2DISAB BIT(6) + #define EP1DISAB BIT(5) + #define CTGEP3IN BIT(4) + #define CTGEP2IN BIT(3) + #define CTGEP1OUT BIT(2) + +#define MAX3420_REG_EPIRQ 11 +#define MAX3420_REG_EPIEN 12 + #define SUDAVIRQ BIT(5) + #define IN3BAVIRQ BIT(4) + #define IN2BAVIRQ BIT(3) + #define OUT1DAVIRQ BIT(2) + #define OUT0DAVIRQ BIT(1) + #define IN0BAVIRQ BIT(0) + +#define MAX3420_REG_USBIRQ 13 +#define MAX3420_REG_USBIEN 14 + #define OSCOKIRQ BIT(0) + #define RWUDNIRQ BIT(1) + #define BUSACTIRQ BIT(2) + #define URESIRQ BIT(3) + #define SUSPIRQ BIT(4) + #define NOVBUSIRQ BIT(5) + #define VBUSIRQ BIT(6) + #define URESDNIRQ BIT(7) + +#define MAX3420_REG_USBCTL 15 + #define HOSCSTEN BIT(7) + #define VBGATE BIT(6) + #define CHIPRES BIT(5) + #define PWRDOWN BIT(4) + #define CONNECT BIT(3) + #define SIGRWU BIT(2) + +#define MAX3420_REG_CPUCTL 16 + #define IE BIT(0) + +#define MAX3420_REG_PINCTL 17 + #define EP3INAK BIT(7) + #define EP2INAK BIT(6) + #define EP0INAK BIT(5) + #define FDUPSPI BIT(4) + #define INTLEVEL BIT(3) + #define POSINT BIT(2) + #define GPXB BIT(1) + #define GPXA BIT(0) + +#define MAX3420_REG_REVISION 18 + +#define MAX3420_REG_FNADDR 19 + #define FNADDR_MASK 0x7f + +#define MAX3420_REG_IOPINS 20 +#define MAX3420_REG_IOPINS2 21 +#define MAX3420_REG_GPINIRQ 22 +#define MAX3420_REG_GPINIEN 23 +#define MAX3420_REG_GPINPOL 24 +#define MAX3420_REG_HIRQ 25 +#define MAX3420_REG_HIEN 26 +#define MAX3420_REG_MODE 27 +#define MAX3420_REG_PERADDR 28 +#define MAX3420_REG_HCTL 29 +#define MAX3420_REG_HXFR 30 +#define MAX3420_REG_HRSL 31 + +#define ENABLE_IRQ BIT(0) +#define IOPIN_UPDATE BIT(1) +#define REMOTE_WAKEUP BIT(2) +#define CONNECT_HOST GENMASK(4, 3) +#define HCONNECT (1 << 3) +#define HDISCONNECT (3 << 3) +#define UDC_START GENMASK(6, 5) +#define START (1 << 5) +#define STOP (3 << 5) +#define ENABLE_EP GENMASK(8, 7) +#define ENABLE (1 << 7) +#define DISABLE (3 << 7) +#define STALL_EP GENMASK(10, 9) +#define STALL (1 << 9) +#define UNSTALL (3 << 9) + +#define MAX3420_CMD(c) FIELD_PREP(GENMASK(7, 3), c) +#define MAX3420_SPI_CMD_RD(c) (MAX3420_CMD(c) | (0 << 1)) +#define MAX3420_SPI_CMD_WR(c) (MAX3420_CMD(c) | (1 << 1)) + +struct max3420_req { + struct usb_request usb_req; + struct list_head queue; + struct max3420_ep *ep; +}; + +struct max3420_ep { + struct usb_ep ep_usb; + struct max3420_udc *udc; + struct list_head queue; + char name[MAX3420_EPNAME_SIZE]; + unsigned int maxpacket; + spinlock_t lock; + int halted; + u32 todo; + int id; +}; + +struct max3420_udc { + struct usb_gadget gadget; + struct max3420_ep ep[MAX3420_MAX_EPS]; + struct usb_gadget_driver *driver; + struct task_struct *thread_task; + int remote_wkp, is_selfpowered; + bool vbus_active, softconnect; + struct usb_ctrlrequest setup; + struct mutex spi_bus_mutex; + struct max3420_req ep0req; + struct spi_device *spi; + struct device *dev; + spinlock_t lock; + bool suspended; + u8 ep0buf[64]; + u32 todo; +}; + +#define to_max3420_req(r) container_of((r), struct max3420_req, usb_req) +#define to_max3420_ep(e) container_of((e), struct max3420_ep, ep_usb) +#define to_udc(g) container_of((g), struct max3420_udc, gadget) + +#define DRIVER_DESC "MAX3420 USB Device-Mode Driver" +static const char driver_name[] = "max3420-udc"; + +/* Control endpoint configuration.*/ +static const struct usb_endpoint_descriptor ep0_desc = { + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = cpu_to_le16(MAX3420_EP_MAX_PACKET), +}; + +static void spi_ack_ctrl(struct max3420_udc *udc) +{ + struct spi_device *spi = udc->spi; + struct spi_transfer transfer; + struct spi_message msg; + u8 txdata[1]; + + memset(&transfer, 0, sizeof(transfer)); + + spi_message_init(&msg); + + txdata[0] = MAX3420_ACKSTAT; + transfer.tx_buf = txdata; + transfer.len = 1; + + spi_message_add_tail(&transfer, &msg); + spi_sync(spi, &msg); +} + +static u8 spi_rd8_ack(struct max3420_udc *udc, u8 reg, int actstat) +{ + struct spi_device *spi = udc->spi; + struct spi_transfer transfer; + struct spi_message msg; + u8 txdata[2], rxdata[2]; + + memset(&transfer, 0, sizeof(transfer)); + + spi_message_init(&msg); + + txdata[0] = MAX3420_SPI_CMD_RD(reg) | (actstat ? MAX3420_ACKSTAT : 0); + transfer.tx_buf = txdata; + transfer.rx_buf = rxdata; + transfer.len = 2; + + spi_message_add_tail(&transfer, &msg); + spi_sync(spi, &msg); + + return rxdata[1]; +} + +static u8 spi_rd8(struct max3420_udc *udc, u8 reg) +{ + return spi_rd8_ack(udc, reg, 0); +} + +static void spi_wr8_ack(struct max3420_udc *udc, u8 reg, u8 val, int actstat) +{ + struct spi_device *spi = udc->spi; + struct spi_transfer transfer; + struct spi_message msg; + u8 txdata[2]; + + memset(&transfer, 0, sizeof(transfer)); + + spi_message_init(&msg); + + txdata[0] = MAX3420_SPI_CMD_WR(reg) | (actstat ? MAX3420_ACKSTAT : 0); + txdata[1] = val; + + transfer.tx_buf = txdata; + transfer.len = 2; + + spi_message_add_tail(&transfer, &msg); + spi_sync(spi, &msg); +} + +static void spi_wr8(struct max3420_udc *udc, u8 reg, u8 val) +{ + spi_wr8_ack(udc, reg, val, 0); +} + +static void spi_rd_buf(struct max3420_udc *udc, u8 reg, void *buf, u8 len) +{ + struct spi_device *spi = udc->spi; + struct spi_transfer transfer; + struct spi_message msg; + u8 local_buf[MAX3420_EP_MAX_PACKET + 1] = {}; + + memset(&transfer, 0, sizeof(transfer)); + + spi_message_init(&msg); + + local_buf[0] = MAX3420_SPI_CMD_RD(reg); + transfer.tx_buf = &local_buf[0]; + transfer.rx_buf = &local_buf[0]; + transfer.len = len + 1; + + spi_message_add_tail(&transfer, &msg); + spi_sync(spi, &msg); + + memcpy(buf, &local_buf[1], len); +} + +static void spi_wr_buf(struct max3420_udc *udc, u8 reg, void *buf, u8 len) +{ + struct spi_device *spi = udc->spi; + struct spi_transfer transfer; + struct spi_message msg; + u8 local_buf[MAX3420_EP_MAX_PACKET + 1] = {}; + + memset(&transfer, 0, sizeof(transfer)); + + spi_message_init(&msg); + + local_buf[0] = MAX3420_SPI_CMD_WR(reg); + memcpy(&local_buf[1], buf, len); + + transfer.tx_buf = local_buf; + transfer.len = len + 1; + + spi_message_add_tail(&transfer, &msg); + spi_sync(spi, &msg); +} + +static int spi_max3420_enable(struct max3420_ep *ep) +{ + struct max3420_udc *udc = ep->udc; + unsigned long flags; + u8 epdis, epien; + int todo; + + spin_lock_irqsave(&ep->lock, flags); + todo = ep->todo & ENABLE_EP; + ep->todo &= ~ENABLE_EP; + spin_unlock_irqrestore(&ep->lock, flags); + + if (!todo || ep->id == 0) + return false; + + epien = spi_rd8(udc, MAX3420_REG_EPIEN); + epdis = spi_rd8(udc, MAX3420_REG_CLRTOGS); + + if (todo == ENABLE) { + epdis &= ~BIT(ep->id + 4); + epien |= BIT(ep->id + 1); + } else { + epdis |= BIT(ep->id + 4); + epien &= ~BIT(ep->id + 1); + } + + spi_wr8(udc, MAX3420_REG_CLRTOGS, epdis); + spi_wr8(udc, MAX3420_REG_EPIEN, epien); + + return true; +} + +static int spi_max3420_stall(struct max3420_ep *ep) +{ + struct max3420_udc *udc = ep->udc; + unsigned long flags; + u8 epstalls; + int todo; + + spin_lock_irqsave(&ep->lock, flags); + todo = ep->todo & STALL_EP; + ep->todo &= ~STALL_EP; + spin_unlock_irqrestore(&ep->lock, flags); + + if (!todo || ep->id == 0) + return false; + + epstalls = spi_rd8(udc, MAX3420_REG_EPSTALLS); + if (todo == STALL) { + ep->halted = 1; + epstalls |= BIT(ep->id + 1); + } else { + u8 clrtogs; + + ep->halted = 0; + epstalls &= ~BIT(ep->id + 1); + clrtogs = spi_rd8(udc, MAX3420_REG_CLRTOGS); + clrtogs |= BIT(ep->id + 1); + spi_wr8(udc, MAX3420_REG_CLRTOGS, clrtogs); + } + spi_wr8(udc, MAX3420_REG_EPSTALLS, epstalls | ACKSTAT); + + return true; +} + +static int spi_max3420_rwkup(struct max3420_udc *udc) +{ + unsigned long flags; + int wake_remote; + u8 usbctl; + + spin_lock_irqsave(&udc->lock, flags); + wake_remote = udc->todo & REMOTE_WAKEUP; + udc->todo &= ~REMOTE_WAKEUP; + spin_unlock_irqrestore(&udc->lock, flags); + + if (!wake_remote || !udc->suspended) + return false; + + /* Set Remote-WkUp Signal*/ + usbctl = spi_rd8(udc, MAX3420_REG_USBCTL); + usbctl |= SIGRWU; + spi_wr8(udc, MAX3420_REG_USBCTL, usbctl); + + msleep_interruptible(5); + + /* Clear Remote-WkUp Signal*/ + usbctl = spi_rd8(udc, MAX3420_REG_USBCTL); + usbctl &= ~SIGRWU; + spi_wr8(udc, MAX3420_REG_USBCTL, usbctl); + + udc->suspended = false; + + return true; +} + +static void max3420_nuke(struct max3420_ep *ep, int status); +static void __max3420_stop(struct max3420_udc *udc) +{ + u8 val; + int i; + + /* clear all pending requests */ + for (i = 1; i < MAX3420_MAX_EPS; i++) + max3420_nuke(&udc->ep[i], -ECONNRESET); + + /* Disable IRQ to CPU */ + spi_wr8(udc, MAX3420_REG_CPUCTL, 0); + + val = spi_rd8(udc, MAX3420_REG_USBCTL); + val |= PWRDOWN; + if (udc->is_selfpowered) + val &= ~HOSCSTEN; + else + val |= HOSCSTEN; + spi_wr8(udc, MAX3420_REG_USBCTL, val); +} + +static void __max3420_start(struct max3420_udc *udc) +{ + u8 val; + + /* Need this delay if bus-powered, + * but even for self-powered it helps stability + */ + msleep_interruptible(250); + + /* configure SPI */ + spi_wr8(udc, MAX3420_REG_PINCTL, FDUPSPI); + + /* Chip Reset */ + spi_wr8(udc, MAX3420_REG_USBCTL, CHIPRES); + msleep_interruptible(5); + spi_wr8(udc, MAX3420_REG_USBCTL, 0); + + /* Poll for OSC to stabilize */ + while (1) { + val = spi_rd8(udc, MAX3420_REG_USBIRQ); + if (val & OSCOKIRQ) + break; + cond_resched(); + } + + /* Enable PULL-UP only when Vbus detected */ + val = spi_rd8(udc, MAX3420_REG_USBCTL); + val |= VBGATE | CONNECT; + spi_wr8(udc, MAX3420_REG_USBCTL, val); + + val = URESDNIRQ | URESIRQ; + if (udc->is_selfpowered) + val |= NOVBUSIRQ; + spi_wr8(udc, MAX3420_REG_USBIEN, val); + + /* Enable only EP0 interrupts */ + val = IN0BAVIRQ | OUT0DAVIRQ | SUDAVIRQ; + spi_wr8(udc, MAX3420_REG_EPIEN, val); + + /* Enable IRQ to CPU */ + spi_wr8(udc, MAX3420_REG_CPUCTL, IE); +} + +static int max3420_start(struct max3420_udc *udc) +{ + unsigned long flags; + int todo; + + spin_lock_irqsave(&udc->lock, flags); + todo = udc->todo & UDC_START; + udc->todo &= ~UDC_START; + spin_unlock_irqrestore(&udc->lock, flags); + + if (!todo) + return false; + + if (udc->vbus_active && udc->softconnect) + __max3420_start(udc); + else + __max3420_stop(udc); + + return true; +} + +static irqreturn_t max3420_vbus_handler(int irq, void *dev_id) +{ + struct max3420_udc *udc = dev_id; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + /* its a vbus change interrupt */ + udc->vbus_active = !udc->vbus_active; + udc->todo |= UDC_START; + usb_udc_vbus_handler(&udc->gadget, udc->vbus_active); + usb_gadget_set_state(&udc->gadget, udc->vbus_active + ? USB_STATE_POWERED : USB_STATE_NOTATTACHED); + spin_unlock_irqrestore(&udc->lock, flags); + + if (udc->thread_task && + udc->thread_task->state != TASK_RUNNING) + wake_up_process(udc->thread_task); + + return IRQ_HANDLED; +} + +static irqreturn_t max3420_irq_handler(int irq, void *dev_id) +{ + struct max3420_udc *udc = dev_id; + struct spi_device *spi = udc->spi; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + if ((udc->todo & ENABLE_IRQ) == 0) { + disable_irq_nosync(spi->irq); + udc->todo |= ENABLE_IRQ; + } + spin_unlock_irqrestore(&udc->lock, flags); + + if (udc->thread_task && + udc->thread_task->state != TASK_RUNNING) + wake_up_process(udc->thread_task); + + return IRQ_HANDLED; +} + +static void max3420_getstatus(struct max3420_udc *udc) +{ + struct max3420_ep *ep; + u16 status = 0; + + switch (udc->setup.bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + /* Get device status */ + status = udc->gadget.is_selfpowered << USB_DEVICE_SELF_POWERED; + status |= (udc->remote_wkp << USB_DEVICE_REMOTE_WAKEUP); + break; + case USB_RECIP_INTERFACE: + if (udc->driver->setup(&udc->gadget, &udc->setup) < 0) + goto stall; + break; + case USB_RECIP_ENDPOINT: + ep = &udc->ep[udc->setup.wIndex & USB_ENDPOINT_NUMBER_MASK]; + if (udc->setup.wIndex & USB_DIR_IN) { + if (!ep->ep_usb.caps.dir_in) + goto stall; + } else { + if (!ep->ep_usb.caps.dir_out) + goto stall; + } + if (ep->halted) + status = 1 << USB_ENDPOINT_HALT; + break; + default: + goto stall; + } + + status = cpu_to_le16(status); + spi_wr_buf(udc, MAX3420_REG_EP0FIFO, &status, 2); + spi_wr8_ack(udc, MAX3420_REG_EP0BC, 2, 1); + return; +stall: + dev_err(udc->dev, "Can't respond to getstatus request\n"); + spi_wr8(udc, MAX3420_REG_EPSTALLS, STLEP0IN | STLEP0OUT | STLSTAT); +} + +static void max3420_set_clear_feature(struct max3420_udc *udc) +{ + struct max3420_ep *ep; + int set = udc->setup.bRequest == USB_REQ_SET_FEATURE; + unsigned long flags; + int id; + + switch (udc->setup.bRequestType) { + case USB_RECIP_DEVICE: + if (udc->setup.wValue != USB_DEVICE_REMOTE_WAKEUP) + break; + + if (udc->setup.bRequest == USB_REQ_SET_FEATURE) + udc->remote_wkp = 1; + else + udc->remote_wkp = 0; + + return spi_ack_ctrl(udc); + + case USB_RECIP_ENDPOINT: + if (udc->setup.wValue != USB_ENDPOINT_HALT) + break; + + id = udc->setup.wIndex & USB_ENDPOINT_NUMBER_MASK; + ep = &udc->ep[id]; + + spin_lock_irqsave(&ep->lock, flags); + ep->todo &= ~STALL_EP; + if (set) + ep->todo |= STALL; + else + ep->todo |= UNSTALL; + spin_unlock_irqrestore(&ep->lock, flags); + + spi_max3420_stall(ep); + return; + default: + break; + } + + dev_err(udc->dev, "Can't respond to SET/CLEAR FEATURE\n"); + spi_wr8(udc, MAX3420_REG_EPSTALLS, STLEP0IN | STLEP0OUT | STLSTAT); +} + +static void max3420_handle_setup(struct max3420_udc *udc) +{ + struct usb_ctrlrequest setup; + u8 addr; + + spi_rd_buf(udc, MAX3420_REG_SUDFIFO, (void *)&setup, 8); + + udc->setup = setup; + udc->setup.wValue = cpu_to_le16(setup.wValue); + udc->setup.wIndex = cpu_to_le16(setup.wIndex); + udc->setup.wLength = cpu_to_le16(setup.wLength); + + switch (udc->setup.bRequest) { + case USB_REQ_GET_STATUS: + /* Data+Status phase form udc */ + if ((udc->setup.bRequestType & + (USB_DIR_IN | USB_TYPE_MASK)) != + (USB_DIR_IN | USB_TYPE_STANDARD)) { + break; + } + return max3420_getstatus(udc); + case USB_REQ_SET_ADDRESS: + /* Status phase from udc */ + if (udc->setup.bRequestType != (USB_DIR_OUT | + USB_TYPE_STANDARD | USB_RECIP_DEVICE)) { + break; + } + addr = spi_rd8_ack(udc, MAX3420_REG_FNADDR, 1); + dev_dbg(udc->dev, "Assigned Address=%d\n", udc->setup.wValue); + return; + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + /* Requests with no data phase, status phase from udc */ + if ((udc->setup.bRequestType & USB_TYPE_MASK) + != USB_TYPE_STANDARD) + break; + return max3420_set_clear_feature(udc); + default: + break; + } + + if (udc->driver->setup(&udc->gadget, &setup) < 0) { + /* Stall EP0 */ + spi_wr8(udc, MAX3420_REG_EPSTALLS, + STLEP0IN | STLEP0OUT | STLSTAT); + } +} + +static void max3420_req_done(struct max3420_req *req, int status) +{ + struct max3420_ep *ep = req->ep; + struct max3420_udc *udc = ep->udc; + + if (req->usb_req.status == -EINPROGRESS) + req->usb_req.status = status; + else + status = req->usb_req.status; + + if (status && status != -ESHUTDOWN) + dev_err(udc->dev, "%s done %p, status %d\n", + ep->ep_usb.name, req, status); + + if (req->usb_req.complete) + req->usb_req.complete(&ep->ep_usb, &req->usb_req); +} + +static int max3420_do_data(struct max3420_udc *udc, int ep_id, int in) +{ + struct max3420_ep *ep = &udc->ep[ep_id]; + struct max3420_req *req; + int done, length, psz; + void *buf; + + if (list_empty(&ep->queue)) + return false; + + req = list_first_entry(&ep->queue, struct max3420_req, queue); + buf = req->usb_req.buf + req->usb_req.actual; + + psz = ep->ep_usb.maxpacket; + length = req->usb_req.length - req->usb_req.actual; + length = min(length, psz); + + if (length == 0) { + done = 1; + goto xfer_done; + } + + done = 0; + if (in) { + prefetch(buf); + spi_wr_buf(udc, MAX3420_REG_EP0FIFO + ep_id, buf, length); + spi_wr8(udc, MAX3420_REG_EP0BC + ep_id, length); + if (length < psz) + done = 1; + } else { + psz = spi_rd8(udc, MAX3420_REG_EP0BC + ep_id); + length = min(length, psz); + prefetchw(buf); + spi_rd_buf(udc, MAX3420_REG_EP0FIFO + ep_id, buf, length); + if (length < ep->ep_usb.maxpacket) + done = 1; + } + + req->usb_req.actual += length; + + if (req->usb_req.actual == req->usb_req.length) + done = 1; + +xfer_done: + if (done) { + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + list_del_init(&req->queue); + spin_unlock_irqrestore(&ep->lock, flags); + + if (ep_id == 0) + spi_ack_ctrl(udc); + + max3420_req_done(req, 0); + } + + return true; +} + +static int max3420_handle_irqs(struct max3420_udc *udc) +{ + u8 epien, epirq, usbirq, usbien, reg[4]; + bool ret = false; + + spi_rd_buf(udc, MAX3420_REG_EPIRQ, reg, 4); + epirq = reg[0]; + epien = reg[1]; + usbirq = reg[2]; + usbien = reg[3]; + + usbirq &= usbien; + epirq &= epien; + + if (epirq & SUDAVIRQ) { + spi_wr8(udc, MAX3420_REG_EPIRQ, SUDAVIRQ); + max3420_handle_setup(udc); + return true; + } + + if (usbirq & VBUSIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, VBUSIRQ); + dev_dbg(udc->dev, "Cable plugged in\n"); + return true; + } + + if (usbirq & NOVBUSIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, NOVBUSIRQ); + dev_dbg(udc->dev, "Cable pulled out\n"); + return true; + } + + if (usbirq & URESIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, URESIRQ); + dev_dbg(udc->dev, "USB Reset - Start\n"); + return true; + } + + if (usbirq & URESDNIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, URESDNIRQ); + dev_dbg(udc->dev, "USB Reset - END\n"); + spi_wr8(udc, MAX3420_REG_USBIEN, URESDNIRQ | URESIRQ); + spi_wr8(udc, MAX3420_REG_EPIEN, SUDAVIRQ | IN0BAVIRQ + | OUT0DAVIRQ); + return true; + } + + if (usbirq & SUSPIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, SUSPIRQ); + dev_dbg(udc->dev, "USB Suspend - Enter\n"); + udc->suspended = true; + return true; + } + + if (usbirq & BUSACTIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, BUSACTIRQ); + dev_dbg(udc->dev, "USB Suspend - Exit\n"); + udc->suspended = false; + return true; + } + + if (usbirq & RWUDNIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, RWUDNIRQ); + dev_dbg(udc->dev, "Asked Host to wakeup\n"); + return true; + } + + if (usbirq & OSCOKIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, OSCOKIRQ); + dev_dbg(udc->dev, "Osc stabilized, start work\n"); + return true; + } + + if (epirq & OUT0DAVIRQ && max3420_do_data(udc, 0, 0)) { + spi_wr8_ack(udc, MAX3420_REG_EPIRQ, OUT0DAVIRQ, 1); + ret = true; + } + + if (epirq & IN0BAVIRQ && max3420_do_data(udc, 0, 1)) + ret = true; + + if (epirq & OUT1DAVIRQ && max3420_do_data(udc, 1, 0)) { + spi_wr8_ack(udc, MAX3420_REG_EPIRQ, OUT1DAVIRQ, 1); + ret = true; + } + + if (epirq & IN2BAVIRQ && max3420_do_data(udc, 2, 1)) + ret = true; + + if (epirq & IN3BAVIRQ && max3420_do_data(udc, 3, 1)) + ret = true; + + return ret; +} + +static int max3420_thread(void *dev_id) +{ + struct max3420_udc *udc = dev_id; + struct spi_device *spi = udc->spi; + int i, loop_again = 1; + unsigned long flags; + + while (!kthread_should_stop()) { + if (!loop_again) { + ktime_t kt = ns_to_ktime(1000 * 1000 * 250); /* 250ms */ + + set_current_state(TASK_INTERRUPTIBLE); + + spin_lock_irqsave(&udc->lock, flags); + if (udc->todo & ENABLE_IRQ) { + enable_irq(spi->irq); + udc->todo &= ~ENABLE_IRQ; + } + spin_unlock_irqrestore(&udc->lock, flags); + + schedule_hrtimeout(&kt, HRTIMER_MODE_REL); + } + loop_again = 0; + + mutex_lock(&udc->spi_bus_mutex); + + /* If bus-vbus_active and disconnected */ + if (!udc->vbus_active || !udc->softconnect) + goto loop; + + if (max3420_start(udc)) { + loop_again = 1; + goto loop; + } + + if (max3420_handle_irqs(udc)) { + loop_again = 1; + goto loop; + } + + if (spi_max3420_rwkup(udc)) { + loop_again = 1; + goto loop; + } + + max3420_do_data(udc, 0, 1); /* get done with the EP0 ZLP */ + + for (i = 1; i < MAX3420_MAX_EPS; i++) { + struct max3420_ep *ep = &udc->ep[i]; + + if (spi_max3420_enable(ep)) + loop_again = 1; + if (spi_max3420_stall(ep)) + loop_again = 1; + } +loop: + mutex_unlock(&udc->spi_bus_mutex); + } + + set_current_state(TASK_RUNNING); + dev_info(udc->dev, "SPI thread exiting"); + return 0; +} + +static int max3420_ep_set_halt(struct usb_ep *_ep, int stall) +{ + struct max3420_ep *ep = to_max3420_ep(_ep); + struct max3420_udc *udc = ep->udc; + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + + ep->todo &= ~STALL_EP; + if (stall) + ep->todo |= STALL; + else + ep->todo |= UNSTALL; + + spin_unlock_irqrestore(&ep->lock, flags); + + wake_up_process(udc->thread_task); + + dev_dbg(udc->dev, "%sStall %s\n", stall ? "" : "Un", ep->name); + return 0; +} + +static int __max3420_ep_enable(struct max3420_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + unsigned int maxp = usb_endpoint_maxp(desc); + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + ep->ep_usb.desc = desc; + ep->ep_usb.maxpacket = maxp; + + ep->todo &= ~ENABLE_EP; + ep->todo |= ENABLE; + spin_unlock_irqrestore(&ep->lock, flags); + + return 0; +} + +static int max3420_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct max3420_ep *ep = to_max3420_ep(_ep); + struct max3420_udc *udc = ep->udc; + + __max3420_ep_enable(ep, desc); + + wake_up_process(udc->thread_task); + + return 0; +} + +static void max3420_nuke(struct max3420_ep *ep, int status) +{ + struct max3420_req *req, *r; + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + + list_for_each_entry_safe(req, r, &ep->queue, queue) { + list_del_init(&req->queue); + + spin_unlock_irqrestore(&ep->lock, flags); + max3420_req_done(req, status); + spin_lock_irqsave(&ep->lock, flags); + } + + spin_unlock_irqrestore(&ep->lock, flags); +} + +static void __max3420_ep_disable(struct max3420_ep *ep) +{ + struct max3420_udc *udc = ep->udc; + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + + ep->ep_usb.desc = NULL; + + ep->todo &= ~ENABLE_EP; + ep->todo |= DISABLE; + + spin_unlock_irqrestore(&ep->lock, flags); + + dev_dbg(udc->dev, "Disabled %s\n", ep->name); +} + +static int max3420_ep_disable(struct usb_ep *_ep) +{ + struct max3420_ep *ep = to_max3420_ep(_ep); + struct max3420_udc *udc = ep->udc; + + max3420_nuke(ep, -ESHUTDOWN); + + __max3420_ep_disable(ep); + + wake_up_process(udc->thread_task); + + return 0; +} + +static struct usb_request *max3420_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + struct max3420_ep *ep = to_max3420_ep(_ep); + struct max3420_req *req; + + req = kzalloc(sizeof(*req), gfp_flags); + if (!req) + return NULL; + + req->ep = ep; + + return &req->usb_req; +} + +static void max3420_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + kfree(to_max3420_req(_req)); +} + +static int max3420_ep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t ignored) +{ + struct max3420_req *req = to_max3420_req(_req); + struct max3420_ep *ep = to_max3420_ep(_ep); + struct max3420_udc *udc = ep->udc; + unsigned long flags; + + _req->status = -EINPROGRESS; + _req->actual = 0; + + spin_lock_irqsave(&ep->lock, flags); + list_add_tail(&req->queue, &ep->queue); + spin_unlock_irqrestore(&ep->lock, flags); + + wake_up_process(udc->thread_task); + return 0; +} + +static int max3420_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct max3420_req *t, *req = to_max3420_req(_req); + struct max3420_ep *ep = to_max3420_ep(_ep); + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + + /* Pluck the descriptor from queue */ + list_for_each_entry(t, &ep->queue, queue) + if (t == req) { + list_del_init(&req->queue); + break; + } + + spin_unlock_irqrestore(&ep->lock, flags); + + if (t == req) + max3420_req_done(req, -ECONNRESET); + + return 0; +} + +static const struct usb_ep_ops max3420_ep_ops = { + .enable = max3420_ep_enable, + .disable = max3420_ep_disable, + .alloc_request = max3420_alloc_request, + .free_request = max3420_free_request, + .queue = max3420_ep_queue, + .dequeue = max3420_ep_dequeue, + .set_halt = max3420_ep_set_halt, +}; + +static int max3420_wakeup(struct usb_gadget *gadget) +{ + struct max3420_udc *udc = to_udc(gadget); + unsigned long flags; + int ret = -EINVAL; + + spin_lock_irqsave(&udc->lock, flags); + + /* Only if wakeup allowed by host */ + if (udc->remote_wkp) { + udc->todo |= REMOTE_WAKEUP; + ret = 0; + } + + spin_unlock_irqrestore(&udc->lock, flags); + + if (udc->thread_task && + udc->thread_task->state != TASK_RUNNING) + wake_up_process(udc->thread_task); + return ret; +} + +static int max3420_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct max3420_udc *udc = to_udc(gadget); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + /* hook up the driver */ + driver->driver.bus = NULL; + udc->driver = driver; + udc->gadget.speed = USB_SPEED_FULL; + + udc->gadget.is_selfpowered = udc->is_selfpowered; + udc->remote_wkp = 0; + udc->softconnect = true; + udc->todo |= UDC_START; + spin_unlock_irqrestore(&udc->lock, flags); + + if (udc->thread_task && + udc->thread_task->state != TASK_RUNNING) + wake_up_process(udc->thread_task); + + return 0; +} + +static int max3420_udc_stop(struct usb_gadget *gadget) +{ + struct max3420_udc *udc = to_udc(gadget); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + udc->is_selfpowered = udc->gadget.is_selfpowered; + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->driver = NULL; + udc->softconnect = false; + udc->todo |= UDC_START; + spin_unlock_irqrestore(&udc->lock, flags); + + if (udc->thread_task && + udc->thread_task->state != TASK_RUNNING) + wake_up_process(udc->thread_task); + + return 0; +} + +static const struct usb_gadget_ops max3420_udc_ops = { + .udc_start = max3420_udc_start, + .udc_stop = max3420_udc_stop, + .wakeup = max3420_wakeup, +}; + +static void max3420_eps_init(struct max3420_udc *udc) +{ + int idx; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + + for (idx = 0; idx < MAX3420_MAX_EPS; idx++) { + struct max3420_ep *ep = &udc->ep[idx]; + + spin_lock_init(&ep->lock); + INIT_LIST_HEAD(&ep->queue); + + ep->udc = udc; + ep->id = idx; + ep->halted = 0; + ep->maxpacket = 0; + ep->ep_usb.name = ep->name; + ep->ep_usb.ops = &max3420_ep_ops; + usb_ep_set_maxpacket_limit(&ep->ep_usb, MAX3420_EP_MAX_PACKET); + + if (idx == 0) { /* For EP0 */ + ep->ep_usb.desc = &ep0_desc; + ep->ep_usb.maxpacket = usb_endpoint_maxp(&ep0_desc); + ep->ep_usb.caps.type_control = true; + ep->ep_usb.caps.dir_in = true; + ep->ep_usb.caps.dir_out = true; + snprintf(ep->name, MAX3420_EPNAME_SIZE, "ep0"); + continue; + } + + if (idx == 1) { /* EP1 is OUT */ + ep->ep_usb.caps.dir_in = false; + ep->ep_usb.caps.dir_out = true; + snprintf(ep->name, MAX3420_EPNAME_SIZE, "ep1-bulk-out"); + } else { /* EP2 & EP3 are IN */ + ep->ep_usb.caps.dir_in = true; + ep->ep_usb.caps.dir_out = false; + snprintf(ep->name, MAX3420_EPNAME_SIZE, + "ep%d-bulk-in", idx); + } + ep->ep_usb.caps.type_iso = false; + ep->ep_usb.caps.type_int = false; + ep->ep_usb.caps.type_bulk = true; + + list_add_tail(&ep->ep_usb.ep_list, + &udc->gadget.ep_list); + } +} + +static int max3420_probe(struct spi_device *spi) +{ + struct max3420_udc *udc; + int err, irq; + u8 reg[8]; + + if (spi->master->flags & SPI_MASTER_HALF_DUPLEX) { + dev_err(&spi->dev, "UDC needs full duplex to work\n"); + return -EINVAL; + } + + spi->mode = SPI_MODE_3; + spi->bits_per_word = 8; + + err = spi_setup(spi); + if (err) { + dev_err(&spi->dev, "Unable to setup SPI bus\n"); + return -EFAULT; + } + + udc = devm_kzalloc(&spi->dev, sizeof(*udc), GFP_KERNEL); + if (!udc) + return -ENOMEM; + + udc->spi = spi; + + udc->remote_wkp = 0; + + /* Setup gadget structure */ + udc->gadget.ops = &max3420_udc_ops; + udc->gadget.max_speed = USB_SPEED_FULL; + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->gadget.ep0 = &udc->ep[0].ep_usb; + udc->gadget.name = driver_name; + + spin_lock_init(&udc->lock); + mutex_init(&udc->spi_bus_mutex); + + udc->ep0req.ep = &udc->ep[0]; + udc->ep0req.usb_req.buf = udc->ep0buf; + INIT_LIST_HEAD(&udc->ep0req.queue); + + /* setup Endpoints */ + max3420_eps_init(udc); + + /* configure SPI */ + spi_rd_buf(udc, MAX3420_REG_EPIRQ, reg, 8); + spi_wr8(udc, MAX3420_REG_PINCTL, FDUPSPI); + + err = usb_add_gadget_udc(&spi->dev, &udc->gadget); + if (err) + return err; + + udc->dev = &udc->gadget.dev; + + spi_set_drvdata(spi, udc); + + irq = of_irq_get_byname(spi->dev.of_node, "udc"); + err = devm_request_irq(&spi->dev, irq, max3420_irq_handler, 0, + "max3420", udc); + if (err < 0) + return err; + + udc->thread_task = kthread_create(max3420_thread, udc, + "max3420-thread"); + if (IS_ERR(udc->thread_task)) + return PTR_ERR(udc->thread_task); + + irq = of_irq_get_byname(spi->dev.of_node, "vbus"); + if (irq <= 0) { /* no vbus irq implies self-powered design */ + udc->is_selfpowered = 1; + udc->vbus_active = true; + udc->todo |= UDC_START; + usb_udc_vbus_handler(&udc->gadget, udc->vbus_active); + usb_gadget_set_state(&udc->gadget, USB_STATE_POWERED); + max3420_start(udc); + } else { + udc->is_selfpowered = 0; + /* Detect current vbus status */ + spi_rd_buf(udc, MAX3420_REG_EPIRQ, reg, 8); + if (reg[7] != 0xff) + udc->vbus_active = true; + + err = devm_request_irq(&spi->dev, irq, + max3420_vbus_handler, 0, "vbus", udc); + if (err < 0) + return err; + } + + return 0; +} + +static int max3420_remove(struct spi_device *spi) +{ + struct max3420_udc *udc = spi_get_drvdata(spi); + unsigned long flags; + + usb_del_gadget_udc(&udc->gadget); + + spin_lock_irqsave(&udc->lock, flags); + + kthread_stop(udc->thread_task); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static const struct of_device_id max3420_udc_of_match[] = { + { .compatible = "maxim,max3420-udc"}, + { .compatible = "maxim,max3421-udc"}, + {}, +}; +MODULE_DEVICE_TABLE(of, max3420_udc_of_match); + +static struct spi_driver max3420_driver = { + .driver = { + .name = "max3420-udc", + .of_match_table = of_match_ptr(max3420_udc_of_match), + }, + .probe = max3420_probe, + .remove = max3420_remove, +}; + +module_spi_driver(max3420_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Jassi Brar <jaswinder.singh@linaro.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 1fd1b9186e46..5eff85eeaa5a 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -2861,6 +2861,8 @@ static void ep_clear_seqnum(struct net2280_ep *ep) static void handle_stat0_irqs_superspeed(struct net2280 *dev, struct net2280_ep *ep, struct usb_ctrlrequest r) { + struct net2280_ep *e; + u16 status; int tmp = 0; #define w_value le16_to_cpu(r.wValue) @@ -2868,9 +2870,6 @@ static void handle_stat0_irqs_superspeed(struct net2280 *dev, #define w_length le16_to_cpu(r.wLength) switch (r.bRequest) { - struct net2280_ep *e; - u16 status; - case USB_REQ_SET_CONFIGURATION: dev->addressed_state = !w_value; goto usb3_delegate; @@ -3857,7 +3856,7 @@ MODULE_DEVICE_TABLE(pci, pci_ids); /* pci driver glue; this is a "new style" PCI driver module */ static struct pci_driver net2280_pci_driver = { - .name = (char *) driver_name, + .name = driver_name, .id_table = pci_ids, .probe = net2280_probe, diff --git a/drivers/usb/gadget/udc/omap_udc.c b/drivers/usb/gadget/udc/omap_udc.c index bd12417996db..bf87c6c0d7f6 100644 --- a/drivers/usb/gadget/udc/omap_udc.c +++ b/drivers/usb/gadget/udc/omap_udc.c @@ -3001,7 +3001,7 @@ static struct platform_driver udc_driver = { .suspend = omap_udc_suspend, .resume = omap_udc_resume, .driver = { - .name = (char *) driver_name, + .name = driver_name, }, }; diff --git a/drivers/usb/gadget/udc/r8a66597-udc.c b/drivers/usb/gadget/udc/r8a66597-udc.c index 582a16165ea9..537094b485bf 100644 --- a/drivers/usb/gadget/udc/r8a66597-udc.c +++ b/drivers/usb/gadget/udc/r8a66597-udc.c @@ -1968,7 +1968,7 @@ clean_up2: static struct platform_driver r8a66597_driver = { .remove = r8a66597_remove, .driver = { - .name = (char *) udc_name, + .name = udc_name, }, }; diff --git a/drivers/usb/gadget/udc/renesas_usb3.c b/drivers/usb/gadget/udc/renesas_usb3.c index c5c3c14df67a..0c418ce50ba0 100644 --- a/drivers/usb/gadget/udc/renesas_usb3.c +++ b/drivers/usb/gadget/udc/renesas_usb3.c @@ -2355,14 +2355,14 @@ static const struct usb_gadget_ops renesas_usb3_gadget_ops = { .set_selfpowered = renesas_usb3_set_selfpowered, }; -static enum usb_role renesas_usb3_role_switch_get(struct device *dev) +static enum usb_role renesas_usb3_role_switch_get(struct usb_role_switch *sw) { - struct renesas_usb3 *usb3 = dev_get_drvdata(dev); + struct renesas_usb3 *usb3 = usb_role_switch_get_drvdata(sw); enum usb_role cur_role; - pm_runtime_get_sync(dev); + pm_runtime_get_sync(usb3_to_dev(usb3)); cur_role = usb3_is_host(usb3) ? USB_ROLE_HOST : USB_ROLE_DEVICE; - pm_runtime_put(dev); + pm_runtime_put(usb3_to_dev(usb3)); return cur_role; } @@ -2372,7 +2372,7 @@ static void handle_ext_role_switch_states(struct device *dev, { struct renesas_usb3 *usb3 = dev_get_drvdata(dev); struct device *host = usb3->host_dev; - enum usb_role cur_role = renesas_usb3_role_switch_get(dev); + enum usb_role cur_role = renesas_usb3_role_switch_get(usb3->role_sw); switch (role) { case USB_ROLE_NONE: @@ -2424,7 +2424,7 @@ static void handle_role_switch_states(struct device *dev, { struct renesas_usb3 *usb3 = dev_get_drvdata(dev); struct device *host = usb3->host_dev; - enum usb_role cur_role = renesas_usb3_role_switch_get(dev); + enum usb_role cur_role = renesas_usb3_role_switch_get(usb3->role_sw); if (cur_role == USB_ROLE_HOST && role == USB_ROLE_DEVICE) { device_release_driver(host); @@ -2438,19 +2438,19 @@ static void handle_role_switch_states(struct device *dev, } } -static int renesas_usb3_role_switch_set(struct device *dev, +static int renesas_usb3_role_switch_set(struct usb_role_switch *sw, enum usb_role role) { - struct renesas_usb3 *usb3 = dev_get_drvdata(dev); + struct renesas_usb3 *usb3 = usb_role_switch_get_drvdata(sw); - pm_runtime_get_sync(dev); + pm_runtime_get_sync(usb3_to_dev(usb3)); if (usb3->role_sw_by_connector) - handle_ext_role_switch_states(dev, role); + handle_ext_role_switch_states(usb3_to_dev(usb3), role); else - handle_role_switch_states(dev, role); + handle_role_switch_states(usb3_to_dev(usb3), role); - pm_runtime_put(dev); + pm_runtime_put(usb3_to_dev(usb3)); return 0; } @@ -2831,6 +2831,8 @@ static int renesas_usb3_probe(struct platform_device *pdev) renesas_usb3_role_switch_desc.fwnode = dev_fwnode(&pdev->dev); } + renesas_usb3_role_switch_desc.driver_data = usb3; + INIT_WORK(&usb3->role_work, renesas_usb3_role_work); usb3->role_sw = usb_role_switch_register(&pdev->dev, &renesas_usb3_role_switch_desc); @@ -2906,7 +2908,7 @@ static struct platform_driver renesas_usb3_driver = { .probe = renesas_usb3_probe, .remove = renesas_usb3_remove, .driver = { - .name = (char *)udc_name, + .name = udc_name, .pm = &renesas_usb3_pm_ops, .of_match_table = of_match_ptr(usb3_of_match), }, diff --git a/drivers/usb/gadget/udc/s3c-hsudc.c b/drivers/usb/gadget/udc/s3c-hsudc.c index 21252fbc0319..aaca1b0a2f59 100644 --- a/drivers/usb/gadget/udc/s3c-hsudc.c +++ b/drivers/usb/gadget/udc/s3c-hsudc.c @@ -1285,7 +1285,8 @@ static int s3c_hsudc_probe(struct platform_device *pdev) ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(hsudc->supplies), hsudc->supplies); if (ret != 0) { - dev_err(dev, "failed to request supplies: %d\n", ret); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to request supplies: %d\n", ret); goto err_supplies; } diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c index 634c2c19a176..52a6add961f4 100644 --- a/drivers/usb/gadget/udc/tegra-xudc.c +++ b/drivers/usb/gadget/udc/tegra-xudc.c @@ -26,7 +26,9 @@ #include <linux/reset.h> #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> +#include <linux/usb/otg.h> #include <linux/usb/role.h> +#include <linux/usb/phy.h> #include <linux/workqueue.h> /* XUSB_DEV registers */ @@ -477,17 +479,21 @@ struct tegra_xudc { struct clk_bulk_data *clks; - enum usb_role device_mode; - struct usb_role_switch *usb_role_sw; + bool device_mode; struct work_struct usb_role_sw_work; - struct phy *usb3_phy; - struct phy *utmi_phy; + struct phy **usb3_phy; + struct phy *curr_usb3_phy; + struct phy **utmi_phy; + struct phy *curr_utmi_phy; struct tegra_xudc_save_regs saved_regs; bool suspended; bool powergated; + struct usb_phy **usbphy; + struct notifier_block vbus_nb; + struct completion disconnect_complete; bool selfpowered; @@ -517,6 +523,7 @@ struct tegra_xudc_soc { unsigned int num_supplies; const char * const *clock_names; unsigned int num_clks; + unsigned int num_phys; bool u1_enable; bool u2_enable; bool lpm_enable; @@ -598,19 +605,18 @@ static void tegra_xudc_device_mode_on(struct tegra_xudc *xudc) pm_runtime_get_sync(xudc->dev); - err = phy_power_on(xudc->utmi_phy); + err = phy_power_on(xudc->curr_utmi_phy); if (err < 0) dev_err(xudc->dev, "utmi power on failed %d\n", err); - err = phy_power_on(xudc->usb3_phy); + err = phy_power_on(xudc->curr_usb3_phy); if (err < 0) dev_err(xudc->dev, "usb3 phy power on failed %d\n", err); dev_dbg(xudc->dev, "device mode on\n"); - tegra_xusb_padctl_set_vbus_override(xudc->padctl, true); - - xudc->device_mode = USB_ROLE_DEVICE; + phy_set_mode_ext(xudc->curr_utmi_phy, PHY_MODE_USB_OTG, + USB_ROLE_DEVICE); } static void tegra_xudc_device_mode_off(struct tegra_xudc *xudc) @@ -625,7 +631,7 @@ static void tegra_xudc_device_mode_off(struct tegra_xudc *xudc) reinit_completion(&xudc->disconnect_complete); - tegra_xusb_padctl_set_vbus_override(xudc->padctl, false); + phy_set_mode_ext(xudc->curr_utmi_phy, PHY_MODE_USB_OTG, USB_ROLE_NONE); pls = (xudc_readl(xudc, PORTSC) & PORTSC_PLS_MASK) >> PORTSC_PLS_SHIFT; @@ -643,8 +649,6 @@ static void tegra_xudc_device_mode_off(struct tegra_xudc *xudc) xudc_writel(xudc, val, PORTSC); } - xudc->device_mode = USB_ROLE_NONE; - /* Wait for disconnect event. */ if (connected) wait_for_completion(&xudc->disconnect_complete); @@ -652,11 +656,11 @@ static void tegra_xudc_device_mode_off(struct tegra_xudc *xudc) /* Make sure interrupt handler has completed before powergating. */ synchronize_irq(xudc->irq); - err = phy_power_off(xudc->utmi_phy); + err = phy_power_off(xudc->curr_utmi_phy); if (err < 0) dev_err(xudc->dev, "utmi_phy power off failed %d\n", err); - err = phy_power_off(xudc->usb3_phy); + err = phy_power_off(xudc->curr_usb3_phy); if (err < 0) dev_err(xudc->dev, "usb3_phy power off failed %d\n", err); @@ -668,29 +672,57 @@ static void tegra_xudc_usb_role_sw_work(struct work_struct *work) struct tegra_xudc *xudc = container_of(work, struct tegra_xudc, usb_role_sw_work); - if (!xudc->usb_role_sw || - usb_role_switch_get_role(xudc->usb_role_sw) == USB_ROLE_DEVICE) + if (xudc->device_mode) tegra_xudc_device_mode_on(xudc); else tegra_xudc_device_mode_off(xudc); +} + +static int tegra_xudc_get_phy_index(struct tegra_xudc *xudc, + struct usb_phy *usbphy) +{ + unsigned int i; + for (i = 0; i < xudc->soc->num_phys; i++) { + if (xudc->usbphy[i] && usbphy == xudc->usbphy[i]) + return i; + } + + dev_info(xudc->dev, "phy index could not be found for shared USB PHY"); + return -1; } -static int tegra_xudc_usb_role_sw_set(struct device *dev, enum usb_role role) +static int tegra_xudc_vbus_notify(struct notifier_block *nb, + unsigned long action, void *data) { - struct tegra_xudc *xudc = dev_get_drvdata(dev); - unsigned long flags; + struct tegra_xudc *xudc = container_of(nb, struct tegra_xudc, + vbus_nb); + struct usb_phy *usbphy = (struct usb_phy *)data; + int phy_index; - dev_dbg(dev, "%s role is %d\n", __func__, role); + dev_dbg(xudc->dev, "%s(): event is %d\n", __func__, usbphy->last_event); - spin_lock_irqsave(&xudc->lock, flags); + if ((xudc->device_mode && usbphy->last_event == USB_EVENT_VBUS) || + (!xudc->device_mode && usbphy->last_event != USB_EVENT_VBUS)) { + dev_dbg(xudc->dev, "Same role(%d) received. Ignore", + xudc->device_mode); + return NOTIFY_OK; + } - if (!xudc->suspended) - schedule_work(&xudc->usb_role_sw_work); + xudc->device_mode = (usbphy->last_event == USB_EVENT_VBUS) ? true : + false; - spin_unlock_irqrestore(&xudc->lock, flags); + phy_index = tegra_xudc_get_phy_index(xudc, usbphy); + dev_dbg(xudc->dev, "%s(): current phy index is %d\n", __func__, + phy_index); - return 0; + if (!xudc->suspended && phy_index != -1) { + xudc->curr_utmi_phy = xudc->utmi_phy[phy_index]; + xudc->curr_usb3_phy = xudc->usb3_phy[phy_index]; + schedule_work(&xudc->usb_role_sw_work); + } + + return NOTIFY_OK; } static void tegra_xudc_plc_reset_work(struct work_struct *work) @@ -708,9 +740,11 @@ static void tegra_xudc_plc_reset_work(struct work_struct *work) if (pls == PORTSC_PLS_INACTIVE) { dev_info(xudc->dev, "PLS = Inactive. Toggle VBUS\n"); - tegra_xusb_padctl_set_vbus_override(xudc->padctl, - false); - tegra_xusb_padctl_set_vbus_override(xudc->padctl, true); + phy_set_mode_ext(xudc->curr_utmi_phy, PHY_MODE_USB_OTG, + USB_ROLE_NONE); + phy_set_mode_ext(xudc->curr_utmi_phy, PHY_MODE_USB_OTG, + USB_ROLE_DEVICE); + xudc->wait_csc = false; } } @@ -729,8 +763,7 @@ static void tegra_xudc_port_reset_war_work(struct work_struct *work) spin_lock_irqsave(&xudc->lock, flags); - if ((xudc->device_mode == USB_ROLE_DEVICE) - && xudc->wait_for_sec_prc) { + if (xudc->device_mode && xudc->wait_for_sec_prc) { pls = (xudc_readl(xudc, PORTSC) & PORTSC_PLS_MASK) >> PORTSC_PLS_SHIFT; dev_dbg(xudc->dev, "pls = %x\n", pls); @@ -738,7 +771,8 @@ static void tegra_xudc_port_reset_war_work(struct work_struct *work) if (pls == PORTSC_PLS_DISABLED) { dev_dbg(xudc->dev, "toggle vbus\n"); /* PRC doesn't complete in 100ms, toggle the vbus */ - ret = tegra_phy_xusb_utmi_port_reset(xudc->utmi_phy); + ret = tegra_phy_xusb_utmi_port_reset( + xudc->curr_utmi_phy); if (ret == 1) xudc->wait_for_sec_prc = 0; } @@ -1927,6 +1961,7 @@ static int tegra_xudc_gadget_start(struct usb_gadget *gadget, unsigned long flags; u32 val; int ret; + unsigned int i; if (!driver) return -EINVAL; @@ -1962,6 +1997,10 @@ static int tegra_xudc_gadget_start(struct usb_gadget *gadget, xudc_writel(xudc, val, CTRL); } + for (i = 0; i < xudc->soc->num_phys; i++) + if (xudc->usbphy[i]) + otg_set_peripheral(xudc->usbphy[i]->otg, gadget); + xudc->driver = driver; unlock: dev_dbg(xudc->dev, "%s: ret value is %d", __func__, ret); @@ -1977,11 +2016,16 @@ static int tegra_xudc_gadget_stop(struct usb_gadget *gadget) struct tegra_xudc *xudc = to_xudc(gadget); unsigned long flags; u32 val; + unsigned int i; pm_runtime_get_sync(xudc->dev); spin_lock_irqsave(&xudc->lock, flags); + for (i = 0; i < xudc->soc->num_phys; i++) + if (xudc->usbphy[i]) + otg_set_peripheral(xudc->usbphy[i]->otg, NULL); + val = xudc_readl(xudc, CTRL); val &= ~(CTRL_IE | CTRL_ENABLE); xudc_writel(xudc, val, CTRL); @@ -3314,33 +3358,120 @@ static void tegra_xudc_device_params_init(struct tegra_xudc *xudc) xudc_writel(xudc, val, CFG_DEV_SSPI_XFER); } -static int tegra_xudc_phy_init(struct tegra_xudc *xudc) +static int tegra_xudc_phy_get(struct tegra_xudc *xudc) { - int err; + int err = 0, usb3; + unsigned int i; - err = phy_init(xudc->utmi_phy); - if (err < 0) { - dev_err(xudc->dev, "utmi phy init failed: %d\n", err); - return err; - } + xudc->utmi_phy = devm_kcalloc(xudc->dev, xudc->soc->num_phys, + sizeof(*xudc->utmi_phy), GFP_KERNEL); + if (!xudc->utmi_phy) + return -ENOMEM; - err = phy_init(xudc->usb3_phy); - if (err < 0) { - dev_err(xudc->dev, "usb3 phy init failed: %d\n", err); - goto exit_utmi_phy; + xudc->usb3_phy = devm_kcalloc(xudc->dev, xudc->soc->num_phys, + sizeof(*xudc->usb3_phy), GFP_KERNEL); + if (!xudc->usb3_phy) + return -ENOMEM; + + xudc->usbphy = devm_kcalloc(xudc->dev, xudc->soc->num_phys, + sizeof(*xudc->usbphy), GFP_KERNEL); + if (!xudc->usbphy) + return -ENOMEM; + + xudc->vbus_nb.notifier_call = tegra_xudc_vbus_notify; + + for (i = 0; i < xudc->soc->num_phys; i++) { + char phy_name[] = "usb.-."; + + /* Get USB2 phy */ + snprintf(phy_name, sizeof(phy_name), "usb2-%d", i); + xudc->utmi_phy[i] = devm_phy_optional_get(xudc->dev, phy_name); + if (IS_ERR(xudc->utmi_phy[i])) { + err = PTR_ERR(xudc->utmi_phy[i]); + if (err != -EPROBE_DEFER) + dev_err(xudc->dev, "failed to get usb2-%d phy: %d\n", + i, err); + + goto clean_up; + } else if (xudc->utmi_phy[i]) { + /* Get usb-phy, if utmi phy is available */ + xudc->usbphy[i] = devm_usb_get_phy_by_node(xudc->dev, + xudc->utmi_phy[i]->dev.of_node, + &xudc->vbus_nb); + if (IS_ERR(xudc->usbphy[i])) { + err = PTR_ERR(xudc->usbphy[i]); + dev_err(xudc->dev, "failed to get usbphy-%d: %d\n", + i, err); + goto clean_up; + } + } else if (!xudc->utmi_phy[i]) { + /* if utmi phy is not available, ignore USB3 phy get */ + continue; + } + + /* Get USB3 phy */ + usb3 = tegra_xusb_padctl_get_usb3_companion(xudc->padctl, i); + if (usb3 < 0) + continue; + + snprintf(phy_name, sizeof(phy_name), "usb3-%d", usb3); + xudc->usb3_phy[i] = devm_phy_optional_get(xudc->dev, phy_name); + if (IS_ERR(xudc->usb3_phy[i])) { + err = PTR_ERR(xudc->usb3_phy[i]); + if (err != -EPROBE_DEFER) + dev_err(xudc->dev, "failed to get usb3-%d phy: %d\n", + usb3, err); + + goto clean_up; + } else if (xudc->usb3_phy[i]) + dev_dbg(xudc->dev, "usb3_phy-%d registered", usb3); } - return 0; + return err; + +clean_up: + for (i = 0; i < xudc->soc->num_phys; i++) { + xudc->usb3_phy[i] = NULL; + xudc->utmi_phy[i] = NULL; + xudc->usbphy[i] = NULL; + } -exit_utmi_phy: - phy_exit(xudc->utmi_phy); return err; } static void tegra_xudc_phy_exit(struct tegra_xudc *xudc) { - phy_exit(xudc->usb3_phy); - phy_exit(xudc->utmi_phy); + unsigned int i; + + for (i = 0; i < xudc->soc->num_phys; i++) { + phy_exit(xudc->usb3_phy[i]); + phy_exit(xudc->utmi_phy[i]); + } +} + +static int tegra_xudc_phy_init(struct tegra_xudc *xudc) +{ + int err; + unsigned int i; + + for (i = 0; i < xudc->soc->num_phys; i++) { + err = phy_init(xudc->utmi_phy[i]); + if (err < 0) { + dev_err(xudc->dev, "utmi phy init failed: %d\n", err); + goto exit_phy; + } + + err = phy_init(xudc->usb3_phy[i]); + if (err < 0) { + dev_err(xudc->dev, "usb3 phy init failed: %d\n", err); + goto exit_phy; + } + } + return 0; + +exit_phy: + tegra_xudc_phy_exit(xudc); + return err; } static const char * const tegra210_xudc_supply_names[] = { @@ -3368,6 +3499,7 @@ static struct tegra_xudc_soc tegra210_xudc_soc_data = { .num_supplies = ARRAY_SIZE(tegra210_xudc_supply_names), .clock_names = tegra210_xudc_clock_names, .num_clks = ARRAY_SIZE(tegra210_xudc_clock_names), + .num_phys = 4, .u1_enable = false, .u2_enable = true, .lpm_enable = false, @@ -3380,6 +3512,7 @@ static struct tegra_xudc_soc tegra210_xudc_soc_data = { static struct tegra_xudc_soc tegra186_xudc_soc_data = { .clock_names = tegra186_xudc_clock_names, .num_clks = ARRAY_SIZE(tegra186_xudc_clock_names), + .num_phys = 4, .u1_enable = true, .u2_enable = true, .lpm_enable = false, @@ -3457,7 +3590,6 @@ static int tegra_xudc_probe(struct platform_device *pdev) { struct tegra_xudc *xudc; struct resource *res; - struct usb_role_switch_desc role_sx_desc = { 0 }; unsigned int i; int err; @@ -3492,11 +3624,8 @@ static int tegra_xudc_probe(struct platform_device *pdev) } xudc->irq = platform_get_irq(pdev, 0); - if (xudc->irq < 0) { - dev_err(xudc->dev, "failed to get IRQ: %d\n", - xudc->irq); + if (xudc->irq < 0) return xudc->irq; - } err = devm_request_irq(&pdev->dev, xudc->irq, tegra_xudc_irq, 0, dev_name(&pdev->dev), xudc); @@ -3546,19 +3675,9 @@ static int tegra_xudc_probe(struct platform_device *pdev) goto put_padctl; } - xudc->usb3_phy = devm_phy_optional_get(&pdev->dev, "usb3"); - if (IS_ERR(xudc->usb3_phy)) { - err = PTR_ERR(xudc->usb3_phy); - dev_err(xudc->dev, "failed to get usb3 phy: %d\n", err); - goto disable_regulator; - } - - xudc->utmi_phy = devm_phy_optional_get(&pdev->dev, "usb2"); - if (IS_ERR(xudc->utmi_phy)) { - err = PTR_ERR(xudc->utmi_phy); - dev_err(xudc->dev, "failed to get usb2 phy: %d\n", err); + err = tegra_xudc_phy_get(xudc); + if (err) goto disable_regulator; - } err = tegra_xudc_powerdomain_init(xudc); if (err) @@ -3587,24 +3706,6 @@ static int tegra_xudc_probe(struct platform_device *pdev) INIT_DELAYED_WORK(&xudc->port_reset_war_work, tegra_xudc_port_reset_war_work); - if (of_property_read_bool(xudc->dev->of_node, "usb-role-switch")) { - role_sx_desc.set = tegra_xudc_usb_role_sw_set; - role_sx_desc.fwnode = dev_fwnode(xudc->dev); - - xudc->usb_role_sw = usb_role_switch_register(xudc->dev, - &role_sx_desc); - if (IS_ERR(xudc->usb_role_sw)) { - err = PTR_ERR(xudc->usb_role_sw); - dev_err(xudc->dev, "Failed to register USB role SW: %d", - err); - goto free_eps; - } - } else { - /* Set the mode as device mode and this keeps phy always ON */ - dev_info(xudc->dev, "Set usb role to device mode always"); - schedule_work(&xudc->usb_role_sw_work); - } - pm_runtime_enable(&pdev->dev); xudc->gadget.ops = &tegra_xudc_gadget_ops; @@ -3639,15 +3740,12 @@ put_padctl: static int tegra_xudc_remove(struct platform_device *pdev) { struct tegra_xudc *xudc = platform_get_drvdata(pdev); + unsigned int i; pm_runtime_get_sync(xudc->dev); cancel_delayed_work(&xudc->plc_reset_work); - - if (xudc->usb_role_sw) { - usb_role_switch_unregister(xudc->usb_role_sw); - cancel_work_sync(&xudc->usb_role_sw_work); - } + cancel_work_sync(&xudc->usb_role_sw_work); usb_del_gadget_udc(&xudc->gadget); @@ -3658,8 +3756,10 @@ static int tegra_xudc_remove(struct platform_device *pdev) regulator_bulk_disable(xudc->soc->num_supplies, xudc->supplies); - phy_power_off(xudc->utmi_phy); - phy_power_off(xudc->usb3_phy); + for (i = 0; i < xudc->soc->num_phys; i++) { + phy_power_off(xudc->utmi_phy[i]); + phy_power_off(xudc->usb3_phy[i]); + } tegra_xudc_phy_exit(xudc); diff --git a/drivers/usb/host/ehci-mv.c b/drivers/usb/host/ehci-mv.c index bd4f6ef534d9..1300c457d9ed 100644 --- a/drivers/usb/host/ehci-mv.c +++ b/drivers/usb/host/ehci-mv.c @@ -110,11 +110,12 @@ static int mv_ehci_probe(struct platform_device *pdev) struct resource *r; int retval = -ENODEV; u32 offset; + u32 status; if (usb_disabled()) return -ENODEV; - hcd = usb_create_hcd(&ehci_platform_hc_driver, &pdev->dev, "mv ehci"); + hcd = usb_create_hcd(&ehci_platform_hc_driver, &pdev->dev, dev_name(&pdev->dev)); if (!hcd) return -ENOMEM; @@ -213,6 +214,14 @@ static int mv_ehci_probe(struct platform_device *pdev) device_wakeup_enable(hcd->self.controller); } + if (of_usb_get_phy_mode(pdev->dev.of_node) == USBPHY_INTERFACE_MODE_HSIC) { + status = ehci_readl(ehci, &ehci->regs->port_status[0]); + /* These "reserved" bits actually enable HSIC mode. */ + status |= BIT(25); + status &= ~GENMASK(31, 30); + ehci_writel(ehci, status, &ehci->regs->port_status[0]); + } + dev_info(&pdev->dev, "successful find EHCI device with regs 0x%p irq %d" " working in %s mode\n", hcd->regs, hcd->irq, diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index b0882c13a1d1..1a48ab1bd3b2 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -384,7 +384,7 @@ MODULE_DEVICE_TABLE(pci, pci_ids); /* pci driver glue; this is a "new style" PCI driver module */ static struct pci_driver ehci_pci_driver = { - .name = (char *) hcd_name, + .name = hcd_name, .id_table = pci_ids, .probe = ehci_pci_probe, diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c index 769749ca5961..e4fc3f66d43b 100644 --- a/drivers/usb/host/ehci-platform.c +++ b/drivers/usb/host/ehci-platform.c @@ -29,6 +29,8 @@ #include <linux/of.h> #include <linux/platform_device.h> #include <linux/reset.h> +#include <linux/sys_soc.h> +#include <linux/timer.h> #include <linux/usb.h> #include <linux/usb/hcd.h> #include <linux/usb/ehci_pdriver.h> @@ -44,6 +46,9 @@ struct ehci_platform_priv { struct clk *clks[EHCI_MAX_CLKS]; struct reset_control *rsts; bool reset_on_resume; + bool quirk_poll; + struct timer_list poll_timer; + struct delayed_work poll_work; }; static const char hcd_name[] = "ehci-platform"; @@ -118,6 +123,111 @@ static struct usb_ehci_pdata ehci_platform_defaults = { .power_off = ehci_platform_power_off, }; +/** + * quirk_poll_check_port_status - Poll port_status if the device sticks + * @ehci: the ehci hcd pointer + * + * Since EHCI/OHCI controllers on R-Car Gen3 SoCs are possible to be getting + * stuck very rarely after a full/low usb device was disconnected. To + * detect such a situation, the controllers require a special way which poll + * the EHCI PORTSC register. + * + * Return: true if the controller's port_status indicated getting stuck + */ +static bool quirk_poll_check_port_status(struct ehci_hcd *ehci) +{ + u32 port_status = ehci_readl(ehci, &ehci->regs->port_status[0]); + + if (!(port_status & PORT_OWNER) && + (port_status & PORT_POWER) && + !(port_status & PORT_CONNECT) && + (port_status & PORT_LS_MASK)) + return true; + + return false; +} + +/** + * quirk_poll_rebind_companion - rebind comanion device to recover + * @ehci: the ehci hcd pointer + * + * Since EHCI/OHCI controllers on R-Car Gen3 SoCs are possible to be getting + * stuck very rarely after a full/low usb device was disconnected. To + * recover from such a situation, the controllers require changing the OHCI + * functional state. + */ +static void quirk_poll_rebind_companion(struct ehci_hcd *ehci) +{ + struct device *companion_dev; + struct usb_hcd *hcd = ehci_to_hcd(ehci); + + companion_dev = usb_of_get_companion_dev(hcd->self.controller); + if (!companion_dev) + return; + + device_release_driver(companion_dev); + if (device_attach(companion_dev) < 0) + ehci_err(ehci, "%s: failed\n", __func__); + + put_device(companion_dev); +} + +static void quirk_poll_work(struct work_struct *work) +{ + struct ehci_platform_priv *priv = + container_of(to_delayed_work(work), struct ehci_platform_priv, + poll_work); + struct ehci_hcd *ehci = container_of((void *)priv, struct ehci_hcd, + priv); + + /* check the status twice to reduce misdetection rate */ + if (!quirk_poll_check_port_status(ehci)) + return; + udelay(10); + if (!quirk_poll_check_port_status(ehci)) + return; + + ehci_dbg(ehci, "%s: detected getting stuck. rebind now!\n", __func__); + quirk_poll_rebind_companion(ehci); +} + +static void quirk_poll_timer(struct timer_list *t) +{ + struct ehci_platform_priv *priv = from_timer(priv, t, poll_timer); + struct ehci_hcd *ehci = container_of((void *)priv, struct ehci_hcd, + priv); + + if (quirk_poll_check_port_status(ehci)) { + /* + * Now scheduling the work for testing the port more. Note that + * updating the status is possible to be delayed when + * reconnection. So, this uses delayed work with 5 ms delay + * to avoid misdetection. + */ + schedule_delayed_work(&priv->poll_work, msecs_to_jiffies(5)); + } + + mod_timer(&priv->poll_timer, jiffies + HZ); +} + +static void quirk_poll_init(struct ehci_platform_priv *priv) +{ + INIT_DELAYED_WORK(&priv->poll_work, quirk_poll_work); + timer_setup(&priv->poll_timer, quirk_poll_timer, 0); + mod_timer(&priv->poll_timer, jiffies + HZ); +} + +static void quirk_poll_end(struct ehci_platform_priv *priv) +{ + del_timer_sync(&priv->poll_timer); + cancel_delayed_work(&priv->poll_work); +} + +static const struct soc_device_attribute quirk_poll_match[] = { + { .family = "R-Car Gen3" }, + { /* sentinel*/ } +}; + static int ehci_platform_probe(struct platform_device *dev) { struct usb_hcd *hcd; @@ -176,6 +286,9 @@ static int ehci_platform_probe(struct platform_device *dev) "has-transaction-translator")) hcd->has_tt = 1; + if (soc_device_match(quirk_poll_match)) + priv->quirk_poll = true; + for (clk = 0; clk < EHCI_MAX_CLKS; clk++) { priv->clks[clk] = of_clk_get(dev->dev.of_node, clk); if (IS_ERR(priv->clks[clk])) { @@ -247,6 +360,9 @@ static int ehci_platform_probe(struct platform_device *dev) device_enable_async_suspend(hcd->self.controller); platform_set_drvdata(dev, hcd); + if (priv->quirk_poll) + quirk_poll_init(priv); + return err; err_power: @@ -273,6 +389,9 @@ static int ehci_platform_remove(struct platform_device *dev) struct ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); int clk; + if (priv->quirk_poll) + quirk_poll_end(priv); + usb_remove_hcd(hcd); if (pdata->power_off) @@ -297,9 +416,13 @@ static int ehci_platform_suspend(struct device *dev) struct usb_hcd *hcd = dev_get_drvdata(dev); struct usb_ehci_pdata *pdata = dev_get_platdata(dev); struct platform_device *pdev = to_platform_device(dev); + struct ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); bool do_wakeup = device_may_wakeup(dev); int ret; + if (priv->quirk_poll) + quirk_poll_end(priv); + ret = ehci_suspend(hcd, do_wakeup); if (ret) return ret; @@ -331,6 +454,10 @@ static int ehci_platform_resume(struct device *dev) } ehci_resume(hcd, priv->reset_on_resume); + + if (priv->quirk_poll) + quirk_poll_init(priv); + return 0; } #endif /* CONFIG_PM_SLEEP */ diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c index d6433f206c17..10d51daa6a1b 100644 --- a/drivers/usb/host/ehci-tegra.c +++ b/drivers/usb/host/ehci-tegra.c @@ -282,7 +282,7 @@ done: struct dma_aligned_buffer { void *kmalloc_ptr; void *old_xfer_buffer; - u8 data[0]; + u8 data[]; }; static void free_dma_aligned_buffer(struct urb *urb) diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index ac5e967907d1..229b3de319e6 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -255,7 +255,7 @@ struct ehci_hcd { /* one per controller */ struct list_head tt_list; /* platform-specific data -- must come last */ - unsigned long priv[0] __aligned(sizeof(s64)); + unsigned long priv[] __aligned(sizeof(s64)); }; /* convert between an HCD pointer and the corresponding EHCI_HCD */ @@ -460,7 +460,7 @@ struct ehci_iso_sched { struct list_head td_list; unsigned span; unsigned first_packet; - struct ehci_iso_packet packet[0]; + struct ehci_iso_packet packet[]; }; /* diff --git a/drivers/usb/host/fhci-hcd.c b/drivers/usb/host/fhci-hcd.c index 04733876c9c6..a8e1048278d0 100644 --- a/drivers/usb/host/fhci-hcd.c +++ b/drivers/usb/host/fhci-hcd.c @@ -396,6 +396,7 @@ static int fhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, case PIPE_CONTROL: /* 1 td fro setup,1 for ack */ size = 2; + fallthrough; case PIPE_BULK: /* one td for every 4096 bytes(can be up to 8k) */ size += urb->transfer_buffer_length / 4096; diff --git a/drivers/usb/host/fotg210.h b/drivers/usb/host/fotg210.h index 1b4db95e5c43..6cee40ec65b4 100644 --- a/drivers/usb/host/fotg210.h +++ b/drivers/usb/host/fotg210.h @@ -490,7 +490,7 @@ struct fotg210_iso_packet { struct fotg210_iso_sched { struct list_head td_list; unsigned span; - struct fotg210_iso_packet packet[0]; + struct fotg210_iso_packet packet[]; }; /* diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index f4e13a3fddee..22117a6aeb4a 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -288,7 +288,7 @@ MODULE_DEVICE_TABLE (pci, pci_ids); /* pci driver glue; this is a "new style" PCI driver module */ static struct pci_driver ohci_pci_driver = { - .name = (char *) hcd_name, + .name = hcd_name, .id_table = pci_ids, .probe = usb_hcd_pci_probe, diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h index b015b00774b2..27c26ca10bfd 100644 --- a/drivers/usb/host/ohci.h +++ b/drivers/usb/host/ohci.h @@ -337,7 +337,7 @@ typedef struct urb_priv { u16 length; // # tds in this request u16 td_cnt; // tds already serviced struct list_head pending; - struct td *td [0]; // all TDs in this request + struct td *td[]; // all TDs in this request } urb_priv_t; @@ -435,7 +435,7 @@ struct ohci_hcd { struct dentry *debug_dir; /* platform-specific data -- must come last */ - unsigned long priv[0] __aligned(sizeof(s64)); + unsigned long priv[] __aligned(sizeof(s64)); }; diff --git a/drivers/usb/host/sl811-hcd.c b/drivers/usb/host/sl811-hcd.c index 72a34a1eb618..adaf4063690a 100644 --- a/drivers/usb/host/sl811-hcd.c +++ b/drivers/usb/host/sl811-hcd.c @@ -1792,7 +1792,7 @@ struct platform_driver sl811h_driver = { .suspend = sl811h_suspend, .resume = sl811h_resume, .driver = { - .name = (char *) hcd_name, + .name = hcd_name, }, }; EXPORT_SYMBOL(sl811h_driver); diff --git a/drivers/usb/host/uhci-pci.c b/drivers/usb/host/uhci-pci.c index 0fa3d72bae26..957c87efc746 100644 --- a/drivers/usb/host/uhci-pci.c +++ b/drivers/usb/host/uhci-pci.c @@ -294,7 +294,7 @@ static const struct pci_device_id uhci_pci_ids[] = { { MODULE_DEVICE_TABLE(pci, uhci_pci_ids); static struct pci_driver uhci_pci_driver = { - .name = (char *)hcd_name, + .name = hcd_name, .id_table = uhci_pci_ids, .probe = usb_hcd_pci_probe, diff --git a/drivers/usb/host/xhci-histb.c b/drivers/usb/host/xhci-histb.c index 3c4abb5a1c3f..5546e7e013a8 100644 --- a/drivers/usb/host/xhci-histb.c +++ b/drivers/usb/host/xhci-histb.c @@ -219,8 +219,7 @@ static int xhci_histb_probe(struct platform_device *pdev) if (irq < 0) return irq; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - histb->ctrl = devm_ioremap_resource(&pdev->dev, res); + histb->ctrl = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(histb->ctrl)) return PTR_ERR(histb->ctrl); diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index af92b2576fe9..9eca1fe81061 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -567,6 +567,7 @@ struct xhci_hub *xhci_get_rhub(struct usb_hcd *hcd) */ static void xhci_set_port_power(struct xhci_hcd *xhci, struct usb_hcd *hcd, u16 index, bool on, unsigned long *flags) + __must_hold(&xhci->lock) { struct xhci_hub *rhub; struct xhci_port *port; @@ -617,6 +618,7 @@ static void xhci_port_set_test_mode(struct xhci_hcd *xhci, static int xhci_enter_test_mode(struct xhci_hcd *xhci, u16 test_mode, u16 wIndex, unsigned long *flags) + __must_hold(&xhci->lock) { int i, retval; @@ -1306,7 +1308,47 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, wIndex, link_state); goto error; } + + /* + * set link to U0, steps depend on current link state. + * U3: set link to U0 and wait for u3exit completion. + * U1/U2: no PLC complete event, only set link to U0. + * Resume/Recovery: device initiated U0, only wait for + * completion + */ + if (link_state == USB_SS_PORT_LS_U0) { + u32 pls = temp & PORT_PLS_MASK; + bool wait_u0 = false; + + /* already in U0 */ + if (pls == XDEV_U0) + break; + if (pls == XDEV_U3 || + pls == XDEV_RESUME || + pls == XDEV_RECOVERY) { + wait_u0 = true; + reinit_completion(&bus_state->u3exit_done[wIndex]); + } + if (pls <= XDEV_U3) /* U1, U2, U3 */ + xhci_set_link_state(xhci, ports[wIndex], + USB_SS_PORT_LS_U0); + if (!wait_u0) { + if (pls > XDEV_U3) + goto error; + break; + } + spin_unlock_irqrestore(&xhci->lock, flags); + if (!wait_for_completion_timeout(&bus_state->u3exit_done[wIndex], + msecs_to_jiffies(100))) + xhci_dbg(xhci, "missing U0 port change event for port %d\n", + wIndex); + spin_lock_irqsave(&xhci->lock, flags); + temp = readl(ports[wIndex]->addr); + break; + } + if (link_state == USB_SS_PORT_LS_U3) { + int retries = 16; slot_id = xhci_find_slot_id_by_port(hcd, xhci, wIndex + 1); if (slot_id) { @@ -1317,17 +1359,18 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, xhci_stop_device(xhci, slot_id, 1); spin_lock_irqsave(&xhci->lock, flags); } - } - - xhci_set_link_state(xhci, ports[wIndex], link_state); - - spin_unlock_irqrestore(&xhci->lock, flags); - msleep(20); /* wait device to enter */ - spin_lock_irqsave(&xhci->lock, flags); - - temp = readl(ports[wIndex]->addr); - if (link_state == USB_SS_PORT_LS_U3) + xhci_set_link_state(xhci, ports[wIndex], USB_SS_PORT_LS_U3); + spin_unlock_irqrestore(&xhci->lock, flags); + while (retries--) { + usleep_range(4000, 8000); + temp = readl(ports[wIndex]->addr); + if ((temp & PORT_PLS_MASK) == XDEV_U3) + break; + } + spin_lock_irqsave(&xhci->lock, flags); + temp = readl(ports[wIndex]->addr); bus_state->suspended_ports |= 1 << wIndex; + } break; case USB_PORT_FEAT_POWER: /* diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 884c601bfa15..9764122c9cdf 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2552,6 +2552,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) xhci->usb3_rhub.bus_state.resume_done[i] = 0; /* Only the USB 2.0 completions will ever be used. */ init_completion(&xhci->usb2_rhub.bus_state.rexit_done[i]); + init_completion(&xhci->usb3_rhub.bus_state.u3exit_done[i]); } if (scratchpad_alloc(xhci, flags)) diff --git a/drivers/usb/host/xhci-mtk.h b/drivers/usb/host/xhci-mtk.h index 5ac458b7d2e0..acd56517215a 100644 --- a/drivers/usb/host/xhci-mtk.h +++ b/drivers/usb/host/xhci-mtk.h @@ -95,7 +95,7 @@ struct mu3h_sch_ep_info { u32 pkts; u32 cs_count; u32 burst_mode; - u32 bw_budget_table[0]; + u32 bw_budget_table[]; }; #define MU3C_U3_PORT_MAX 4 diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 1fddc41fa1f3..766b74723e64 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -50,6 +50,7 @@ #define PCI_DEVICE_ID_INTEL_TITAN_RIDGE_DD_XHCI 0x15f0 #define PCI_DEVICE_ID_INTEL_ICE_LAKE_XHCI 0x8a13 #define PCI_DEVICE_ID_INTEL_CML_XHCI 0xa3af +#define PCI_DEVICE_ID_INTEL_TIGER_LAKE_XHCI 0x9a13 #define PCI_DEVICE_ID_AMD_PROMONTORYA_4 0x43b9 #define PCI_DEVICE_ID_AMD_PROMONTORYA_3 0x43ba @@ -217,7 +218,8 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) pdev->device == PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_XHCI || pdev->device == PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_XHCI || pdev->device == PCI_DEVICE_ID_INTEL_TITAN_RIDGE_DD_XHCI || - pdev->device == PCI_DEVICE_ID_INTEL_ICE_LAKE_XHCI)) + pdev->device == PCI_DEVICE_ID_INTEL_ICE_LAKE_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_TIGER_LAKE_XHCI)) xhci->quirks |= XHCI_DEFAULT_PM_RUNTIME_ALLOW; if (pdev->vendor == PCI_VENDOR_ID_ETRON && @@ -244,6 +246,9 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) pdev->device == 0x3432) xhci->quirks |= XHCI_BROKEN_STREAMS; + if (pdev->vendor == PCI_VENDOR_ID_VIA && pdev->device == 0x3483) + xhci->quirks |= XHCI_LPM_SUPPORT; + if (pdev->vendor == PCI_VENDOR_ID_ASMEDIA && pdev->device == 0x1042) xhci->quirks |= XHCI_BROKEN_STREAMS; @@ -550,7 +555,7 @@ MODULE_DEVICE_TABLE(pci, pci_ids); /* pci driver glue; this is a "new style" PCI driver module */ static struct pci_driver xhci_pci_driver = { - .name = (char *) hcd_name, + .name = hcd_name, .id_table = pci_ids, .probe = xhci_pci_probe, diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 315b4552693c..1d4f6f85f0fe 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -219,8 +219,7 @@ static int xhci_plat_probe(struct platform_device *pdev) goto disable_runtime; } - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - hcd->regs = devm_ioremap_resource(&pdev->dev, res); + hcd->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(hcd->regs)) { ret = PTR_ERR(hcd->regs); goto put_hcd; diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index d23f7408c81f..a78787bb5133 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -955,6 +955,7 @@ void xhci_stop_endpoint_command_watchdog(struct timer_list *t) struct xhci_virt_ep *ep = from_timer(ep, t, stop_cmd_timer); struct xhci_hcd *xhci = ep->xhci; unsigned long flags; + u32 usbsts; spin_lock_irqsave(&xhci->lock, flags); @@ -965,8 +966,11 @@ void xhci_stop_endpoint_command_watchdog(struct timer_list *t) xhci_dbg(xhci, "Stop EP timer raced with cmd completion, exit"); return; } + usbsts = readl(&xhci->op_regs->status); xhci_warn(xhci, "xHCI host not responding to stop endpoint command.\n"); + xhci_warn(xhci, "USBSTS:%s\n", xhci_decode_usbsts(usbsts)); + ep->ep_state &= ~EP_STOP_CMD_PENDING; xhci_halt(xhci); @@ -1677,6 +1681,7 @@ static void handle_port_status(struct xhci_hcd *xhci, (portsc & PORT_PLS_MASK) == XDEV_U1 || (portsc & PORT_PLS_MASK) == XDEV_U2)) { xhci_dbg(xhci, "resume SS port %d finished\n", port_id); + complete(&bus_state->u3exit_done[hcd_portnum]); /* We've just brought the device into U0/1/2 through either the * Resume state after a device remote wakeup, or through the * U3Exit state after a host-initiated resume. If it's a device @@ -2413,6 +2418,10 @@ static int handle_tx_event(struct xhci_hcd *xhci, status = -EPIPE; break; case COMP_SPLIT_TRANSACTION_ERROR: + xhci_dbg(xhci, "Split transaction error for slot %u ep %u\n", + slot_id, ep_index); + status = -EPROTO; + break; case COMP_USB_TRANSACTION_ERROR: xhci_dbg(xhci, "Transfer error for slot %u ep %u on endpoint\n", slot_id, ep_index); diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index 8163aefc6c6b..2eaf5c0af80c 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -24,6 +24,9 @@ #include <linux/regulator/consumer.h> #include <linux/reset.h> #include <linux/slab.h> +#include <linux/usb/otg.h> +#include <linux/usb/phy.h> +#include <linux/usb/role.h> #include <soc/tegra/pmc.h> #include "xhci.h" @@ -203,6 +206,8 @@ struct tegra_xusb_soc { bool scale_ss_clock; bool has_ipfs; + bool lpm_support; + bool otg_reset_sspi; }; struct tegra_xusb_context { @@ -250,6 +255,14 @@ struct tegra_xusb { struct phy **phys; unsigned int num_phys; + struct usb_phy **usbphy; + unsigned int num_usb_phys; + int otg_usb2_port; + int otg_usb3_port; + bool host_mode; + struct notifier_block id_nb; + struct work_struct id_work; + /* Firmware loading related */ struct { size_t size; @@ -1081,6 +1094,205 @@ static int tegra_xusb_enable_firmware_messages(struct tegra_xusb *tegra) return err; } +static void tegra_xhci_set_port_power(struct tegra_xusb *tegra, bool main, + bool set) +{ + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + struct usb_hcd *hcd = main ? xhci->main_hcd : xhci->shared_hcd; + unsigned int wait = (!main && !set) ? 1000 : 10; + u16 typeReq = set ? SetPortFeature : ClearPortFeature; + u16 wIndex = main ? tegra->otg_usb2_port + 1 : tegra->otg_usb3_port + 1; + u32 status; + u32 stat_power = main ? USB_PORT_STAT_POWER : USB_SS_PORT_STAT_POWER; + u32 status_val = set ? stat_power : 0; + + dev_dbg(tegra->dev, "%s():%s %s port power\n", __func__, + set ? "set" : "clear", main ? "HS" : "SS"); + + hcd->driver->hub_control(hcd, typeReq, USB_PORT_FEAT_POWER, wIndex, + NULL, 0); + + do { + tegra_xhci_hc_driver.hub_control(hcd, GetPortStatus, 0, wIndex, + (char *) &status, sizeof(status)); + if (status_val == (status & stat_power)) + break; + + if (!main && !set) + usleep_range(600, 700); + else + usleep_range(10, 20); + } while (--wait > 0); + + if (status_val != (status & stat_power)) + dev_info(tegra->dev, "failed to %s %s PP %d\n", + set ? "set" : "clear", + main ? "HS" : "SS", status); +} + +static struct phy *tegra_xusb_get_phy(struct tegra_xusb *tegra, char *name, + int port) +{ + unsigned int i, phy_count = 0; + + for (i = 0; i < tegra->soc->num_types; i++) { + if (!strncmp(tegra->soc->phy_types[i].name, "usb2", + strlen(name))) + return tegra->phys[phy_count+port]; + + phy_count += tegra->soc->phy_types[i].num; + } + + return NULL; +} + +static void tegra_xhci_id_work(struct work_struct *work) +{ + struct tegra_xusb *tegra = container_of(work, struct tegra_xusb, + id_work); + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + struct tegra_xusb_mbox_msg msg; + struct phy *phy = tegra_xusb_get_phy(tegra, "usb2", + tegra->otg_usb2_port); + u32 status; + int ret; + + dev_dbg(tegra->dev, "host mode %s\n", tegra->host_mode ? "on" : "off"); + + mutex_lock(&tegra->lock); + + if (tegra->host_mode) + phy_set_mode_ext(phy, PHY_MODE_USB_OTG, USB_ROLE_HOST); + else + phy_set_mode_ext(phy, PHY_MODE_USB_OTG, USB_ROLE_NONE); + + mutex_unlock(&tegra->lock); + + if (tegra->host_mode) { + /* switch to host mode */ + if (tegra->otg_usb3_port >= 0) { + if (tegra->soc->otg_reset_sspi) { + /* set PP=0 */ + tegra_xhci_hc_driver.hub_control( + xhci->shared_hcd, GetPortStatus, + 0, tegra->otg_usb3_port+1, + (char *) &status, sizeof(status)); + if (status & USB_SS_PORT_STAT_POWER) + tegra_xhci_set_port_power(tegra, false, + false); + + /* reset OTG port SSPI */ + msg.cmd = MBOX_CMD_RESET_SSPI; + msg.data = tegra->otg_usb3_port+1; + + ret = tegra_xusb_mbox_send(tegra, &msg); + if (ret < 0) { + dev_info(tegra->dev, + "failed to RESET_SSPI %d\n", + ret); + } + } + + tegra_xhci_set_port_power(tegra, false, true); + } + + tegra_xhci_set_port_power(tegra, true, true); + + } else { + if (tegra->otg_usb3_port >= 0) + tegra_xhci_set_port_power(tegra, false, false); + + tegra_xhci_set_port_power(tegra, true, false); + } +} + +static int tegra_xusb_get_usb2_port(struct tegra_xusb *tegra, + struct usb_phy *usbphy) +{ + unsigned int i; + + for (i = 0; i < tegra->num_usb_phys; i++) { + if (tegra->usbphy[i] && usbphy == tegra->usbphy[i]) + return i; + } + + return -1; +} + +static int tegra_xhci_id_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct tegra_xusb *tegra = container_of(nb, struct tegra_xusb, + id_nb); + struct usb_phy *usbphy = (struct usb_phy *)data; + + dev_dbg(tegra->dev, "%s(): action is %d", __func__, usbphy->last_event); + + if ((tegra->host_mode && usbphy->last_event == USB_EVENT_ID) || + (!tegra->host_mode && usbphy->last_event != USB_EVENT_ID)) { + dev_dbg(tegra->dev, "Same role(%d) received. Ignore", + tegra->host_mode); + return NOTIFY_OK; + } + + tegra->otg_usb2_port = tegra_xusb_get_usb2_port(tegra, usbphy); + tegra->otg_usb3_port = tegra_xusb_padctl_get_usb3_companion( + tegra->padctl, + tegra->otg_usb2_port); + + tegra->host_mode = (usbphy->last_event == USB_EVENT_ID) ? true : false; + + schedule_work(&tegra->id_work); + + return NOTIFY_OK; +} + +static int tegra_xusb_init_usb_phy(struct tegra_xusb *tegra) +{ + unsigned int i; + + tegra->usbphy = devm_kcalloc(tegra->dev, tegra->num_usb_phys, + sizeof(*tegra->usbphy), GFP_KERNEL); + if (!tegra->usbphy) + return -ENOMEM; + + INIT_WORK(&tegra->id_work, tegra_xhci_id_work); + tegra->id_nb.notifier_call = tegra_xhci_id_notify; + + for (i = 0; i < tegra->num_usb_phys; i++) { + struct phy *phy = tegra_xusb_get_phy(tegra, "usb2", i); + + if (!phy) + continue; + + tegra->usbphy[i] = devm_usb_get_phy_by_node(tegra->dev, + phy->dev.of_node, + &tegra->id_nb); + if (!IS_ERR(tegra->usbphy[i])) { + dev_dbg(tegra->dev, "usbphy-%d registered", i); + otg_set_host(tegra->usbphy[i]->otg, &tegra->hcd->self); + } else { + /* + * usb-phy is optional, continue if its not available. + */ + tegra->usbphy[i] = NULL; + } + } + + return 0; +} + +static void tegra_xusb_deinit_usb_phy(struct tegra_xusb *tegra) +{ + unsigned int i; + + cancel_work_sync(&tegra->id_work); + + for (i = 0; i < tegra->num_usb_phys; i++) + if (tegra->usbphy[i]) + otg_set_host(tegra->usbphy[i]->otg, NULL); +} + static int tegra_xusb_probe(struct platform_device *pdev) { struct tegra_xusb *tegra; @@ -1254,8 +1466,11 @@ static int tegra_xusb_probe(struct platform_device *pdev) goto put_powerdomains; } - for (i = 0; i < tegra->soc->num_types; i++) + for (i = 0; i < tegra->soc->num_types; i++) { + if (!strncmp(tegra->soc->phy_types[i].name, "usb2", 4)) + tegra->num_usb_phys = tegra->soc->phy_types[i].num; tegra->num_phys += tegra->soc->phy_types[i].num; + } tegra->phys = devm_kcalloc(&pdev->dev, tegra->num_phys, sizeof(*tegra->phys), GFP_KERNEL); @@ -1384,6 +1599,12 @@ static int tegra_xusb_probe(struct platform_device *pdev) goto remove_usb3; } + err = tegra_xusb_init_usb_phy(tegra); + if (err < 0) { + dev_err(&pdev->dev, "failed to init USB PHY: %d\n", err); + goto remove_usb3; + } + return 0; remove_usb3: @@ -1420,6 +1641,8 @@ static int tegra_xusb_remove(struct platform_device *pdev) struct tegra_xusb *tegra = platform_get_drvdata(pdev); struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + tegra_xusb_deinit_usb_phy(tegra); + usb_remove_hcd(xhci->shared_hcd); usb_put_hcd(xhci->shared_hcd); xhci->shared_hcd = NULL; @@ -1694,6 +1917,7 @@ static const struct tegra_xusb_soc tegra124_soc = { }, .scale_ss_clock = true, .has_ipfs = true, + .otg_reset_sspi = false, .mbox = { .cmd = 0xe4, .data_in = 0xe8, @@ -1733,6 +1957,7 @@ static const struct tegra_xusb_soc tegra210_soc = { }, .scale_ss_clock = false, .has_ipfs = true, + .otg_reset_sspi = true, .mbox = { .cmd = 0xe4, .data_in = 0xe8, @@ -1773,12 +1998,14 @@ static const struct tegra_xusb_soc tegra186_soc = { }, .scale_ss_clock = false, .has_ipfs = false, + .otg_reset_sspi = false, .mbox = { .cmd = 0xe4, .data_in = 0xe8, .data_out = 0xec, .owner = 0xf0, }, + .lpm_support = true, }; static const char * const tegra194_supply_names[] = { @@ -1802,12 +2029,14 @@ static const struct tegra_xusb_soc tegra194_soc = { }, .scale_ss_clock = false, .has_ipfs = false, + .otg_reset_sspi = false, .mbox = { .cmd = 0x68, .data_in = 0x6c, .data_out = 0x70, .owner = 0x74, }, + .lpm_support = true, }; MODULE_FIRMWARE("nvidia/tegra194/xusb.bin"); @@ -1832,7 +2061,11 @@ static struct platform_driver tegra_xusb_driver = { 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; } static int tegra_xhci_setup(struct usb_hcd *hcd) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index dbac0fa9748d..fe38275363e0 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1157,8 +1157,10 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) xhci_dbg(xhci, "Stop HCD\n"); xhci_halt(xhci); xhci_zero_64b_regs(xhci); - xhci_reset(xhci); + retval = xhci_reset(xhci); spin_unlock_irq(&xhci->lock); + if (retval) + return retval; xhci_cleanup_msix(xhci); xhci_dbg(xhci, "// Disabling event ring interrupts\n"); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 3ecee10fdcdc..3289bb516201 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1642,7 +1642,7 @@ struct xhci_scratchpad { struct urb_priv { int num_tds; int num_tds_done; - struct xhci_td td[0]; + struct xhci_td td[]; }; /* @@ -1694,6 +1694,7 @@ struct xhci_bus_state { /* Which ports are waiting on RExit to U0 transition. */ unsigned long rexit_ports; struct completion rexit_done[USB_MAXCHILDREN]; + struct completion u3exit_done[USB_MAXCHILDREN]; }; @@ -1901,7 +1902,7 @@ struct xhci_hcd { void *dbc; /* platform-specific data -- must come last */ - unsigned long priv[0] __aligned(sizeof(s64)); + unsigned long priv[] __aligned(sizeof(s64)); }; /* Platform specific overrides to generic XHCI hc_driver ops */ @@ -2589,6 +2590,35 @@ static inline const char *xhci_decode_portsc(u32 portsc) return str; } +static inline const char *xhci_decode_usbsts(u32 usbsts) +{ + static char str[256]; + int ret = 0; + + if (usbsts == ~(u32)0) + return " 0xffffffff"; + if (usbsts & STS_HALT) + ret += sprintf(str + ret, " HCHalted"); + if (usbsts & STS_FATAL) + ret += sprintf(str + ret, " HSE"); + if (usbsts & STS_EINT) + ret += sprintf(str + ret, " EINT"); + if (usbsts & STS_PORT) + ret += sprintf(str + ret, " PCD"); + if (usbsts & STS_SAVE) + ret += sprintf(str + ret, " SSS"); + if (usbsts & STS_RESTORE) + ret += sprintf(str + ret, " RSS"); + if (usbsts & STS_SRE) + ret += sprintf(str + ret, " SRE"); + if (usbsts & STS_CNR) + ret += sprintf(str + ret, " CNR"); + if (usbsts & STS_HCE) + ret += sprintf(str + ret, " HCE"); + + return str; +} + static inline const char *xhci_decode_doorbell(u32 slot, u32 doorbell) { static char str[256]; diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 834b2494da73..833a460ee55a 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -137,6 +137,16 @@ config USB_APPLEDISPLAY Say Y here if you want to control the backlight of Apple Cinema Displays over USB. This driver provides a sysfs interface. +config APPLE_MFI_FASTCHARGE + tristate "Fast charge control for iOS devices" + select POWER_SUPPLY + help + Say Y here if you want to control whether iOS devices will + fast charge from the USB interface, as implemented in "MFi" + chargers. + + It is safe to say M here. + source "drivers/usb/misc/sisusbvga/Kconfig" config USB_LD diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index 0d416eb624bb..da39bddb0604 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_USB_EMI26) += emi26.o obj-$(CONFIG_USB_EMI62) += emi62.o obj-$(CONFIG_USB_EZUSB_FX2) += ezusb.o obj-$(CONFIG_USB_FTDI_ELAN) += ftdi-elan.o +obj-$(CONFIG_APPLE_MFI_FASTCHARGE) += apple-mfi-fastcharge.o obj-$(CONFIG_USB_IDMOUSE) += idmouse.o obj-$(CONFIG_USB_IOWARRIOR) += iowarrior.o obj-$(CONFIG_USB_ISIGHTFW) += isight_firmware.o diff --git a/drivers/usb/misc/apple-mfi-fastcharge.c b/drivers/usb/misc/apple-mfi-fastcharge.c new file mode 100644 index 000000000000..b403094a6b3a --- /dev/null +++ b/drivers/usb/misc/apple-mfi-fastcharge.c @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Fast-charge control for Apple "MFi" devices + * + * Copyright (C) 2019 Bastien Nocera <hadess@hadess.net> + */ + +/* Standard include files */ +#include <linux/module.h> +#include <linux/power_supply.h> +#include <linux/slab.h> +#include <linux/usb.h> + +MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>"); +MODULE_DESCRIPTION("Fast-charge control for Apple \"MFi\" devices"); +MODULE_LICENSE("GPL"); + +#define TRICKLE_CURRENT_MA 0 +#define FAST_CURRENT_MA 2500 + +#define APPLE_VENDOR_ID 0x05ac /* Apple */ + +/* The product ID is defined as starting with 0x12nn, as per the + * "Choosing an Apple Device USB Configuration" section in + * release R9 (2012) of the "MFi Accessory Hardware Specification" + * + * To distinguish an Apple device, a USB host can check the device + * descriptor of attached USB devices for the following fields: + * ■ Vendor ID: 0x05AC + * ■ Product ID: 0x12nn + * + * Those checks will be done in .match() and .probe(). + */ + +static const struct usb_device_id mfi_fc_id_table[] = { + { .idVendor = APPLE_VENDOR_ID, + .match_flags = USB_DEVICE_ID_MATCH_VENDOR }, + {}, +}; + +MODULE_DEVICE_TABLE(usb, mfi_fc_id_table); + +/* Driver-local specific stuff */ +struct mfi_device { + struct usb_device *udev; + struct power_supply *battery; + int charge_type; +}; + +static int apple_mfi_fc_set_charge_type(struct mfi_device *mfi, + const union power_supply_propval *val) +{ + int current_ma; + int retval; + __u8 request_type; + + if (mfi->charge_type == val->intval) { + dev_dbg(&mfi->udev->dev, "charge type %d already set\n", + mfi->charge_type); + return 0; + } + + switch (val->intval) { + case POWER_SUPPLY_CHARGE_TYPE_TRICKLE: + current_ma = TRICKLE_CURRENT_MA; + break; + case POWER_SUPPLY_CHARGE_TYPE_FAST: + current_ma = FAST_CURRENT_MA; + break; + default: + return -EINVAL; + } + + request_type = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE; + retval = usb_control_msg(mfi->udev, usb_sndctrlpipe(mfi->udev, 0), + 0x40, /* Vendor‐defined power request */ + request_type, + current_ma, /* wValue, current offset */ + current_ma, /* wIndex, current offset */ + NULL, 0, USB_CTRL_GET_TIMEOUT); + if (retval) { + dev_dbg(&mfi->udev->dev, "retval = %d\n", retval); + return retval; + } + + mfi->charge_type = val->intval; + + return 0; +} + +static int apple_mfi_fc_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct mfi_device *mfi = power_supply_get_drvdata(psy); + + dev_dbg(&mfi->udev->dev, "prop: %d\n", psp); + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = mfi->charge_type; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_DEVICE; + break; + default: + return -ENODATA; + } + + return 0; +} + +static int apple_mfi_fc_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct mfi_device *mfi = power_supply_get_drvdata(psy); + int ret; + + dev_dbg(&mfi->udev->dev, "prop: %d\n", psp); + + ret = pm_runtime_get_sync(&mfi->udev->dev); + if (ret < 0) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = apple_mfi_fc_set_charge_type(mfi, val); + break; + default: + ret = -EINVAL; + } + + pm_runtime_mark_last_busy(&mfi->udev->dev); + pm_runtime_put_autosuspend(&mfi->udev->dev); + + return ret; +} + +static int apple_mfi_fc_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPE: + return 1; + default: + return 0; + } +} + +static enum power_supply_property apple_mfi_fc_properties[] = { + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_SCOPE +}; + +static const struct power_supply_desc apple_mfi_fc_desc = { + .name = "apple_mfi_fastcharge", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = apple_mfi_fc_properties, + .num_properties = ARRAY_SIZE(apple_mfi_fc_properties), + .get_property = apple_mfi_fc_get_property, + .set_property = apple_mfi_fc_set_property, + .property_is_writeable = apple_mfi_fc_property_is_writeable +}; + +static int mfi_fc_probe(struct usb_device *udev) +{ + struct power_supply_config battery_cfg = {}; + struct mfi_device *mfi = NULL; + int err, idProduct; + + idProduct = le16_to_cpu(udev->descriptor.idProduct); + /* See comment above mfi_fc_id_table[] */ + if (idProduct < 0x1200 || idProduct > 0x12ff) { + return -ENODEV; + } + + mfi = kzalloc(sizeof(struct mfi_device), GFP_KERNEL); + if (!mfi) { + err = -ENOMEM; + goto error; + } + + battery_cfg.drv_data = mfi; + + mfi->charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + mfi->battery = power_supply_register(&udev->dev, + &apple_mfi_fc_desc, + &battery_cfg); + if (IS_ERR(mfi->battery)) { + dev_err(&udev->dev, "Can't register battery\n"); + err = PTR_ERR(mfi->battery); + goto error; + } + + mfi->udev = usb_get_dev(udev); + dev_set_drvdata(&udev->dev, mfi); + + return 0; + +error: + kfree(mfi); + return err; +} + +static void mfi_fc_disconnect(struct usb_device *udev) +{ + struct mfi_device *mfi; + + mfi = dev_get_drvdata(&udev->dev); + if (mfi->battery) + power_supply_unregister(mfi->battery); + dev_set_drvdata(&udev->dev, NULL); + usb_put_dev(mfi->udev); + kfree(mfi); +} + +static struct usb_device_driver mfi_fc_driver = { + .name = "apple-mfi-fastcharge", + .probe = mfi_fc_probe, + .disconnect = mfi_fc_disconnect, + .id_table = mfi_fc_id_table, + .generic_subclass = 1, +}; + +static int __init mfi_fc_driver_init(void) +{ + return usb_register_device_driver(&mfi_fc_driver, THIS_MODULE); +} + +static void __exit mfi_fc_driver_exit(void) +{ + usb_deregister_device_driver(&mfi_fc_driver); +} + +module_init(mfi_fc_driver_init); +module_exit(mfi_fc_driver_exit); diff --git a/drivers/usb/mon/mon_text.c b/drivers/usb/mon/mon_text.c index bc5ecd5ff565..39cb14164652 100644 --- a/drivers/usb/mon/mon_text.c +++ b/drivers/usb/mon/mon_text.c @@ -414,7 +414,7 @@ static ssize_t mon_text_read_t(struct file *file, char __user *buf, mon_text_read_head_t(rp, &ptr, ep); mon_text_read_statset(rp, &ptr, ep); - ptr.cnt += snprintf(ptr.pbuf + ptr.cnt, ptr.limit - ptr.cnt, + ptr.cnt += scnprintf(ptr.pbuf + ptr.cnt, ptr.limit - ptr.cnt, " %d", ep->length); mon_text_read_data(rp, &ptr, ep); @@ -462,7 +462,7 @@ static ssize_t mon_text_read_u(struct file *file, char __user *buf, } else { mon_text_read_statset(rp, &ptr, ep); } - ptr.cnt += snprintf(ptr.pbuf + ptr.cnt, ptr.limit - ptr.cnt, + ptr.cnt += scnprintf(ptr.pbuf + ptr.cnt, ptr.limit - ptr.cnt, " %d", ep->length); mon_text_read_data(rp, &ptr, ep); @@ -520,7 +520,7 @@ static void mon_text_read_head_t(struct mon_reader_text *rp, case USB_ENDPOINT_XFER_CONTROL: utype = 'C'; break; default: /* PIPE_BULK */ utype = 'B'; } - p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + p->cnt += scnprintf(p->pbuf + p->cnt, p->limit - p->cnt, "%lx %u %c %c%c:%03u:%02u", ep->id, ep->tstamp, ep->type, utype, udir, ep->devnum, ep->epnum); @@ -538,7 +538,7 @@ static void mon_text_read_head_u(struct mon_reader_text *rp, case USB_ENDPOINT_XFER_CONTROL: utype = 'C'; break; default: /* PIPE_BULK */ utype = 'B'; } - p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + p->cnt += scnprintf(p->pbuf + p->cnt, p->limit - p->cnt, "%lx %u %c %c%c:%d:%03u:%u", ep->id, ep->tstamp, ep->type, utype, udir, ep->busnum, ep->devnum, ep->epnum); @@ -549,7 +549,7 @@ static void mon_text_read_statset(struct mon_reader_text *rp, { if (ep->setup_flag == 0) { /* Setup packet is present and captured */ - p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + p->cnt += scnprintf(p->pbuf + p->cnt, p->limit - p->cnt, " s %02x %02x %04x %04x %04x", ep->setup[0], ep->setup[1], @@ -557,10 +557,10 @@ static void mon_text_read_statset(struct mon_reader_text *rp, (ep->setup[5] << 8) | ep->setup[4], (ep->setup[7] << 8) | ep->setup[6]); } else if (ep->setup_flag != '-') { /* Unable to capture setup packet */ - p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + p->cnt += scnprintf(p->pbuf + p->cnt, p->limit - p->cnt, " %c __ __ ____ ____ ____", ep->setup_flag); } else { /* No setup for this kind of URB */ - p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + p->cnt += scnprintf(p->pbuf + p->cnt, p->limit - p->cnt, " %d", ep->status); } } @@ -568,7 +568,7 @@ static void mon_text_read_statset(struct mon_reader_text *rp, static void mon_text_read_intstat(struct mon_reader_text *rp, struct mon_text_ptr *p, const struct mon_event_text *ep) { - p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + p->cnt += scnprintf(p->pbuf + p->cnt, p->limit - p->cnt, " %d:%d", ep->status, ep->interval); } @@ -576,10 +576,10 @@ static void mon_text_read_isostat(struct mon_reader_text *rp, struct mon_text_ptr *p, const struct mon_event_text *ep) { if (ep->type == 'S') { - p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + p->cnt += scnprintf(p->pbuf + p->cnt, p->limit - p->cnt, " %d:%d:%d", ep->status, ep->interval, ep->start_frame); } else { - p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + p->cnt += scnprintf(p->pbuf + p->cnt, p->limit - p->cnt, " %d:%d:%d:%d", ep->status, ep->interval, ep->start_frame, ep->error_count); } @@ -592,7 +592,7 @@ static void mon_text_read_isodesc(struct mon_reader_text *rp, int i; const struct mon_iso_desc *dp; - p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + p->cnt += scnprintf(p->pbuf + p->cnt, p->limit - p->cnt, " %d", ep->numdesc); ndesc = ep->numdesc; if (ndesc > ISODESC_MAX) @@ -601,7 +601,7 @@ static void mon_text_read_isodesc(struct mon_reader_text *rp, ndesc = 0; dp = ep->isodesc; for (i = 0; i < ndesc; i++) { - p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + p->cnt += scnprintf(p->pbuf + p->cnt, p->limit - p->cnt, " %d:%u:%u", dp->status, dp->offset, dp->length); dp++; } @@ -614,28 +614,28 @@ static void mon_text_read_data(struct mon_reader_text *rp, if ((data_len = ep->length) > 0) { if (ep->data_flag == 0) { - p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + p->cnt += scnprintf(p->pbuf + p->cnt, p->limit - p->cnt, " ="); if (data_len >= DATA_MAX) data_len = DATA_MAX; for (i = 0; i < data_len; i++) { if (i % 4 == 0) { - p->cnt += snprintf(p->pbuf + p->cnt, + p->cnt += scnprintf(p->pbuf + p->cnt, p->limit - p->cnt, " "); } - p->cnt += snprintf(p->pbuf + p->cnt, + p->cnt += scnprintf(p->pbuf + p->cnt, p->limit - p->cnt, "%02x", ep->data[i]); } - p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + p->cnt += scnprintf(p->pbuf + p->cnt, p->limit - p->cnt, "\n"); } else { - p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + p->cnt += scnprintf(p->pbuf + p->cnt, p->limit - p->cnt, " %c\n", ep->data_flag); } } else { - p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, "\n"); + p->cnt += scnprintf(p->pbuf + p->cnt, p->limit - p->cnt, "\n"); } } diff --git a/drivers/usb/mtu3/mtu3_dr.c b/drivers/usb/mtu3/mtu3_dr.c index 08e18448e8b8..04f666e85731 100644 --- a/drivers/usb/mtu3/mtu3_dr.c +++ b/drivers/usb/mtu3/mtu3_dr.c @@ -320,9 +320,9 @@ void ssusb_set_force_mode(struct ssusb_mtk *ssusb, mtu3_writel(ssusb->ippc_base, SSUSB_U2_CTRL(0), value); } -static int ssusb_role_sw_set(struct device *dev, enum usb_role role) +static int ssusb_role_sw_set(struct usb_role_switch *sw, enum usb_role role) { - struct ssusb_mtk *ssusb = dev_get_drvdata(dev); + struct ssusb_mtk *ssusb = usb_role_switch_get_drvdata(sw); bool to_host = false; if (role == USB_ROLE_HOST) @@ -334,9 +334,9 @@ static int ssusb_role_sw_set(struct device *dev, enum usb_role role) return 0; } -static enum usb_role ssusb_role_sw_get(struct device *dev) +static enum usb_role ssusb_role_sw_get(struct usb_role_switch *sw) { - struct ssusb_mtk *ssusb = dev_get_drvdata(dev); + struct ssusb_mtk *ssusb = usb_role_switch_get_drvdata(sw); enum usb_role role; role = ssusb->is_host ? USB_ROLE_HOST : USB_ROLE_DEVICE; @@ -356,6 +356,7 @@ static int ssusb_role_sw_register(struct otg_switch_mtk *otg_sx) role_sx_desc.set = ssusb_role_sw_set; role_sx_desc.get = ssusb_role_sw_get; role_sx_desc.fwnode = dev_fwnode(ssusb->dev); + role_sx_desc.driver_data = ssusb; otg_sx->role_sw = usb_role_switch_register(ssusb->dev, &role_sx_desc); return PTR_ERR_OR_ZERO(otg_sx->role_sw); diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig index eb2ded1026ee..3b0d1c20ebe6 100644 --- a/drivers/usb/musb/Kconfig +++ b/drivers/usb/musb/Kconfig @@ -110,9 +110,11 @@ config USB_MUSB_UX500 config USB_MUSB_JZ4740 tristate "JZ4740" + depends on OF depends on MIPS || COMPILE_TEST depends on USB_MUSB_GADGET depends on USB=n || USB_OTG_BLACKLIST_HUB + select USB_ROLE_SWITCH config USB_MUSB_MEDIATEK tristate "MediaTek platforms" @@ -144,7 +146,7 @@ config USB_UX500_DMA config USB_INVENTRA_DMA bool 'Inventra' - depends on USB_MUSB_OMAP2PLUS || USB_MUSB_MEDIATEK + depends on USB_MUSB_OMAP2PLUS || USB_MUSB_MEDIATEK || USB_MUSB_JZ4740 help Enable DMA transfers using Mentor's engine. diff --git a/drivers/usb/musb/jz4740.c b/drivers/usb/musb/jz4740.c index bc0109f4700b..e64dd30e80e7 100644 --- a/drivers/usb/musb/jz4740.c +++ b/drivers/usb/musb/jz4740.c @@ -12,23 +12,29 @@ #include <linux/module.h> #include <linux/of_device.h> #include <linux/platform_device.h> +#include <linux/usb/role.h> #include <linux/usb/usb_phy_generic.h> #include "musb_core.h" struct jz4740_glue { struct platform_device *pdev; + struct musb *musb; struct clk *clk; + struct usb_role_switch *role_sw; }; static irqreturn_t jz4740_musb_interrupt(int irq, void *__hci) { unsigned long flags; - irqreturn_t retval = IRQ_NONE; + irqreturn_t retval = IRQ_NONE, retval_dma = IRQ_NONE; struct musb *musb = __hci; spin_lock_irqsave(&musb->lock, flags); + if (IS_ENABLED(CONFIG_USB_INVENTRA_DMA) && musb->dma_controller) + retval_dma = dma_controller_irq(irq, musb->dma_controller); + musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB); musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX); musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX); @@ -46,7 +52,10 @@ static irqreturn_t jz4740_musb_interrupt(int irq, void *__hci) spin_unlock_irqrestore(&musb->lock, flags); - return retval; + if (retval == IRQ_HANDLED || retval_dma == IRQ_HANDLED) + return IRQ_HANDLED; + + return IRQ_NONE; } static struct musb_fifo_cfg jz4740_musb_fifo_cfg[] = { @@ -66,11 +75,40 @@ static const struct musb_hdrc_config jz4740_musb_config = { .fifo_cfg_size = ARRAY_SIZE(jz4740_musb_fifo_cfg), }; +static int jz4740_musb_role_switch_set(struct usb_role_switch *sw, + enum usb_role role) +{ + struct jz4740_glue *glue = usb_role_switch_get_drvdata(sw); + struct usb_phy *phy = glue->musb->xceiv; + + switch (role) { + case USB_ROLE_NONE: + atomic_notifier_call_chain(&phy->notifier, USB_EVENT_NONE, phy); + break; + case USB_ROLE_DEVICE: + atomic_notifier_call_chain(&phy->notifier, USB_EVENT_VBUS, phy); + break; + case USB_ROLE_HOST: + atomic_notifier_call_chain(&phy->notifier, USB_EVENT_ID, phy); + break; + } + + return 0; +} + static int jz4740_musb_init(struct musb *musb) { struct device *dev = musb->controller->parent; + struct jz4740_glue *glue = dev_get_drvdata(dev); + struct usb_role_switch_desc role_sw_desc = { + .set = jz4740_musb_role_switch_set, + .driver_data = glue, + .fwnode = dev_fwnode(dev), + }; int err; + glue->musb = musb; + if (dev->of_node) musb->xceiv = devm_usb_get_phy_by_phandle(dev, "phys", 0); else @@ -82,6 +120,12 @@ static int jz4740_musb_init(struct musb *musb) return err; } + glue->role_sw = usb_role_switch_register(dev, &role_sw_desc); + if (IS_ERR(glue->role_sw)) { + dev_err(dev, "Failed to register USB role switch"); + return PTR_ERR(glue->role_sw); + } + /* * Silicon does not implement ConfigData register. * Set dyn_fifo to avoid reading EP config from hardware. @@ -93,14 +137,24 @@ static int jz4740_musb_init(struct musb *musb) return 0; } -/* - * DMA has not been confirmed to work with CONFIG_USB_INVENTRA_DMA, - * so let's not set up the dma function pointers yet. - */ +static int jz4740_musb_exit(struct musb *musb) +{ + struct jz4740_glue *glue = dev_get_drvdata(musb->controller->parent); + + usb_role_switch_unregister(glue->role_sw); + + return 0; +} + static const struct musb_platform_ops jz4740_musb_ops = { .quirks = MUSB_DMA_INVENTRA | MUSB_INDEXED_EP, .fifo_mode = 2, .init = jz4740_musb_init, + .exit = jz4740_musb_exit, +#ifdef CONFIG_USB_INVENTRA_DMA + .dma_init = musbhs_dma_controller_create_noirq, + .dma_exit = musbhs_dma_controller_destroy, +#endif }; static const struct musb_hdrc_platform_data jz4740_musb_pdata = { @@ -109,10 +163,37 @@ static const struct musb_hdrc_platform_data jz4740_musb_pdata = { .platform_ops = &jz4740_musb_ops, }; +static struct musb_fifo_cfg jz4770_musb_fifo_cfg[] = { + { .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 2, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 3, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 3, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 4, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 4, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 5, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 5, .style = FIFO_RX, .maxpacket = 512, }, +}; + +static struct musb_hdrc_config jz4770_musb_config = { + .multipoint = 1, + .num_eps = 11, + .ram_bits = 11, + .fifo_cfg = jz4770_musb_fifo_cfg, + .fifo_cfg_size = ARRAY_SIZE(jz4770_musb_fifo_cfg), +}; + +static const struct musb_hdrc_platform_data jz4770_musb_pdata = { + .mode = MUSB_PERIPHERAL, /* TODO: support OTG */ + .config = &jz4770_musb_config, + .platform_ops = &jz4740_musb_ops, +}; + static int jz4740_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - const struct musb_hdrc_platform_data *pdata = &jz4740_musb_pdata; + const struct musb_hdrc_platform_data *pdata; struct platform_device *musb; struct jz4740_glue *glue; struct clk *clk; @@ -122,6 +203,12 @@ static int jz4740_probe(struct platform_device *pdev) if (!glue) return -ENOMEM; + pdata = of_device_get_match_data(dev); + if (!pdata) { + dev_err(dev, "missing platform data"); + return -EINVAL; + } + musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO); if (!musb) { dev_err(dev, "failed to allocate musb device"); @@ -142,6 +229,8 @@ static int jz4740_probe(struct platform_device *pdev) } musb->dev.parent = dev; + musb->dev.dma_mask = &musb->dev.coherent_dma_mask; + musb->dev.coherent_dma_mask = DMA_BIT_MASK(32); glue->pdev = musb; glue->clk = clk; @@ -186,20 +275,19 @@ static int jz4740_remove(struct platform_device *pdev) return 0; } -#ifdef CONFIG_OF static const struct of_device_id jz4740_musb_of_match[] = { - { .compatible = "ingenic,jz4740-musb" }, + { .compatible = "ingenic,jz4740-musb", .data = &jz4740_musb_pdata }, + { .compatible = "ingenic,jz4770-musb", .data = &jz4770_musb_pdata }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, jz4740_musb_of_match); -#endif static struct platform_driver jz4740_driver = { .probe = jz4740_probe, .remove = jz4740_remove, .driver = { .name = "musb-jz4740", - .of_match_table = of_match_ptr(jz4740_musb_of_match), + .of_match_table = jz4740_musb_of_match, }, }; diff --git a/drivers/usb/musb/mediatek.c b/drivers/usb/musb/mediatek.c index 6b88c2f5d970..6196b0e8d77d 100644 --- a/drivers/usb/musb/mediatek.c +++ b/drivers/usb/musb/mediatek.c @@ -115,9 +115,8 @@ static void mtk_musb_clks_disable(struct mtk_glue *glue) clk_disable_unprepare(glue->main); } -static int musb_usb_role_sx_set(struct device *dev, enum usb_role role) +static int mtk_otg_switch_set(struct mtk_glue *glue, enum usb_role role) { - struct mtk_glue *glue = dev_get_drvdata(dev); struct musb *musb = glue->musb; u8 devctl = readb(musb->mregs + MUSB_DEVCTL); enum usb_role new_role; @@ -168,9 +167,14 @@ static int musb_usb_role_sx_set(struct device *dev, enum usb_role role) return 0; } -static enum usb_role musb_usb_role_sx_get(struct device *dev) +static int musb_usb_role_sx_set(struct usb_role_switch *sw, enum usb_role role) { - struct mtk_glue *glue = dev_get_drvdata(dev); + return mtk_otg_switch_set(usb_role_switch_get_drvdata(sw), role); +} + +static enum usb_role musb_usb_role_sx_get(struct usb_role_switch *sw) +{ + struct mtk_glue *glue = usb_role_switch_get_drvdata(sw); return glue->role; } @@ -182,6 +186,7 @@ static int mtk_otg_switch_init(struct mtk_glue *glue) role_sx_desc.set = musb_usb_role_sx_set; role_sx_desc.get = musb_usb_role_sx_get; role_sx_desc.fwnode = dev_fwnode(glue->dev); + role_sx_desc.driver_data = glue; glue->role_sw = usb_role_switch_register(glue->dev, &role_sx_desc); return PTR_ERR_OR_ZERO(glue->role_sw); @@ -288,8 +293,7 @@ static int mtk_musb_set_mode(struct musb *musb, u8 mode) return -EINVAL; } - glue->role = new_role; - musb_usb_role_sx_set(dev, glue->role); + mtk_otg_switch_set(glue, new_role); return 0; } @@ -444,7 +448,7 @@ static int mtk_musb_probe(struct platform_device *pdev) struct platform_device_info pinfo; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; - int ret = -ENOMEM; + int ret; glue = devm_kzalloc(dev, sizeof(*glue), GFP_KERNEL); if (!glue) diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index f616fb489542..d590110539ab 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -2945,7 +2945,7 @@ static const struct dev_pm_ops musb_dev_pm_ops = { static struct platform_driver musb_driver = { .driver = { - .name = (char *)musb_driver_name, + .name = musb_driver_name, .bus = &platform_bus_type, .pm = MUSB_DEV_PM_OPS, .dev_groups = musb_groups, diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c index 886c9b602f8c..8736f4251a22 100644 --- a/drivers/usb/musb/musb_host.c +++ b/drivers/usb/musb/musb_host.c @@ -1436,10 +1436,7 @@ done: * We need to map sg if the transfer_buffer is * NULL. */ - if (!urb->transfer_buffer) - qh->use_sg = true; - - if (qh->use_sg) { + if (!urb->transfer_buffer) { /* sg_miter_start is already done in musb_ep_program */ if (!sg_miter_next(&qh->sg_miter)) { dev_err(musb->controller, "error: sg list empty\n"); @@ -1447,9 +1444,8 @@ done: status = -EINVAL; goto done; } - urb->transfer_buffer = qh->sg_miter.addr; length = min_t(u32, length, qh->sg_miter.length); - musb_write_fifo(hw_ep, length, urb->transfer_buffer); + musb_write_fifo(hw_ep, length, qh->sg_miter.addr); qh->sg_miter.consumed = length; sg_miter_stop(&qh->sg_miter); } else { @@ -1458,11 +1454,6 @@ done: qh->segsize = length; - if (qh->use_sg) { - if (offset + length >= urb->transfer_buffer_length) - qh->use_sg = false; - } - musb_ep_select(mbase, epnum); musb_writew(epio, MUSB_TXCSR, MUSB_TXCSR_H_WZC_BITS | MUSB_TXCSR_TXPKTRDY); @@ -1977,8 +1968,10 @@ finish: urb->actual_length += xfer_len; qh->offset += xfer_len; if (done) { - if (qh->use_sg) + if (qh->use_sg) { qh->use_sg = false; + urb->transfer_buffer = NULL; + } if (urb->status == -EINPROGRESS) urb->status = status; @@ -2550,7 +2543,7 @@ static int musb_bus_resume(struct usb_hcd *hcd) struct musb_temp_buffer { void *kmalloc_ptr; void *old_xfer_buffer; - u8 data[0]; + u8 data[]; }; static void musb_free_temp_buffer(struct urb *urb) diff --git a/drivers/usb/musb/tusb6010.c b/drivers/usb/musb/tusb6010.c index 5d449089e3ad..99890d1bbfcb 100644 --- a/drivers/usb/musb/tusb6010.c +++ b/drivers/usb/musb/tusb6010.c @@ -156,7 +156,7 @@ static u8 tusb_readb(void __iomem *addr, u32 offset) return val; } -static void tusb_writeb(void __iomem *addr, unsigned offset, u8 data) +static void tusb_writeb(void __iomem *addr, u32 offset, u8 data) { u16 tmp; diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index ff24fca0a2d9..4b3fa78995cf 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -184,4 +184,12 @@ config USB_ULPI_VIEWPORT Provides read/write operations to the ULPI phy register set for controllers with a viewport register (e.g. Chipidea/ARC controllers). +config JZ4770_PHY + tristate "Ingenic JZ4770 Transceiver Driver" + depends on MIPS || COMPILE_TEST + select USB_PHY + help + This driver provides PHY support for the USB controller found + on the JZ4770 SoC from Ingenic. + endmenu diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile index df1d99010079..b352bdbe8712 100644 --- a/drivers/usb/phy/Makefile +++ b/drivers/usb/phy/Makefile @@ -24,3 +24,4 @@ obj-$(CONFIG_USB_MXS_PHY) += phy-mxs-usb.o obj-$(CONFIG_USB_ULPI) += phy-ulpi.o obj-$(CONFIG_USB_ULPI_VIEWPORT) += phy-ulpi-viewport.o obj-$(CONFIG_KEYSTONE_USB_PHY) += phy-keystone.o +obj-$(CONFIG_JZ4770_PHY) += phy-jz4770.o diff --git a/drivers/usb/phy/phy-jz4770.c b/drivers/usb/phy/phy-jz4770.c new file mode 100644 index 000000000000..3ea1f5b9bcf8 --- /dev/null +++ b/drivers/usb/phy/phy-jz4770.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Ingenic JZ4770 USB PHY driver + * Copyright (c) Paul Cercueil <paul@crapouillou.net> + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/usb/otg.h> +#include <linux/usb/phy.h> + +#define REG_USBPCR_OFFSET 0x00 +#define REG_USBRDT_OFFSET 0x04 +#define REG_USBVBFIL_OFFSET 0x08 +#define REG_USBPCR1_OFFSET 0x0c + +/* USBPCR */ +#define USBPCR_USB_MODE BIT(31) +#define USBPCR_AVLD_REG BIT(30) +#define USBPCR_INCRM BIT(27) +#define USBPCR_CLK12_EN BIT(26) +#define USBPCR_COMMONONN BIT(25) +#define USBPCR_VBUSVLDEXT BIT(24) +#define USBPCR_VBUSVLDEXTSEL BIT(23) +#define USBPCR_POR BIT(22) +#define USBPCR_SIDDQ BIT(21) +#define USBPCR_OTG_DISABLE BIT(20) +#define USBPCR_TXPREEMPHTUNE BIT(6) + +#define USBPCR_IDPULLUP_LSB 28 +#define USBPCR_IDPULLUP_MASK GENMASK(29, USBPCR_IDPULLUP_LSB) +#define USBPCR_IDPULLUP_ALWAYS (3 << USBPCR_IDPULLUP_LSB) +#define USBPCR_IDPULLUP_SUSPEND (1 << USBPCR_IDPULLUP_LSB) +#define USBPCR_IDPULLUP_OTG (0 << USBPCR_IDPULLUP_LSB) + +#define USBPCR_COMPDISTUNE_LSB 17 +#define USBPCR_COMPDISTUNE_MASK GENMASK(19, USBPCR_COMPDISTUNE_LSB) +#define USBPCR_COMPDISTUNE_DFT 4 + +#define USBPCR_OTGTUNE_LSB 14 +#define USBPCR_OTGTUNE_MASK GENMASK(16, USBPCR_OTGTUNE_LSB) +#define USBPCR_OTGTUNE_DFT 4 + +#define USBPCR_SQRXTUNE_LSB 11 +#define USBPCR_SQRXTUNE_MASK GENMASK(13, USBPCR_SQRXTUNE_LSB) +#define USBPCR_SQRXTUNE_DFT 3 + +#define USBPCR_TXFSLSTUNE_LSB 7 +#define USBPCR_TXFSLSTUNE_MASK GENMASK(10, USBPCR_TXFSLSTUNE_LSB) +#define USBPCR_TXFSLSTUNE_DFT 3 + +#define USBPCR_TXRISETUNE_LSB 4 +#define USBPCR_TXRISETUNE_MASK GENMASK(5, USBPCR_TXRISETUNE_LSB) +#define USBPCR_TXRISETUNE_DFT 3 + +#define USBPCR_TXVREFTUNE_LSB 0 +#define USBPCR_TXVREFTUNE_MASK GENMASK(3, USBPCR_TXVREFTUNE_LSB) +#define USBPCR_TXVREFTUNE_DFT 5 + +/* USBRDT */ +#define USBRDT_VBFIL_LD_EN BIT(25) +#define USBRDT_IDDIG_EN BIT(24) +#define USBRDT_IDDIG_REG BIT(23) + +#define USBRDT_USBRDT_LSB 0 +#define USBRDT_USBRDT_MASK GENMASK(22, USBRDT_USBRDT_LSB) + +/* USBPCR1 */ +#define USBPCR1_UHC_POWON BIT(5) + +struct jz4770_phy { + struct usb_phy phy; + struct usb_otg otg; + struct device *dev; + void __iomem *base; + struct clk *clk; + struct regulator *vcc_supply; +}; + +static inline struct jz4770_phy *otg_to_jz4770_phy(struct usb_otg *otg) +{ + return container_of(otg, struct jz4770_phy, otg); +} + +static inline struct jz4770_phy *phy_to_jz4770_phy(struct usb_phy *phy) +{ + return container_of(phy, struct jz4770_phy, phy); +} + +static int jz4770_phy_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + struct jz4770_phy *priv = otg_to_jz4770_phy(otg); + u32 reg; + + reg = readl(priv->base + REG_USBPCR_OFFSET); + reg &= ~USBPCR_USB_MODE; + reg |= USBPCR_VBUSVLDEXT | USBPCR_VBUSVLDEXTSEL | USBPCR_OTG_DISABLE; + writel(reg, priv->base + REG_USBPCR_OFFSET); + + return 0; +} + +static int jz4770_phy_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + struct jz4770_phy *priv = otg_to_jz4770_phy(otg); + u32 reg; + + reg = readl(priv->base + REG_USBPCR_OFFSET); + reg &= ~(USBPCR_VBUSVLDEXT | USBPCR_VBUSVLDEXTSEL | USBPCR_OTG_DISABLE); + reg |= USBPCR_USB_MODE; + writel(reg, priv->base + REG_USBPCR_OFFSET); + + return 0; +} + +static int jz4770_phy_init(struct usb_phy *phy) +{ + struct jz4770_phy *priv = phy_to_jz4770_phy(phy); + int err; + u32 reg; + + err = regulator_enable(priv->vcc_supply); + if (err) { + dev_err(priv->dev, "Unable to enable VCC: %d", err); + return err; + } + + err = clk_prepare_enable(priv->clk); + if (err) { + dev_err(priv->dev, "Unable to start clock: %d", err); + return err; + } + + reg = USBPCR_AVLD_REG | USBPCR_COMMONONN | USBPCR_IDPULLUP_ALWAYS | + (USBPCR_COMPDISTUNE_DFT << USBPCR_COMPDISTUNE_LSB) | + (USBPCR_OTGTUNE_DFT << USBPCR_OTGTUNE_LSB) | + (USBPCR_SQRXTUNE_DFT << USBPCR_SQRXTUNE_LSB) | + (USBPCR_TXFSLSTUNE_DFT << USBPCR_TXFSLSTUNE_LSB) | + (USBPCR_TXRISETUNE_DFT << USBPCR_TXRISETUNE_LSB) | + (USBPCR_TXVREFTUNE_DFT << USBPCR_TXVREFTUNE_LSB) | + USBPCR_POR; + writel(reg, priv->base + REG_USBPCR_OFFSET); + + /* Wait for PHY to reset */ + usleep_range(30, 300); + writel(reg & ~USBPCR_POR, priv->base + REG_USBPCR_OFFSET); + usleep_range(300, 1000); + + return 0; +} + +static void jz4770_phy_shutdown(struct usb_phy *phy) +{ + struct jz4770_phy *priv = phy_to_jz4770_phy(phy); + + clk_disable_unprepare(priv->clk); + regulator_disable(priv->vcc_supply); +} + +static void jz4770_phy_remove(void *phy) +{ + usb_remove_phy(phy); +} + +static int jz4770_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct jz4770_phy *priv; + int err; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + priv->dev = dev; + priv->phy.dev = dev; + priv->phy.otg = &priv->otg; + priv->phy.label = "jz4770-phy"; + priv->phy.init = jz4770_phy_init; + priv->phy.shutdown = jz4770_phy_shutdown; + + priv->otg.state = OTG_STATE_UNDEFINED; + priv->otg.usb_phy = &priv->phy; + priv->otg.set_host = jz4770_phy_set_host; + priv->otg.set_peripheral = jz4770_phy_set_peripheral; + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) { + dev_err(dev, "Failed to map registers"); + return PTR_ERR(priv->base); + } + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + err = PTR_ERR(priv->clk); + if (err != -EPROBE_DEFER) + dev_err(dev, "Failed to get clock"); + return err; + } + + priv->vcc_supply = devm_regulator_get(dev, "vcc"); + if (IS_ERR(priv->vcc_supply)) { + err = PTR_ERR(priv->vcc_supply); + if (err != -EPROBE_DEFER) + dev_err(dev, "failed to get regulator"); + return err; + } + + err = usb_add_phy(&priv->phy, USB_PHY_TYPE_USB2); + if (err) { + if (err != -EPROBE_DEFER) + dev_err(dev, "Unable to register PHY"); + return err; + } + + return devm_add_action_or_reset(dev, jz4770_phy_remove, &priv->phy); +} + +#ifdef CONFIG_OF +static const struct of_device_id jz4770_phy_of_matches[] = { + { .compatible = "ingenic,jz4770-phy" }, + { } +}; +MODULE_DEVICE_TABLE(of, jz4770_phy_of_matches); +#endif + +static struct platform_driver jz4770_phy_driver = { + .probe = jz4770_phy_probe, + .driver = { + .name = "jz4770-phy", + .of_match_table = of_match_ptr(jz4770_phy_of_matches), + }, +}; +module_platform_driver(jz4770_phy_driver); + +MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); +MODULE_DESCRIPTION("Ingenic JZ4770 USB PHY driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/phy-tegra-usb.c b/drivers/usb/phy/phy-tegra-usb.c index 6153cc35aba0..cffe2aced488 100644 --- a/drivers/usb/phy/phy-tegra-usb.c +++ b/drivers/usb/phy/phy-tegra-usb.c @@ -12,12 +12,11 @@ #include <linux/delay.h> #include <linux/err.h> #include <linux/export.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/iopoll.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_device.h> -#include <linux/of_gpio.h> #include <linux/platform_device.h> #include <linux/resource.h> #include <linux/slab.h> diff --git a/drivers/usb/roles/class.c b/drivers/usb/roles/class.c index 63a00ff26655..5b17709821df 100644 --- a/drivers/usb/roles/class.c +++ b/drivers/usb/roles/class.c @@ -48,7 +48,7 @@ int usb_role_switch_set_role(struct usb_role_switch *sw, enum usb_role role) mutex_lock(&sw->lock); - ret = sw->set(sw->dev.parent, role); + ret = sw->set(sw, role); if (!ret) sw->role = role; @@ -75,7 +75,7 @@ enum usb_role usb_role_switch_get_role(struct usb_role_switch *sw) mutex_lock(&sw->lock); if (sw->get) - role = sw->get(sw->dev.parent); + role = sw->get(sw); else role = sw->role; @@ -199,7 +199,7 @@ EXPORT_SYMBOL_GPL(usb_role_switch_find_by_fwnode); static umode_t usb_role_switch_is_visible(struct kobject *kobj, struct attribute *attr, int n) { - struct device *dev = container_of(kobj, typeof(*dev), kobj); + struct device *dev = kobj_to_dev(kobj); struct usb_role_switch *sw = to_role_switch(dev); if (sw->allow_userspace_control) @@ -329,7 +329,9 @@ usb_role_switch_register(struct device *parent, sw->dev.fwnode = desc->fwnode; sw->dev.class = role_class; sw->dev.type = &usb_role_dev_type; - dev_set_name(&sw->dev, "%s-role-switch", dev_name(parent)); + dev_set_drvdata(&sw->dev, desc->driver_data); + dev_set_name(&sw->dev, "%s-role-switch", + desc->name ? desc->name : dev_name(parent)); ret = device_register(&sw->dev); if (ret) { @@ -356,6 +358,27 @@ void usb_role_switch_unregister(struct usb_role_switch *sw) } EXPORT_SYMBOL_GPL(usb_role_switch_unregister); +/** + * usb_role_switch_set_drvdata - Assign private data pointer to a switch + * @sw: USB Role Switch + * @data: Private data pointer + */ +void usb_role_switch_set_drvdata(struct usb_role_switch *sw, void *data) +{ + dev_set_drvdata(&sw->dev, data); +} +EXPORT_SYMBOL_GPL(usb_role_switch_set_drvdata); + +/** + * usb_role_switch_get_drvdata - Get the private data pointer of a switch + * @sw: USB Role Switch + */ +void *usb_role_switch_get_drvdata(struct usb_role_switch *sw) +{ + return dev_get_drvdata(&sw->dev); +} +EXPORT_SYMBOL_GPL(usb_role_switch_get_drvdata); + static int __init usb_roles_init(void) { role_class = class_create(THIS_MODULE, "usb_role"); diff --git a/drivers/usb/roles/intel-xhci-usb-role-switch.c b/drivers/usb/roles/intel-xhci-usb-role-switch.c index 80d6559bbcb2..5c96e929acea 100644 --- a/drivers/usb/roles/intel-xhci-usb-role-switch.c +++ b/drivers/usb/roles/intel-xhci-usb-role-switch.c @@ -42,6 +42,7 @@ #define DRV_NAME "intel_xhci_usb_sw" struct intel_xhci_usb_data { + struct device *dev; struct usb_role_switch *role_sw; void __iomem *base; bool enable_sw_switch; @@ -51,9 +52,10 @@ static const struct software_node intel_xhci_usb_node = { "intel-xhci-usb-sw", }; -static int intel_xhci_usb_set_role(struct device *dev, enum usb_role role) +static int intel_xhci_usb_set_role(struct usb_role_switch *sw, + enum usb_role role) { - struct intel_xhci_usb_data *data = dev_get_drvdata(dev); + struct intel_xhci_usb_data *data = usb_role_switch_get_drvdata(sw); unsigned long timeout; acpi_status status; u32 glk, val; @@ -66,11 +68,11 @@ static int intel_xhci_usb_set_role(struct device *dev, enum usb_role role) */ status = acpi_acquire_global_lock(ACPI_WAIT_FOREVER, &glk); if (ACPI_FAILURE(status) && status != AE_NOT_CONFIGURED) { - dev_err(dev, "Error could not acquire lock\n"); + dev_err(data->dev, "Error could not acquire lock\n"); return -EIO; } - pm_runtime_get_sync(dev); + pm_runtime_get_sync(data->dev); /* * Set idpin value as requested. @@ -112,7 +114,7 @@ static int intel_xhci_usb_set_role(struct device *dev, enum usb_role role) do { val = readl(data->base + DUAL_ROLE_CFG1); if (!!(val & HOST_MODE) == (role == USB_ROLE_HOST)) { - pm_runtime_put(dev); + pm_runtime_put(data->dev); return 0; } @@ -120,21 +122,21 @@ static int intel_xhci_usb_set_role(struct device *dev, enum usb_role role) usleep_range(5000, 10000); } while (time_before(jiffies, timeout)); - pm_runtime_put(dev); + pm_runtime_put(data->dev); - dev_warn(dev, "Timeout waiting for role-switch\n"); + dev_warn(data->dev, "Timeout waiting for role-switch\n"); return -ETIMEDOUT; } -static enum usb_role intel_xhci_usb_get_role(struct device *dev) +static enum usb_role intel_xhci_usb_get_role(struct usb_role_switch *sw) { - struct intel_xhci_usb_data *data = dev_get_drvdata(dev); + struct intel_xhci_usb_data *data = usb_role_switch_get_drvdata(sw); enum usb_role role; u32 val; - pm_runtime_get_sync(dev); + pm_runtime_get_sync(data->dev); val = readl(data->base + DUAL_ROLE_CFG0); - pm_runtime_put(dev); + pm_runtime_put(data->dev); if (!(val & SW_IDPIN)) role = USB_ROLE_HOST; @@ -175,7 +177,9 @@ static int intel_xhci_usb_probe(struct platform_device *pdev) sw_desc.get = intel_xhci_usb_get_role, sw_desc.allow_userspace_control = true, sw_desc.fwnode = software_node_fwnode(&intel_xhci_usb_node); + sw_desc.driver_data = data; + data->dev = dev; data->enable_sw_switch = !device_property_read_bool(dev, "sw_switch_disable"); diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 578ebdd86520..91055a191995 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -1472,7 +1472,7 @@ static int digi_read_oob_callback(struct urb *urb) struct usb_serial_port *port = urb->context; struct usb_serial *serial = port->serial; struct tty_struct *tty; - struct digi_port *priv = usb_get_serial_port_data(port); + struct digi_port *priv; unsigned char *buf = urb->transfer_buffer; int opcode, line, status, val; unsigned long flags; diff --git a/drivers/usb/serial/f81232.c b/drivers/usb/serial/f81232.c index 43fa1f0716b7..dcda7fb164b4 100644 --- a/drivers/usb/serial/f81232.c +++ b/drivers/usb/serial/f81232.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 /* * Fintek F81232 USB to serial adaptor driver + * Fintek F81532A/534A/535/536 USB to 2/4/8/12 serial adaptor driver * * Copyright (C) 2012 Greg Kroah-Hartman (gregkh@linuxfoundation.org) * Copyright (C) 2012 Linux Foundation @@ -21,11 +22,45 @@ #include <linux/usb/serial.h> #include <linux/serial_reg.h> -static const struct usb_device_id id_table[] = { - { USB_DEVICE(0x1934, 0x0706) }, +#define F81232_ID \ + { USB_DEVICE(0x1934, 0x0706) } /* 1 port UART device */ + +#define F81534A_SERIES_ID \ + { USB_DEVICE(0x2c42, 0x1602) }, /* In-Box 2 port UART device */ \ + { USB_DEVICE(0x2c42, 0x1604) }, /* In-Box 4 port UART device */ \ + { USB_DEVICE(0x2c42, 0x1605) }, /* In-Box 8 port UART device */ \ + { USB_DEVICE(0x2c42, 0x1606) }, /* In-Box 12 port UART device */ \ + { USB_DEVICE(0x2c42, 0x1608) }, /* Non-Flash type */ \ + { USB_DEVICE(0x2c42, 0x1632) }, /* 2 port UART device */ \ + { USB_DEVICE(0x2c42, 0x1634) }, /* 4 port UART device */ \ + { USB_DEVICE(0x2c42, 0x1635) }, /* 8 port UART device */ \ + { USB_DEVICE(0x2c42, 0x1636) } /* 12 port UART device */ + +#define F81534A_CTRL_ID \ + { USB_DEVICE(0x2c42, 0x16f8) } /* Global control device */ + +static const struct usb_device_id f81232_id_table[] = { + F81232_ID, { } /* Terminating entry */ }; -MODULE_DEVICE_TABLE(usb, id_table); + +static const struct usb_device_id f81534a_id_table[] = { + F81534A_SERIES_ID, + { } /* Terminating entry */ +}; + +static const struct usb_device_id f81534a_ctrl_id_table[] = { + F81534A_CTRL_ID, + { } /* Terminating entry */ +}; + +static const struct usb_device_id combined_id_table[] = { + F81232_ID, + F81534A_SERIES_ID, + F81534A_CTRL_ID, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, combined_id_table); /* Maximum baudrate for F81232 */ #define F81232_MAX_BAUDRATE 1500000 @@ -35,6 +70,7 @@ MODULE_DEVICE_TABLE(usb, id_table); #define F81232_REGISTER_REQUEST 0xa0 #define F81232_GET_REGISTER 0xc0 #define F81232_SET_REGISTER 0x40 +#define F81534A_ACCESS_REG_RETRY 2 #define SERIAL_BASE_ADDRESS 0x0120 #define RECEIVE_BUFFER_REGISTER (0x00 + SERIAL_BASE_ADDRESS) @@ -61,6 +97,22 @@ MODULE_DEVICE_TABLE(usb, id_table); #define F81232_CLK_14_77_MHZ (BIT(1) | BIT(0)) #define F81232_CLK_MASK GENMASK(1, 0) +#define F81534A_MODE_REG 0x107 +#define F81534A_TRIGGER_MASK GENMASK(3, 2) +#define F81534A_TRIGGER_MULTIPLE_4X BIT(3) +#define F81534A_FIFO_128BYTE (BIT(1) | BIT(0)) + +/* Serial port self GPIO control, 2bytes [control&output data][input data] */ +#define F81534A_GPIO_REG 0x10e +#define F81534A_GPIO_MODE2_DIR BIT(6) /* 1: input, 0: output */ +#define F81534A_GPIO_MODE1_DIR BIT(5) +#define F81534A_GPIO_MODE0_DIR BIT(4) +#define F81534A_GPIO_MODE2_OUTPUT BIT(2) +#define F81534A_GPIO_MODE1_OUTPUT BIT(1) +#define F81534A_GPIO_MODE0_OUTPUT BIT(0) + +#define F81534A_CTRL_CMD_ENABLE_PORT 0x116 + struct f81232_private { struct mutex lock; u8 modem_control; @@ -322,10 +374,38 @@ exit: __func__, retval); } +static char f81232_handle_lsr(struct usb_serial_port *port, u8 lsr) +{ + struct f81232_private *priv = usb_get_serial_port_data(port); + char tty_flag = TTY_NORMAL; + + if (!(lsr & UART_LSR_BRK_ERROR_BITS)) + return tty_flag; + + if (lsr & UART_LSR_BI) { + tty_flag = TTY_BREAK; + port->icount.brk++; + usb_serial_handle_break(port); + } else if (lsr & UART_LSR_PE) { + tty_flag = TTY_PARITY; + port->icount.parity++; + } else if (lsr & UART_LSR_FE) { + tty_flag = TTY_FRAME; + port->icount.frame++; + } + + if (lsr & UART_LSR_OE) { + port->icount.overrun++; + schedule_work(&priv->lsr_work); + tty_insert_flip_char(&port->port, 0, TTY_OVERRUN); + } + + return tty_flag; +} + static void f81232_process_read_urb(struct urb *urb) { struct usb_serial_port *port = urb->context; - struct f81232_private *priv = usb_get_serial_port_data(port); unsigned char *data = urb->transfer_buffer; char tty_flag; unsigned int i; @@ -341,29 +421,8 @@ static void f81232_process_read_urb(struct urb *urb) /* bulk-in data: [LSR(1Byte)+DATA(1Byte)][LSR(1Byte)+DATA(1Byte)]... */ for (i = 0; i < urb->actual_length; i += 2) { - tty_flag = TTY_NORMAL; lsr = data[i]; - - if (lsr & UART_LSR_BRK_ERROR_BITS) { - if (lsr & UART_LSR_BI) { - tty_flag = TTY_BREAK; - port->icount.brk++; - usb_serial_handle_break(port); - } else if (lsr & UART_LSR_PE) { - tty_flag = TTY_PARITY; - port->icount.parity++; - } else if (lsr & UART_LSR_FE) { - tty_flag = TTY_FRAME; - port->icount.frame++; - } - - if (lsr & UART_LSR_OE) { - port->icount.overrun++; - schedule_work(&priv->lsr_work); - tty_insert_flip_char(&port->port, 0, - TTY_OVERRUN); - } - } + tty_flag = f81232_handle_lsr(port, lsr); if (port->port.console && port->sysrq) { if (usb_serial_handle_sysrq_char(port, data[i + 1])) @@ -376,6 +435,47 @@ static void f81232_process_read_urb(struct urb *urb) tty_flip_buffer_push(&port->port); } +static void f81534a_process_read_urb(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + unsigned char *data = urb->transfer_buffer; + char tty_flag; + unsigned int i; + u8 lsr; + u8 len; + + if (urb->actual_length < 3) { + dev_err(&port->dev, "short message received: %d\n", + urb->actual_length); + return; + } + + len = data[0]; + if (len != urb->actual_length) { + dev_err(&port->dev, "malformed message received: %d (%d)\n", + urb->actual_length, len); + return; + } + + /* bulk-in data: [LEN][Data.....][LSR] */ + lsr = data[len - 1]; + tty_flag = f81232_handle_lsr(port, lsr); + + if (port->port.console && port->sysrq) { + for (i = 1; i < len - 1; ++i) { + if (!usb_serial_handle_sysrq_char(port, data[i])) { + tty_insert_flip_char(&port->port, data[i], + tty_flag); + } + } + } else { + tty_insert_flip_string_fixed_flag(&port->port, &data[1], + tty_flag, len - 2); + } + + tty_flip_buffer_push(&port->port); +} + static void f81232_break_ctl(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; @@ -659,6 +759,24 @@ static int f81232_open(struct tty_struct *tty, struct usb_serial_port *port) return 0; } +static int f81534a_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + int status; + u8 mask; + u8 val; + + val = F81534A_TRIGGER_MULTIPLE_4X | F81534A_FIFO_128BYTE; + mask = F81534A_TRIGGER_MASK | F81534A_FIFO_128BYTE; + + status = f81232_set_mask_register(port, F81534A_MODE_REG, mask, val); + if (status) { + dev_err(&port->dev, "failed to set MODE_REG: %d\n", status); + return status; + } + + return f81232_open(tty, port); +} + static void f81232_close(struct usb_serial_port *port) { struct f81232_private *port_priv = usb_get_serial_port_data(port); @@ -678,6 +796,20 @@ static void f81232_dtr_rts(struct usb_serial_port *port, int on) f81232_set_mctrl(port, 0, TIOCM_DTR | TIOCM_RTS); } +static bool f81232_tx_empty(struct usb_serial_port *port) +{ + int status; + u8 tmp; + + status = f81232_get_register(port, LINE_STATUS_REGISTER, &tmp); + if (!status) { + if ((tmp & UART_LSR_TEMT) != UART_LSR_TEMT) + return false; + } + + return true; +} + static int f81232_carrier_raised(struct usb_serial_port *port) { u8 msr; @@ -728,11 +860,98 @@ static void f81232_lsr_worker(struct work_struct *work) dev_warn(&port->dev, "read LSR failed: %d\n", status); } +static int f81534a_ctrl_set_register(struct usb_interface *intf, u16 reg, + u16 size, void *val) +{ + struct usb_device *dev = interface_to_usbdev(intf); + int retry = F81534A_ACCESS_REG_RETRY; + int status; + u8 *tmp; + + tmp = kmemdup(val, size, GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + while (retry--) { + status = usb_control_msg(dev, + usb_sndctrlpipe(dev, 0), + F81232_REGISTER_REQUEST, + F81232_SET_REGISTER, + reg, + 0, + tmp, + size, + USB_CTRL_SET_TIMEOUT); + if (status < 0) { + status = usb_translate_errors(status); + if (status == -EIO) + continue; + } else if (status != size) { + /* Retry on short transfers */ + status = -EIO; + continue; + } else { + status = 0; + } + + break; + } + + if (status) { + dev_err(&intf->dev, "failed to set register 0x%x: %d\n", + reg, status); + } + + kfree(tmp); + return status; +} + +static int f81534a_ctrl_enable_all_ports(struct usb_interface *intf, bool en) +{ + unsigned char enable[2] = {0}; + int status; + + /* + * Enable all available serial ports, define as following: + * bit 15 : Reset behavior (when HUB got soft reset) + * 0: maintain all serial port enabled state. + * 1: disable all serial port. + * bit 0~11 : Serial port enable bit. + */ + if (en) { + enable[0] = 0xff; + enable[1] = 0x8f; + } + + status = f81534a_ctrl_set_register(intf, F81534A_CTRL_CMD_ENABLE_PORT, + sizeof(enable), enable); + if (status) + dev_err(&intf->dev, "failed to enable ports: %d\n", status); + + return status; +} + +static int f81534a_ctrl_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + return f81534a_ctrl_enable_all_ports(intf, true); +} + +static void f81534a_ctrl_disconnect(struct usb_interface *intf) +{ + f81534a_ctrl_enable_all_ports(intf, false); +} + +static int f81534a_ctrl_resume(struct usb_interface *intf) +{ + return f81534a_ctrl_enable_all_ports(intf, true); +} + static int f81232_port_probe(struct usb_serial_port *port) { struct f81232_private *priv; - priv = kzalloc(sizeof(*priv), GFP_KERNEL); + priv = devm_kzalloc(&port->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; @@ -748,14 +967,17 @@ static int f81232_port_probe(struct usb_serial_port *port) return 0; } -static int f81232_port_remove(struct usb_serial_port *port) +static int f81534a_port_probe(struct usb_serial_port *port) { - struct f81232_private *priv; + int status; - priv = usb_get_serial_port_data(port); - kfree(priv); + /* tri-state with pull-high, default RS232 Mode */ + status = f81232_set_register(port, F81534A_GPIO_REG, + F81534A_GPIO_MODE2_DIR); + if (status) + return status; - return 0; + return f81232_port_probe(port); } static int f81232_suspend(struct usb_serial *serial, pm_message_t message) @@ -799,7 +1021,7 @@ static struct usb_serial_driver f81232_device = { .owner = THIS_MODULE, .name = "f81232", }, - .id_table = id_table, + .id_table = f81232_id_table, .num_ports = 1, .bulk_in_size = 256, .bulk_out_size = 256, @@ -813,22 +1035,82 @@ static struct usb_serial_driver f81232_device = { .tiocmget = f81232_tiocmget, .tiocmset = f81232_tiocmset, .tiocmiwait = usb_serial_generic_tiocmiwait, + .tx_empty = f81232_tx_empty, .process_read_urb = f81232_process_read_urb, .read_int_callback = f81232_read_int_callback, .port_probe = f81232_port_probe, - .port_remove = f81232_port_remove, + .suspend = f81232_suspend, + .resume = f81232_resume, +}; + +static struct usb_serial_driver f81534a_device = { + .driver = { + .owner = THIS_MODULE, + .name = "f81534a", + }, + .id_table = f81534a_id_table, + .num_ports = 1, + .open = f81534a_open, + .close = f81232_close, + .dtr_rts = f81232_dtr_rts, + .carrier_raised = f81232_carrier_raised, + .get_serial = f81232_get_serial_info, + .break_ctl = f81232_break_ctl, + .set_termios = f81232_set_termios, + .tiocmget = f81232_tiocmget, + .tiocmset = f81232_tiocmset, + .tiocmiwait = usb_serial_generic_tiocmiwait, + .tx_empty = f81232_tx_empty, + .process_read_urb = f81534a_process_read_urb, + .read_int_callback = f81232_read_int_callback, + .port_probe = f81534a_port_probe, .suspend = f81232_suspend, .resume = f81232_resume, }; static struct usb_serial_driver * const serial_drivers[] = { &f81232_device, + &f81534a_device, NULL, }; -module_usb_serial_driver(serial_drivers, id_table); +static struct usb_driver f81534a_ctrl_driver = { + .name = "f81534a_ctrl", + .id_table = f81534a_ctrl_id_table, + .probe = f81534a_ctrl_probe, + .disconnect = f81534a_ctrl_disconnect, + .resume = f81534a_ctrl_resume, +}; + +static int __init f81232_init(void) +{ + int status; + + status = usb_register_driver(&f81534a_ctrl_driver, THIS_MODULE, + KBUILD_MODNAME); + if (status) + return status; + + status = usb_serial_register_drivers(serial_drivers, KBUILD_MODNAME, + combined_id_table); + if (status) { + usb_deregister(&f81534a_ctrl_driver); + return status; + } + + return 0; +} + +static void __exit f81232_exit(void) +{ + usb_serial_deregister_drivers(serial_drivers); + usb_deregister(&f81534a_ctrl_driver); +} + +module_init(f81232_init); +module_exit(f81232_exit); -MODULE_DESCRIPTION("Fintek F81232 USB to serial adaptor driver"); +MODULE_DESCRIPTION("Fintek F81232/532A/534A/535/536 USB to serial driver"); MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@linuxfoundation.org>"); MODULE_AUTHOR("Peter Hong <peter_hong@fintek.com.tw>"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c index 1be8bea372a2..5cdf180cda23 100644 --- a/drivers/usb/serial/generic.c +++ b/drivers/usb/serial/generic.c @@ -417,7 +417,7 @@ void usb_serial_generic_read_bulk_callback(struct urb *urb) /* * Make sure URB is marked as free before checking the throttled flag * to avoid racing with unthrottle() on another CPU. Matches the - * smp_mb() in unthrottle(). + * smp_mb__after_atomic() in unthrottle(). */ smp_mb__after_atomic(); @@ -489,7 +489,7 @@ void usb_serial_generic_unthrottle(struct tty_struct *tty) * Matches the smp_mb__after_atomic() in * usb_serial_generic_read_bulk_callback(). */ - smp_mb(); + smp_mb__after_atomic(); usb_serial_generic_submit_read_urbs(port, GFP_KERNEL); } @@ -609,12 +609,10 @@ EXPORT_SYMBOL_GPL(usb_serial_handle_break); * @tty: tty for the port * @status: new carrier detect status, nonzero if active */ -void usb_serial_handle_dcd_change(struct usb_serial_port *usb_port, +void usb_serial_handle_dcd_change(struct usb_serial_port *port, struct tty_struct *tty, unsigned int status) { - struct tty_port *port = &usb_port->port; - - dev_dbg(&usb_port->dev, "%s - status %d\n", __func__, status); + dev_dbg(&port->dev, "%s - status %d\n", __func__, status); if (tty) { struct tty_ldisc *ld = tty_ldisc_ref(tty); @@ -627,7 +625,7 @@ void usb_serial_handle_dcd_change(struct usb_serial_port *usb_port, } if (status) - wake_up_interruptible(&port->open_wait); + wake_up_interruptible(&port->port.open_wait); else if (tty && !C_CLOCAL(tty)) tty_hangup(tty); } diff --git a/drivers/usb/serial/io_edgeport.c b/drivers/usb/serial/io_edgeport.c index 5737add6a2a4..4cca0b836f43 100644 --- a/drivers/usb/serial/io_edgeport.c +++ b/drivers/usb/serial/io_edgeport.c @@ -710,7 +710,7 @@ static void edge_interrupt_callback(struct urb *urb) /* grab the txcredits for the ports if available */ position = 2; portNumber = 0; - while ((position < length) && + while ((position < length - 1) && (portNumber < edge_serial->serial->num_ports)) { txCredits = data[position] | (data[position+1] << 8); if (txCredits) { diff --git a/drivers/usb/serial/io_usbvend.h b/drivers/usb/serial/io_usbvend.h index c38e87ac5ea9..0d1a5bb4636e 100644 --- a/drivers/usb/serial/io_usbvend.h +++ b/drivers/usb/serial/io_usbvend.h @@ -593,7 +593,7 @@ struct ti_i2c_desc { __u8 Type; // Type of descriptor __le16 Size; // Size of data only not including header __u8 CheckSum; // Checksum (8 bit sum of data only) - __u8 Data[0]; // Data starts here + __u8 Data[]; // Data starts here } __attribute__((packed)); // for 5152 devices only (type 2 record) @@ -601,7 +601,7 @@ struct ti_i2c_desc { struct ti_i2c_firmware_rec { __u8 Ver_Major; // Firmware Major version number __u8 Ver_Minor; // Firmware Minor version number - __u8 Data[0]; // Download starts here + __u8 Data[]; // Download starts here } __attribute__((packed)); diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 0b5dcf973d94..8bfffca3e4ae 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -1992,8 +1992,14 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(0x07d1, 0x3e01, 0xff, 0xff, 0xff) }, /* D-Link DWM-152/C1 */ { USB_DEVICE_AND_INTERFACE_INFO(0x07d1, 0x3e02, 0xff, 0xff, 0xff) }, /* D-Link DWM-156/C1 */ { USB_DEVICE_AND_INTERFACE_INFO(0x07d1, 0x7e11, 0xff, 0xff, 0xff) }, /* D-Link DWM-156/A3 */ + { USB_DEVICE_INTERFACE_CLASS(0x1435, 0xd191, 0xff), /* Wistron Neweb D19Q1 */ + .driver_info = RSVD(1) | RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(0x1690, 0x7588, 0xff), /* ASKEY WWHC050 */ + .driver_info = RSVD(1) | RSVD(4) }, { USB_DEVICE_INTERFACE_CLASS(0x2020, 0x2031, 0xff), /* Olicard 600 */ .driver_info = RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(0x2020, 0x2033, 0xff), /* BroadMobi BM806U */ + .driver_info = RSVD(4) }, { USB_DEVICE_INTERFACE_CLASS(0x2020, 0x2060, 0xff), /* BroadMobi BM818 */ .driver_info = RSVD(4) }, { USB_DEVICE_INTERFACE_CLASS(0x2020, 0x4000, 0xff) }, /* OLICARD300 - MT6225 */ diff --git a/drivers/usb/serial/ti_usb_3410_5052.c b/drivers/usb/serial/ti_usb_3410_5052.c index ef23acc9b9ce..73075b9351c5 100644 --- a/drivers/usb/serial/ti_usb_3410_5052.c +++ b/drivers/usb/serial/ti_usb_3410_5052.c @@ -219,7 +219,7 @@ struct ti_write_data_bytes { u8 bDataCounter; __be16 wBaseAddrHi; __be16 wBaseAddrLo; - u8 bData[0]; + u8 bData[]; } __packed; struct ti_read_data_request { @@ -234,7 +234,7 @@ struct ti_read_data_bytes { __u8 bCmdCode; __u8 bModuleId; __u8 bErrorCode; - __u8 bData[0]; + __u8 bData[]; } __packed; /* Interrupt struct */ diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index dc7a65b9ec98..27e3bb58c872 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -288,7 +288,7 @@ static void serial_close(struct tty_struct *tty, struct file *filp) /** * serial_cleanup - free resources post close/hangup - * @port: port to free up + * @tty: tty to clean up * * Do the resource freeing and refcount dropping for the port. * Avoid freeing the console. diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c index 9a79cd9762f3..94a64729dc27 100644 --- a/drivers/usb/storage/usb.c +++ b/drivers/usb/storage/usb.c @@ -121,12 +121,12 @@ MODULE_PARM_DESC(quirks, "supplemental list of device IDs and their quirks"); .initFunction = init_function, \ } -static struct us_unusual_dev us_unusual_dev_list[] = { +static const struct us_unusual_dev us_unusual_dev_list[] = { # include "unusual_devs.h" { } /* Terminating entry */ }; -static struct us_unusual_dev for_dynamic_ids = +static const struct us_unusual_dev for_dynamic_ids = USUAL_DEV(USB_SC_SCSI, USB_PR_BULK); #undef UNUSUAL_DEV @@ -583,7 +583,7 @@ EXPORT_SYMBOL_GPL(usb_stor_adjust_quirks); /* Get the unusual_devs entries and the string descriptors */ static int get_device_info(struct us_data *us, const struct usb_device_id *id, - struct us_unusual_dev *unusual_dev) + const struct us_unusual_dev *unusual_dev) { struct usb_device *dev = us->pusb_dev; struct usb_interface_descriptor *idesc = @@ -933,7 +933,7 @@ static unsigned int usb_stor_sg_tablesize(struct usb_interface *intf) int usb_stor_probe1(struct us_data **pus, struct usb_interface *intf, const struct usb_device_id *id, - struct us_unusual_dev *unusual_dev, + const struct us_unusual_dev *unusual_dev, struct scsi_host_template *sht) { struct Scsi_Host *host; @@ -1092,7 +1092,7 @@ static struct scsi_host_template usb_stor_host_template; static int storage_probe(struct usb_interface *intf, const struct usb_device_id *id) { - struct us_unusual_dev *unusual_dev; + const struct us_unusual_dev *unusual_dev; struct us_data *us; int result; int size; diff --git a/drivers/usb/storage/usb.h b/drivers/usb/storage/usb.h index 85052cd66839..5850d624cac7 100644 --- a/drivers/usb/storage/usb.h +++ b/drivers/usb/storage/usb.h @@ -93,7 +93,8 @@ struct us_data { struct mutex dev_mutex; /* protect pusb_dev */ struct usb_device *pusb_dev; /* this usb_device */ struct usb_interface *pusb_intf; /* this interface */ - struct us_unusual_dev *unusual_dev; /* device-filter entry */ + const struct us_unusual_dev *unusual_dev; + /* device-filter entry */ unsigned long fflags; /* fixed flags from filter */ unsigned long dflags; /* dynamic atomic bitflags */ unsigned int send_bulk_pipe; /* cached pipe values */ @@ -185,7 +186,7 @@ extern int usb_stor_post_reset(struct usb_interface *iface); extern int usb_stor_probe1(struct us_data **pus, struct usb_interface *intf, const struct usb_device_id *id, - struct us_unusual_dev *unusual_dev, + const struct us_unusual_dev *unusual_dev, struct scsi_host_template *sht); extern int usb_stor_probe2(struct us_data *us); extern void usb_stor_disconnect(struct usb_interface *intf); diff --git a/drivers/usb/storage/usual-tables.c b/drivers/usb/storage/usual-tables.c index cfd12e523678..529512827d8f 100644 --- a/drivers/usb/storage/usual-tables.c +++ b/drivers/usb/storage/usual-tables.c @@ -40,7 +40,7 @@ .driver_info = (flags) \ } -struct usb_device_id usb_storage_usb_ids[] = { +const struct usb_device_id usb_storage_usb_ids[] = { # include "unusual_devs.h" { } /* Terminating entry */ }; @@ -68,7 +68,7 @@ struct ignore_entry { .bcdmax = bcdDeviceMax, \ } -static struct ignore_entry ignore_ids[] = { +static const struct ignore_entry ignore_ids[] = { # include "unusual_alauda.h" # include "unusual_cypress.h" # include "unusual_datafab.h" @@ -92,7 +92,7 @@ int usb_usual_ignore_device(struct usb_interface *intf) { struct usb_device *udev; unsigned vid, pid, bcd; - struct ignore_entry *p; + const struct ignore_entry *p; udev = interface_to_usbdev(intf); vid = le16_to_cpu(udev->descriptor.idVendor); diff --git a/drivers/usb/typec/bus.c b/drivers/usb/typec/bus.c index 2e45eb479386..c823122f9cb7 100644 --- a/drivers/usb/typec/bus.c +++ b/drivers/usb/typec/bus.c @@ -30,17 +30,10 @@ static int typec_altmode_set_state(struct typec_altmode *adev, { bool is_port = is_typec_port(adev->dev.parent); struct altmode *port_altmode; - int ret; port_altmode = is_port ? to_altmode(adev) : to_altmode(adev)->partner; - ret = typec_altmode_set_mux(port_altmode, conf, data); - if (ret) - return ret; - - blocking_notifier_call_chain(&port_altmode->nh, conf, NULL); - - return 0; + return typec_altmode_set_mux(port_altmode, conf, data); } /* -------------------------------------------------------------------------- */ @@ -82,9 +75,6 @@ int typec_altmode_notify(struct typec_altmode *adev, if (ret) return ret; - blocking_notifier_call_chain(is_port ? &altmode->nh : &partner->nh, - conf, data); - if (partner->adev.ops && partner->adev.ops->notify) return partner->adev.ops->notify(&partner->adev, conf, data); diff --git a/drivers/usb/typec/bus.h b/drivers/usb/typec/bus.h index 0c9661c96473..8ba8112d2740 100644 --- a/drivers/usb/typec/bus.h +++ b/drivers/usb/typec/bus.h @@ -22,8 +22,6 @@ struct altmode { struct altmode *partner; struct altmode *plug[2]; - - struct blocking_notifier_head nh; }; #define to_altmode(d) container_of(d, struct altmode, adev) diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 7c44e930602f..8d894bdff77d 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -206,69 +206,6 @@ static void typec_altmode_put_partner(struct altmode *altmode) put_device(&adev->dev); } -static void *typec_port_match(struct device_connection *con, int ep, void *data) -{ - struct device *dev; - - /* - * FIXME: Check does the fwnode supports the requested SVID. If it does - * we need to return ERR_PTR(-PROBE_DEFER) when there is no device. - */ - if (con->fwnode) - return class_find_device_by_fwnode(typec_class, con->fwnode); - - dev = class_find_device_by_name(typec_class, con->endpoint[ep]); - - return dev ? dev : ERR_PTR(-EPROBE_DEFER); -} - -struct typec_altmode * -typec_altmode_register_notifier(struct device *dev, u16 svid, u8 mode, - struct notifier_block *nb) -{ - struct typec_device_id id = { svid, mode, }; - struct device *altmode_dev; - struct device *port_dev; - struct altmode *altmode; - int ret; - - /* Find the port linked to the caller */ - port_dev = device_connection_find_match(dev, NULL, NULL, - typec_port_match); - if (IS_ERR_OR_NULL(port_dev)) - return port_dev ? ERR_CAST(port_dev) : ERR_PTR(-ENODEV); - - /* Find the altmode with matching svid */ - altmode_dev = device_find_child(port_dev, &id, altmode_match); - - put_device(port_dev); - - if (!altmode_dev) - return ERR_PTR(-ENODEV); - - altmode = to_altmode(to_typec_altmode(altmode_dev)); - - /* Register notifier */ - ret = blocking_notifier_chain_register(&altmode->nh, nb); - if (ret) { - put_device(altmode_dev); - return ERR_PTR(ret); - } - - return &altmode->adev; -} -EXPORT_SYMBOL_GPL(typec_altmode_register_notifier); - -void typec_altmode_unregister_notifier(struct typec_altmode *adev, - struct notifier_block *nb) -{ - struct altmode *altmode = to_altmode(adev); - - blocking_notifier_chain_unregister(&altmode->nh, nb); - put_device(&adev->dev); -} -EXPORT_SYMBOL_GPL(typec_altmode_unregister_notifier); - /** * typec_altmode_update_active - Report Enter/Exit mode * @adev: Handle to the alternate mode @@ -432,7 +369,28 @@ static struct attribute *typec_altmode_attrs[] = { &dev_attr_vdo.attr, NULL }; -ATTRIBUTE_GROUPS(typec_altmode); + +static umode_t typec_altmode_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct typec_altmode *adev = to_typec_altmode(kobj_to_dev(kobj)); + + if (attr == &dev_attr_active.attr) + if (!adev->ops || !adev->ops->activate) + return 0444; + + return attr->mode; +} + +static struct attribute_group typec_altmode_group = { + .is_visible = typec_altmode_attr_is_visible, + .attrs = typec_altmode_attrs, +}; + +static const struct attribute_group *typec_altmode_groups[] = { + &typec_altmode_group, + NULL +}; static int altmode_id_get(struct device *dev) { @@ -517,9 +475,7 @@ typec_register_altmode(struct device *parent, dev_set_name(&alt->adev.dev, "%s.%u", dev_name(parent), id); /* Link partners and plugs with the ports */ - if (is_port) - BLOCKING_INIT_NOTIFIER_HEAD(&alt->nh); - else + if (!is_port) typec_altmode_set_partner(alt); /* The partners are bind to drivers */ @@ -859,7 +815,7 @@ struct typec_cable *typec_cable_get(struct typec_port *port) EXPORT_SYMBOL_GPL(typec_cable_get); /** - * typec_cable_get - Decrement the reference count on USB Type-C cable + * typec_cable_put - Decrement the reference count on USB Type-C cable * @cable: The USB Type-C cable */ void typec_cable_put(struct typec_cable *cable) @@ -1091,11 +1047,6 @@ static ssize_t power_role_store(struct device *dev, struct typec_port *port = to_typec_port(dev); int ret; - if (!port->cap->pd_revision) { - dev_dbg(dev, "USB Power Delivery not supported\n"); - return -EOPNOTSUPP; - } - if (!port->ops || !port->ops->pr_set) { dev_dbg(dev, "power role swapping not supported\n"); return -EOPNOTSUPP; @@ -1293,6 +1244,25 @@ static ssize_t usb_power_delivery_revision_show(struct device *dev, } static DEVICE_ATTR_RO(usb_power_delivery_revision); +static ssize_t orientation_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_port *p = to_typec_port(dev); + enum typec_orientation orientation = typec_get_orientation(p); + + switch (orientation) { + case TYPEC_ORIENTATION_NORMAL: + return sprintf(buf, "%s\n", "normal"); + case TYPEC_ORIENTATION_REVERSE: + return sprintf(buf, "%s\n", "reverse"); + case TYPEC_ORIENTATION_NONE: + default: + return sprintf(buf, "%s\n", "unknown"); + } +} +static DEVICE_ATTR_RO(orientation); + static struct attribute *typec_attrs[] = { &dev_attr_data_role.attr, &dev_attr_power_operation_mode.attr, @@ -1303,9 +1273,54 @@ static struct attribute *typec_attrs[] = { &dev_attr_usb_typec_revision.attr, &dev_attr_vconn_source.attr, &dev_attr_port_type.attr, + &dev_attr_orientation.attr, NULL, }; -ATTRIBUTE_GROUPS(typec); + +static umode_t typec_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct typec_port *port = to_typec_port(kobj_to_dev(kobj)); + + if (attr == &dev_attr_data_role.attr) { + if (port->cap->data != TYPEC_PORT_DRD || + !port->ops || !port->ops->dr_set) + return 0444; + } else if (attr == &dev_attr_power_role.attr) { + if (port->cap->type != TYPEC_PORT_DRP || + !port->ops || !port->ops->pr_set) + return 0444; + } else if (attr == &dev_attr_vconn_source.attr) { + if (!port->cap->pd_revision || + !port->ops || !port->ops->vconn_set) + return 0444; + } else if (attr == &dev_attr_preferred_role.attr) { + if (port->cap->type != TYPEC_PORT_DRP || + !port->ops || !port->ops->try_role) + return 0444; + } else if (attr == &dev_attr_port_type.attr) { + if (!port->ops || !port->ops->port_type_set) + return 0; + if (port->cap->type != TYPEC_PORT_DRP) + return 0444; + } else if (attr == &dev_attr_orientation.attr) { + if (port->cap->orientation_aware) + return 0444; + return 0; + } + + return attr->mode; +} + +static struct attribute_group typec_group = { + .is_visible = typec_attr_is_visible, + .attrs = typec_attrs, +}; + +static const struct attribute_group *typec_groups[] = { + &typec_group, + NULL +}; static int typec_uevent(struct device *dev, struct kobj_uevent_env *env) { @@ -1495,13 +1510,13 @@ int typec_set_orientation(struct typec_port *port, { int ret; - if (port->sw) { - ret = port->sw->set(port->sw, orientation); - if (ret) - return ret; - } + ret = typec_switch_set(port->sw, orientation); + if (ret) + return ret; port->orientation = orientation; + sysfs_notify(&port->dev.kobj, NULL, "orientation"); + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); return 0; } @@ -1533,7 +1548,7 @@ int typec_set_mode(struct typec_port *port, int mode) state.mode = mode; - return port->mux ? port->mux->set(port->mux, &state) : 0; + return typec_mux_set(port->mux, &state); } EXPORT_SYMBOL_GPL(typec_set_mode); diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c index 5baf0f416c73..52ad277e4565 100644 --- a/drivers/usb/typec/mux.c +++ b/drivers/usb/typec/mux.c @@ -17,11 +17,6 @@ #include "bus.h" -static int name_match(struct device *dev, const void *name) -{ - return !strcmp((const char *)name, dev_name(dev)); -} - static bool dev_name_ends_with(struct device *dev, const char *suffix) { const char *name = dev_name(dev); @@ -44,41 +39,36 @@ static void *typec_switch_match(struct device_connection *con, int ep, { struct device *dev; - if (con->fwnode) { - if (con->id && !fwnode_property_present(con->fwnode, con->id)) - return NULL; + if (con->id && !fwnode_property_present(con->fwnode, con->id)) + return NULL; - dev = class_find_device(&typec_mux_class, NULL, con->fwnode, - switch_fwnode_match); - } else { - dev = class_find_device(&typec_mux_class, NULL, - con->endpoint[ep], name_match); - } + dev = class_find_device(&typec_mux_class, NULL, con->fwnode, + switch_fwnode_match); return dev ? to_typec_switch(dev) : ERR_PTR(-EPROBE_DEFER); } /** - * typec_switch_get - Find USB Type-C orientation switch - * @dev: The caller device + * fwnode_typec_switch_get - Find USB Type-C orientation switch + * @fwnode: The caller device node * * Finds a switch linked with @dev. Returns a reference to the switch on * success, NULL if no matching connection was found, or * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch * has not been enumerated yet. */ -struct typec_switch *typec_switch_get(struct device *dev) +struct typec_switch *fwnode_typec_switch_get(struct fwnode_handle *fwnode) { struct typec_switch *sw; - sw = device_connection_find_match(dev, "orientation-switch", NULL, + sw = fwnode_connection_find_match(fwnode, "orientation-switch", NULL, typec_switch_match); if (!IS_ERR_OR_NULL(sw)) WARN_ON(!try_module_get(sw->dev.parent->driver->owner)); return sw; } -EXPORT_SYMBOL_GPL(typec_switch_get); +EXPORT_SYMBOL_GPL(fwnode_typec_switch_get); /** * typec_put_switch - Release USB Type-C orientation switch @@ -137,7 +127,8 @@ typec_switch_register(struct device *parent, sw->dev.class = &typec_mux_class; sw->dev.type = &typec_switch_dev_type; sw->dev.driver_data = desc->drvdata; - dev_set_name(&sw->dev, "%s-switch", dev_name(parent)); + dev_set_name(&sw->dev, "%s-switch", + desc->name ? desc->name : dev_name(parent)); ret = device_add(&sw->dev); if (ret) { @@ -150,6 +141,16 @@ typec_switch_register(struct device *parent, } EXPORT_SYMBOL_GPL(typec_switch_register); +int typec_switch_set(struct typec_switch *sw, + enum typec_orientation orientation) +{ + if (IS_ERR_OR_NULL(sw)) + return 0; + + return sw->set(sw, orientation); +} +EXPORT_SYMBOL_GPL(typec_switch_set); + /** * typec_switch_unregister - Unregister USB Type-C orientation switch * @sw: USB Type-C orientation switch @@ -191,13 +192,6 @@ static void *typec_mux_match(struct device_connection *con, int ep, void *data) u16 *val; int i; - if (!con->fwnode) { - dev = class_find_device(&typec_mux_class, NULL, - con->endpoint[ep], name_match); - - return dev ? to_typec_switch(dev) : ERR_PTR(-EPROBE_DEFER); - } - /* * Check has the identifier already been "consumed". If it * has, no need to do any extra connection identification. @@ -247,8 +241,8 @@ find_mux: } /** - * typec_mux_get - Find USB Type-C Multiplexer - * @dev: The caller device + * 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 @@ -256,19 +250,19 @@ find_mux: * 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 *typec_mux_get(struct device *dev, - const struct typec_altmode_desc *desc) +struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode, + const struct typec_altmode_desc *desc) { struct typec_mux *mux; - mux = device_connection_find_match(dev, "mode-switch", (void *)desc, + mux = fwnode_connection_find_match(fwnode, "mode-switch", (void *)desc, typec_mux_match); if (!IS_ERR_OR_NULL(mux)) WARN_ON(!try_module_get(mux->dev.parent->driver->owner)); return mux; } -EXPORT_SYMBOL_GPL(typec_mux_get); +EXPORT_SYMBOL_GPL(fwnode_typec_mux_get); /** * typec_mux_put - Release handle to a Multiplexer @@ -285,6 +279,15 @@ void typec_mux_put(struct typec_mux *mux) } EXPORT_SYMBOL_GPL(typec_mux_put); +int typec_mux_set(struct typec_mux *mux, struct typec_mux_state *state) +{ + if (IS_ERR_OR_NULL(mux)) + return 0; + + return mux->set(mux, state); +} +EXPORT_SYMBOL_GPL(typec_mux_set); + static void typec_mux_release(struct device *dev) { kfree(to_typec_mux(dev)); @@ -326,7 +329,8 @@ typec_mux_register(struct device *parent, const struct typec_mux_desc *desc) mux->dev.class = &typec_mux_class; mux->dev.type = &typec_mux_dev_type; mux->dev.driver_data = desc->drvdata; - dev_set_name(&mux->dev, "%s-mux", dev_name(parent)); + dev_set_name(&mux->dev, "%s-mux", + desc->name ? desc->name : dev_name(parent)); ret = device_add(&mux->dev); if (ret) { diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig index 01ed0d5e10e8..77eb97b2aa86 100644 --- a/drivers/usb/typec/mux/Kconfig +++ b/drivers/usb/typec/mux/Kconfig @@ -9,4 +9,13 @@ config TYPEC_MUX_PI3USB30532 Say Y or M if your system has a Pericom PI3USB30532 Type-C cross switch / mux chip found on some devices with a Type-C port. +config TYPEC_MUX_INTEL_PMC + tristate "Intel PMC mux control" + depends on INTEL_PMC_IPC + select USB_ROLE_SWITCH + help + Driver for USB muxes controlled by Intel PMC FW. Intel PMC FW can + control the USB role switch and also the multiplexer/demultiplexer + switches used with USB Type-C Alternate Modes. + endmenu diff --git a/drivers/usb/typec/mux/Makefile b/drivers/usb/typec/mux/Makefile index 1332e469b8a0..280a6f553115 100644 --- a/drivers/usb/typec/mux/Makefile +++ b/drivers/usb/typec/mux/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_TYPEC_MUX_PI3USB30532) += pi3usb30532.o +obj-$(CONFIG_TYPEC_MUX_INTEL_PMC) += intel_pmc_mux.o diff --git a/drivers/usb/typec/mux/intel_pmc_mux.c b/drivers/usb/typec/mux/intel_pmc_mux.c new file mode 100644 index 000000000000..f5c5e0aef66f --- /dev/null +++ b/drivers/usb/typec/mux/intel_pmc_mux.c @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Intel PMC USB mux control + * + * Copyright (C) 2020 Intel Corporation + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> + */ + +#include <linux/acpi.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/usb/role.h> +#include <linux/usb/typec_mux.h> +#include <linux/usb/typec_dp.h> +#include <linux/usb/typec_tbt.h> + +#include <asm/intel_pmc_ipc.h> + +#define PMC_USBC_CMD 0xa7 + +/* "Usage" OOB Message field values */ +enum { + PMC_USB_CONNECT, + PMC_USB_DISCONNECT, + PMC_USB_SAFE_MODE, + PMC_USB_ALT_MODE, + PMC_USB_DP_HPD, +}; + +#define PMC_USB_MSG_USB2_PORT_SHIFT 0 +#define PMC_USB_MSG_USB3_PORT_SHIFT 4 +#define PMC_USB_MSG_UFP_SHIFT 4 +#define PMC_USB_MSG_ORI_HSL_SHIFT 5 +#define PMC_USB_MSG_ORI_AUX_SHIFT 6 + +/* Alt Mode Request */ +struct altmode_req { + u8 usage; + u8 mode_type; + u8 mode_id; + u8 reserved; + u32 mode_data; +} __packed; + +#define PMC_USB_MODE_TYPE_SHIFT 4 + +enum { + PMC_USB_MODE_TYPE_USB, + PMC_USB_MODE_TYPE_DP, + PMC_USB_MODE_TYPE_TBT, +}; + +/* Common Mode Data bits */ +#define PMC_USB_ALTMODE_ACTIVE_CABLE BIT(2) + +#define PMC_USB_ALTMODE_ORI_SHIFT 1 +#define PMC_USB_ALTMODE_UFP_SHIFT 3 +#define PMC_USB_ALTMODE_ORI_AUX_SHIFT 4 +#define PMC_USB_ALTMODE_ORI_HSL_SHIFT 5 + +/* DP specific Mode Data bits */ +#define PMC_USB_ALTMODE_DP_MODE_SHIFT 8 + +/* TBT specific Mode Data bits */ +#define PMC_USB_ALTMODE_TBT_TYPE BIT(17) +#define PMC_USB_ALTMODE_CABLE_TYPE BIT(18) +#define PMC_USB_ALTMODE_ACTIVE_LINK BIT(20) +#define PMC_USB_ALTMODE_FORCE_LSR BIT(23) +#define PMC_USB_ALTMODE_CABLE_SPD(_s_) (((_s_) & GENMASK(2, 0)) << 25) +#define PMC_USB_ALTMODE_CABLE_USB31 1 +#define PMC_USB_ALTMODE_CABLE_10GPS 2 +#define PMC_USB_ALTMODE_CABLE_20GPS 3 +#define PMC_USB_ALTMODE_TBT_GEN(_g_) (((_g_) & GENMASK(1, 0)) << 28) + +/* Display HPD Request bits */ +#define PMC_USB_DP_HPD_IRQ BIT(5) +#define PMC_USB_DP_HPD_LVL BIT(6) + +struct pmc_usb; + +struct pmc_usb_port { + int num; + struct pmc_usb *pmc; + struct typec_mux *typec_mux; + struct typec_switch *typec_sw; + struct usb_role_switch *usb_sw; + + enum typec_orientation orientation; + enum usb_role role; + + u8 usb2_port; + u8 usb3_port; +}; + +struct pmc_usb { + u8 num_ports; + struct device *dev; + struct pmc_usb_port *port; +}; + +static int pmc_usb_command(struct pmc_usb_port *port, u8 *msg, u32 len) +{ + u8 response[4]; + + /* + * Error bit will always be 0 with the USBC command. + * Status can be checked from the response message. + */ + intel_pmc_ipc_command(PMC_USBC_CMD, 0, msg, len, + (void *)response, 1); + + if (response[2]) { + if (response[2] & BIT(1)) + return -EIO; + return -EBUSY; + } + + return 0; +} + +static int +pmc_usb_mux_dp_hpd(struct pmc_usb_port *port, struct typec_mux_state *state) +{ + struct typec_displayport_data *data = state->data; + u8 msg[2] = { }; + + msg[0] = PMC_USB_DP_HPD; + msg[0] |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT; + + msg[1] = PMC_USB_DP_HPD_IRQ; + + if (data->status & DP_STATUS_HPD_STATE) + msg[1] |= PMC_USB_DP_HPD_LVL; + + return pmc_usb_command(port, msg, sizeof(msg)); +} + +static int +pmc_usb_mux_dp(struct pmc_usb_port *port, struct typec_mux_state *state) +{ + struct typec_displayport_data *data = state->data; + struct altmode_req req = { }; + + if (data->status & DP_STATUS_IRQ_HPD) + return pmc_usb_mux_dp_hpd(port, state); + + req.usage = PMC_USB_ALT_MODE; + req.usage |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT; + req.mode_type = PMC_USB_MODE_TYPE_DP << PMC_USB_MODE_TYPE_SHIFT; + + req.mode_data = (port->orientation - 1) << PMC_USB_ALTMODE_ORI_SHIFT; + req.mode_data |= (port->role - 1) << PMC_USB_ALTMODE_UFP_SHIFT; + req.mode_data |= (port->orientation - 1) << PMC_USB_ALTMODE_ORI_AUX_SHIFT; + req.mode_data |= (port->orientation - 1) << PMC_USB_ALTMODE_ORI_HSL_SHIFT; + + req.mode_data |= (state->mode - TYPEC_STATE_MODAL) << + PMC_USB_ALTMODE_DP_MODE_SHIFT; + + return pmc_usb_command(port, (void *)&req, sizeof(req)); +} + +static int +pmc_usb_mux_tbt(struct pmc_usb_port *port, struct typec_mux_state *state) +{ + struct typec_thunderbolt_data *data = state->data; + u8 cable_speed = TBT_CABLE_SPEED(data->cable_mode); + struct altmode_req req = { }; + + req.usage = PMC_USB_ALT_MODE; + req.usage |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT; + req.mode_type = PMC_USB_MODE_TYPE_TBT << PMC_USB_MODE_TYPE_SHIFT; + + req.mode_data = (port->orientation - 1) << PMC_USB_ALTMODE_ORI_SHIFT; + req.mode_data |= (port->role - 1) << PMC_USB_ALTMODE_UFP_SHIFT; + req.mode_data |= (port->orientation - 1) << PMC_USB_ALTMODE_ORI_AUX_SHIFT; + req.mode_data |= (port->orientation - 1) << PMC_USB_ALTMODE_ORI_HSL_SHIFT; + + if (TBT_ADAPTER(data->device_mode) == TBT_ADAPTER_TBT3) + req.mode_data |= PMC_USB_ALTMODE_TBT_TYPE; + + if (data->cable_mode & TBT_CABLE_OPTICAL) + req.mode_data |= PMC_USB_ALTMODE_CABLE_TYPE; + + if (data->cable_mode & TBT_CABLE_LINK_TRAINING) + req.mode_data |= PMC_USB_ALTMODE_ACTIVE_LINK; + + if (data->enter_vdo & TBT_ENTER_MODE_ACTIVE_CABLE) + req.mode_data |= PMC_USB_ALTMODE_ACTIVE_CABLE; + + req.mode_data |= PMC_USB_ALTMODE_CABLE_SPD(cable_speed); + + return pmc_usb_command(port, (void *)&req, sizeof(req)); +} + +static int pmc_usb_mux_safe_state(struct pmc_usb_port *port) +{ + u8 msg; + + msg = PMC_USB_SAFE_MODE; + msg |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT; + + return pmc_usb_command(port, &msg, sizeof(msg)); +} + +static int pmc_usb_connect(struct pmc_usb_port *port) +{ + u8 msg[2]; + + msg[0] = PMC_USB_CONNECT; + msg[0] |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT; + + msg[1] = port->usb2_port << PMC_USB_MSG_USB2_PORT_SHIFT; + msg[1] |= (port->orientation - 1) << PMC_USB_MSG_ORI_HSL_SHIFT; + msg[1] |= (port->orientation - 1) << PMC_USB_MSG_ORI_AUX_SHIFT; + + return pmc_usb_command(port, msg, sizeof(msg)); +} + +static int pmc_usb_disconnect(struct pmc_usb_port *port) +{ + u8 msg[2]; + + msg[0] = PMC_USB_DISCONNECT; + msg[0] |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT; + + msg[1] = port->usb2_port << PMC_USB_MSG_USB2_PORT_SHIFT; + + return pmc_usb_command(port, msg, sizeof(msg)); +} + +static int +pmc_usb_mux_set(struct typec_mux *mux, struct typec_mux_state *state) +{ + struct pmc_usb_port *port = typec_mux_get_drvdata(mux); + + if (!state->alt) + return 0; + + if (state->mode == TYPEC_STATE_SAFE) + return pmc_usb_mux_safe_state(port); + + switch (state->alt->svid) { + case USB_TYPEC_TBT_SID: + return pmc_usb_mux_tbt(port, state); + case USB_TYPEC_DP_SID: + return pmc_usb_mux_dp(port, state); + } + + return -EOPNOTSUPP; +} + +static int pmc_usb_set_orientation(struct typec_switch *sw, + enum typec_orientation orientation) +{ + struct pmc_usb_port *port = typec_switch_get_drvdata(sw); + + if (port->orientation == orientation) + return 0; + + port->orientation = orientation; + + if (port->role) { + if (orientation == TYPEC_ORIENTATION_NONE) + return pmc_usb_disconnect(port); + else + return pmc_usb_connect(port); + } + + return 0; +} + +static int pmc_usb_set_role(struct usb_role_switch *sw, enum usb_role role) +{ + struct pmc_usb_port *port = usb_role_switch_get_drvdata(sw); + + if (port->role == role) + return 0; + + port->role = role; + + if (port->orientation) { + if (role == USB_ROLE_NONE) + return pmc_usb_disconnect(port); + else + return pmc_usb_connect(port); + } + + return 0; +} + +static int pmc_usb_register_port(struct pmc_usb *pmc, int index, + struct fwnode_handle *fwnode) +{ + struct pmc_usb_port *port = &pmc->port[index]; + struct usb_role_switch_desc desc = { }; + struct typec_switch_desc sw_desc = { }; + struct typec_mux_desc mux_desc = { }; + int ret; + + ret = fwnode_property_read_u8(fwnode, "usb2-port", &port->usb2_port); + if (ret) + return ret; + + ret = fwnode_property_read_u8(fwnode, "usb3-port", &port->usb3_port); + if (ret) + return ret; + + port->num = index; + port->pmc = pmc; + + sw_desc.fwnode = fwnode; + sw_desc.drvdata = port; + sw_desc.name = fwnode_get_name(fwnode); + sw_desc.set = pmc_usb_set_orientation; + + port->typec_sw = typec_switch_register(pmc->dev, &sw_desc); + if (IS_ERR(port->typec_sw)) + return PTR_ERR(port->typec_sw); + + mux_desc.fwnode = fwnode; + mux_desc.drvdata = port; + mux_desc.name = fwnode_get_name(fwnode); + mux_desc.set = pmc_usb_mux_set; + + port->typec_mux = typec_mux_register(pmc->dev, &mux_desc); + if (IS_ERR(port->typec_mux)) { + ret = PTR_ERR(port->typec_mux); + goto err_unregister_switch; + } + + desc.fwnode = fwnode; + desc.driver_data = port; + desc.name = fwnode_get_name(fwnode); + desc.set = pmc_usb_set_role; + + port->usb_sw = usb_role_switch_register(pmc->dev, &desc); + if (IS_ERR(port->usb_sw)) { + ret = PTR_ERR(port->usb_sw); + goto err_unregister_mux; + } + + return 0; + +err_unregister_mux: + typec_mux_unregister(port->typec_mux); + +err_unregister_switch: + typec_switch_unregister(port->typec_sw); + + return ret; +} + +static int pmc_usb_probe(struct platform_device *pdev) +{ + struct fwnode_handle *fwnode = NULL; + struct pmc_usb *pmc; + int i = 0; + int ret; + + pmc = devm_kzalloc(&pdev->dev, sizeof(*pmc), GFP_KERNEL); + if (!pmc) + return -ENOMEM; + + device_for_each_child_node(&pdev->dev, fwnode) + pmc->num_ports++; + + pmc->port = devm_kcalloc(&pdev->dev, pmc->num_ports, + sizeof(struct pmc_usb_port), GFP_KERNEL); + if (!pmc->port) + return -ENOMEM; + + pmc->dev = &pdev->dev; + + /* + * For every physical USB connector (USB2 and USB3 combo) there is a + * child ACPI device node under the PMC mux ACPI device object. + */ + for (i = 0; i < pmc->num_ports; i++) { + fwnode = device_get_next_child_node(pmc->dev, fwnode); + if (!fwnode) + break; + + ret = pmc_usb_register_port(pmc, i, fwnode); + if (ret) + goto err_remove_ports; + } + + platform_set_drvdata(pdev, pmc); + + return 0; + +err_remove_ports: + for (i = 0; i < pmc->num_ports; i++) { + typec_switch_unregister(pmc->port[i].typec_sw); + typec_mux_unregister(pmc->port[i].typec_mux); + } + + return ret; +} + +static int pmc_usb_remove(struct platform_device *pdev) +{ + struct pmc_usb *pmc = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < pmc->num_ports; i++) { + typec_switch_unregister(pmc->port[i].typec_sw); + typec_mux_unregister(pmc->port[i].typec_mux); + } + + return 0; +} + +static const struct acpi_device_id pmc_usb_acpi_ids[] = { + { "INTC105C", }, + { } +}; +MODULE_DEVICE_TABLE(acpi, pmc_usb_acpi_ids); + +static struct platform_driver pmc_usb_driver = { + .driver = { + .name = "intel_pmc_usb", + .acpi_match_table = ACPI_PTR(pmc_usb_acpi_ids), + }, + .probe = pmc_usb_probe, + .remove = pmc_usb_remove, +}; + +module_platform_driver(pmc_usb_driver); + +MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel PMC USB mux control"); diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index f3087ef8265c..de3576e6530a 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -373,6 +373,14 @@ struct pd_rx_event { ((port)->try_src_count == 0 && (port)->try_role == TYPEC_SOURCE && \ (port)->port_type == TYPEC_PORT_DRP) +#define tcpm_data_role_for_source(port) \ + ((port)->typec_caps.data == TYPEC_PORT_UFP ? \ + TYPEC_DEVICE : TYPEC_HOST) + +#define tcpm_data_role_for_sink(port) \ + ((port)->typec_caps.data == TYPEC_PORT_DFP ? \ + TYPEC_HOST : TYPEC_DEVICE) + static enum tcpm_state tcpm_default_state(struct tcpm_port *port) { if (port->port_type == TYPEC_PORT_DRP) { @@ -788,10 +796,30 @@ static int tcpm_set_roles(struct tcpm_port *port, bool attached, else orientation = TYPEC_ORIENTATION_REVERSE; - if (data == TYPEC_HOST) - usb_role = USB_ROLE_HOST; - else - usb_role = USB_ROLE_DEVICE; + if (port->typec_caps.data == TYPEC_PORT_DRD) { + if (data == TYPEC_HOST) + usb_role = USB_ROLE_HOST; + else + usb_role = USB_ROLE_DEVICE; + } else if (port->typec_caps.data == TYPEC_PORT_DFP) { + if (data == TYPEC_HOST) { + if (role == TYPEC_SOURCE) + usb_role = USB_ROLE_HOST; + else + usb_role = USB_ROLE_NONE; + } else { + return -ENOTSUPP; + } + } else { + if (data == TYPEC_DEVICE) { + if (role == TYPEC_SINK) + usb_role = USB_ROLE_DEVICE; + else + usb_role = USB_ROLE_NONE; + } else { + return -ENOTSUPP; + } + } ret = tcpm_mux_set(port, TYPEC_STATE_USB, usb_role, orientation); if (ret < 0) @@ -1817,7 +1845,7 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, tcpm_set_state(port, SOFT_RESET, 0); break; case PD_CTRL_DR_SWAP: - if (port->port_type != TYPEC_PORT_DRP) { + if (port->typec_caps.data != TYPEC_PORT_DRD) { tcpm_queue_message(port, PD_MSG_CTRL_REJECT); break; } @@ -2618,7 +2646,8 @@ static int tcpm_src_attach(struct tcpm_port *port) if (ret < 0) return ret; - ret = tcpm_set_roles(port, true, TYPEC_SOURCE, TYPEC_HOST); + ret = tcpm_set_roles(port, true, TYPEC_SOURCE, + tcpm_data_role_for_source(port)); if (ret < 0) return ret; @@ -2740,7 +2769,8 @@ static int tcpm_snk_attach(struct tcpm_port *port) if (ret < 0) return ret; - ret = tcpm_set_roles(port, true, TYPEC_SINK, TYPEC_DEVICE); + ret = tcpm_set_roles(port, true, TYPEC_SINK, + tcpm_data_role_for_sink(port)); if (ret < 0) return ret; @@ -2766,7 +2796,8 @@ static int tcpm_acc_attach(struct tcpm_port *port) if (port->attached) return 0; - ret = tcpm_set_roles(port, true, TYPEC_SOURCE, TYPEC_HOST); + ret = tcpm_set_roles(port, true, TYPEC_SOURCE, + tcpm_data_role_for_source(port)); if (ret < 0) return ret; @@ -3293,7 +3324,7 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_vconn(port, true); tcpm_set_vbus(port, false); tcpm_set_roles(port, port->self_powered, TYPEC_SOURCE, - TYPEC_HOST); + tcpm_data_role_for_source(port)); tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, PD_T_SRC_RECOVER); break; case SRC_HARD_RESET_VBUS_ON: @@ -3308,7 +3339,7 @@ static void run_state_machine(struct tcpm_port *port) if (port->pd_capable) tcpm_set_charge(port, false); tcpm_set_roles(port, port->self_powered, TYPEC_SINK, - TYPEC_DEVICE); + tcpm_data_role_for_sink(port)); /* * VBUS may or may not toggle, depending on the adapter. * If it doesn't toggle, transition to SNK_HARD_RESET_SINK_ON @@ -3649,8 +3680,12 @@ static void _tcpm_cc_change(struct tcpm_port *port, enum typec_cc_status cc1, case SRC_SEND_CAPABILITIES: case SRC_READY: if (tcpm_port_is_disconnected(port) || - !tcpm_port_is_source(port)) - tcpm_set_state(port, SRC_UNATTACHED, 0); + !tcpm_port_is_source(port)) { + if (port->port_type == TYPEC_PORT_SRC) + tcpm_set_state(port, SRC_UNATTACHED, 0); + else + tcpm_set_state(port, SNK_UNATTACHED, 0); + } break; case SNK_UNATTACHED: if (tcpm_port_is_sink(port)) @@ -3969,7 +4004,7 @@ static int tcpm_dr_set(struct typec_port *p, enum typec_data_role data) mutex_lock(&port->swap_lock); mutex_lock(&port->lock); - if (port->port_type != TYPEC_PORT_DRP) { + if (port->typec_caps.data != TYPEC_PORT_DRD) { ret = -EINVAL; goto port_unlock; } @@ -4711,6 +4746,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) port->typec_caps.pd_revision = 0x0300; /* USB-PD spec release 3.0 */ port->typec_caps.driver_data = port; port->typec_caps.ops = &tcpm_ops; + port->typec_caps.orientation_aware = 1; port->partner_desc.identity = &port->partner_ident; port->port_type = port->typec_caps.type; diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index d5a6aac86327..ddf2ad3752de 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -270,9 +270,16 @@ static int ucsi_register_altmode(struct ucsi_connector *con, switch (desc->svid) { case USB_TYPEC_DP_SID: - case USB_TYPEC_NVIDIA_VLINK_SID: alt = ucsi_register_displayport(con, override, i, desc); break; + case USB_TYPEC_NVIDIA_VLINK_SID: + if (desc->vdo == USB_TYPEC_NVIDIA_VLINK_DBG_VDO) + alt = typec_port_register_altmode(con->port, + desc); + else + alt = ucsi_register_displayport(con, override, + i, desc); + break; default: alt = typec_port_register_altmode(con->port, desc); break; @@ -400,7 +407,7 @@ static int ucsi_register_altmodes(struct ucsi_connector *con, u8 recipient) struct typec_altmode_desc desc; struct ucsi_altmode alt[2]; u64 command; - int num = 1; + int num; int ret; int len; int j; @@ -475,7 +482,8 @@ static void ucsi_unregister_altmodes(struct ucsi_connector *con, u8 recipient) while (adev[i]) { if (recipient == UCSI_RECIPIENT_SOP && (adev[i]->svid == USB_TYPEC_DP_SID || - adev[i]->svid == USB_TYPEC_NVIDIA_VLINK_SID)) { + (adev[i]->svid == USB_TYPEC_NVIDIA_VLINK_SID && + adev[i]->vdo != USB_TYPEC_NVIDIA_VLINK_DBG_VDO))) { pdev = typec_altmode_get_partner(adev[i]); ucsi_displayport_remove_partner((void *)pdev); } diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h index e434b9c9a9eb..8e831108f481 100644 --- a/drivers/usb/typec/ucsi/ucsi.h +++ b/drivers/usb/typec/ucsi/ucsi.h @@ -119,12 +119,14 @@ void ucsi_connector_change(struct ucsi *ucsi, u8 num); #define UCSI_SET_PDR_ACCEPT_ROLE_SWAPS BIT(25) /* GET_ALTERNATE_MODES command bits */ +#define UCSI_ALTMODE_RECIPIENT(_r_) (((_r_) >> 16) & 0x7) #define UCSI_GET_ALTMODE_RECIPIENT(_r_) ((u64)(_r_) << 16) #define UCSI_RECIPIENT_CON 0 #define UCSI_RECIPIENT_SOP 1 #define UCSI_RECIPIENT_SOP_P 2 #define UCSI_RECIPIENT_SOP_PP 3 #define UCSI_GET_ALTMODE_CONNECTOR_NUMBER(_r_) ((u64)(_r_) << 24) +#define UCSI_ALTMODE_OFFSET(_r_) (((_r_) >> 32) & 0xff) #define UCSI_GET_ALTMODE_OFFSET(_r_) ((u64)(_r_) << 32) #define UCSI_GET_ALTMODE_NUM_ALTMODES(_r_) ((u64)(_r_) << 40) @@ -340,4 +342,11 @@ static inline void ucsi_displayport_remove_partner(struct typec_altmode *adev) { } #endif /* CONFIG_TYPEC_DP_ALTMODE */ +/* + * NVIDIA VirtualLink (svid 0x955) has two altmode. VirtualLink + * DP mode with vdo=0x1 and NVIDIA test mode with vdo=0x3 + */ +#define USB_TYPEC_NVIDIA_VLINK_DP_VDO 0x1 +#define USB_TYPEC_NVIDIA_VLINK_DBG_VDO 0x3 + #endif /* __DRIVER_USB_TYPEC_UCSI_H */ diff --git a/drivers/usb/typec/ucsi/ucsi_ccg.c b/drivers/usb/typec/ucsi/ucsi_ccg.c index a5b8530490db..bff96d64dddf 100644 --- a/drivers/usb/typec/ucsi/ucsi_ccg.c +++ b/drivers/usb/typec/ucsi/ucsi_ccg.c @@ -125,6 +125,10 @@ struct version_format { #define CCG_FW_BUILD_NVIDIA (('n' << 8) | 'v') #define CCG_OLD_FW_VERSION (CCG_VERSION(0x31) | CCG_VERSION_PATCH(10)) +/* Altmode offset for NVIDIA Function Test Board (FTB) */ +#define NVIDIA_FTB_DP_OFFSET (2) +#define NVIDIA_FTB_DBG_OFFSET (3) + struct version_info { struct version_format base; struct version_format app; @@ -477,24 +481,65 @@ static void ucsi_ccg_update_set_new_cam_cmd(struct ucsi_ccg *uc, *cmd |= UCSI_SET_NEW_CAM_SET_AM(cam); } +/* + * Change the order of vdo values of NVIDIA test device FTB + * (Function Test Board) which reports altmode list with vdo=0x3 + * first and then vdo=0x. Current logic to assign mode value is + * based on order in altmode list and it causes a mismatch of CON + * and SOP altmodes since NVIDIA GPU connector has order of vdo=0x1 + * first and then vdo=0x3 + */ +static void ucsi_ccg_nvidia_altmode(struct ucsi_ccg *uc, + struct ucsi_altmode *alt) +{ + switch (UCSI_ALTMODE_OFFSET(uc->last_cmd_sent)) { + case NVIDIA_FTB_DP_OFFSET: + if (alt[0].mid == USB_TYPEC_NVIDIA_VLINK_DBG_VDO) + alt[0].mid = USB_TYPEC_NVIDIA_VLINK_DP_VDO | + DP_CAP_DP_SIGNALING | DP_CAP_USB | + DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_E)); + break; + case NVIDIA_FTB_DBG_OFFSET: + if (alt[0].mid == USB_TYPEC_NVIDIA_VLINK_DP_VDO) + alt[0].mid = USB_TYPEC_NVIDIA_VLINK_DBG_VDO; + break; + default: + break; + } +} + static int ucsi_ccg_read(struct ucsi *ucsi, unsigned int offset, void *val, size_t val_len) { struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi); - int ret; u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(offset); + struct ucsi_altmode *alt; + int ret; ret = ccg_read(uc, reg, val, val_len); if (ret) return ret; - if (offset == UCSI_MESSAGE_IN) { - if (UCSI_COMMAND(uc->last_cmd_sent) == UCSI_GET_CURRENT_CAM && - uc->has_multiple_dp) { + if (offset != UCSI_MESSAGE_IN) + return ret; + + switch (UCSI_COMMAND(uc->last_cmd_sent)) { + case UCSI_GET_CURRENT_CAM: + if (uc->has_multiple_dp) ucsi_ccg_update_get_current_cam_cmd(uc, (u8 *)val); + break; + case UCSI_GET_ALTERNATE_MODES: + if (UCSI_ALTMODE_RECIPIENT(uc->last_cmd_sent) == + UCSI_RECIPIENT_SOP) { + alt = val; + if (alt[0].svid == USB_TYPEC_NVIDIA_VLINK_SID) + ucsi_ccg_nvidia_altmode(uc, alt); } - uc->last_cmd_sent = 0; + break; + default: + break; } + uc->last_cmd_sent = 0; return ret; } @@ -1219,6 +1264,7 @@ static int ccg_restart(struct ucsi_ccg *uc) return status; } + pm_runtime_enable(uc->dev); return 0; } @@ -1234,6 +1280,7 @@ static void ccg_update_firmware(struct work_struct *work) if (flash_mode != FLASH_NOT_NEEDED) { ucsi_unregister(uc->ucsi); + pm_runtime_disable(uc->dev); free_irq(uc->irq, uc); ccg_fw_update(uc, flash_mode); diff --git a/include/linux/phy/tegra/xusb.h b/include/linux/phy/tegra/xusb.h index 1235865e7e2c..71d956935405 100644 --- a/include/linux/phy/tegra/xusb.h +++ b/include/linux/phy/tegra/xusb.h @@ -21,4 +21,6 @@ int tegra_xusb_padctl_usb3_set_lfps_detect(struct tegra_xusb_padctl *padctl, int tegra_xusb_padctl_set_vbus_override(struct tegra_xusb_padctl *padctl, bool val); int tegra_phy_xusb_utmi_port_reset(struct phy *phy); +int tegra_xusb_padctl_get_usb3_companion(struct tegra_xusb_padctl *padctl, + unsigned int port); #endif /* PHY_TEGRA_XUSB_H */ diff --git a/include/linux/platform_device.h b/include/linux/platform_device.h index 041bfa412aa0..f242f66db19d 100644 --- a/include/linux/platform_device.h +++ b/include/linux/platform_device.h @@ -55,6 +55,9 @@ extern struct device * platform_find_device_by_driver(struct device *start, const struct device_driver *drv); extern void __iomem * +devm_platform_get_and_ioremap_resource(struct platform_device *pdev, + unsigned int index, struct resource **res); +extern void __iomem * devm_platform_ioremap_resource(struct platform_device *pdev, unsigned int index); extern void __iomem * diff --git a/include/linux/usb.h b/include/linux/usb.h index e656e7b4b1e4..9f3c721c70dc 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -325,7 +325,7 @@ struct usb_interface_cache { /* variable-length array of alternate settings for this interface, * stored in no particular order */ - struct usb_host_interface altsetting[0]; + struct usb_host_interface altsetting[]; }; #define ref_to_usb_interface_cache(r) \ container_of(r, struct usb_interface_cache, ref) @@ -708,6 +708,7 @@ struct usb_device { unsigned lpm_disable_count; u16 hub_delay; + unsigned use_generic_driver:1; }; #define to_usb_device(d) container_of(d, struct usb_device, dev) @@ -1228,12 +1229,16 @@ struct usb_driver { * @drvwrap: Driver-model core structure wrapper. * @supports_autosuspend: if set to 0, the USB core will not allow autosuspend * for devices bound to this driver. + * @generic_subclass: if set to 1, the generic USB driver's probe, disconnect, + * resume and suspend functions will be called in addition to the driver's + * own, so this part of the setup does not need to be replicated. * * USB drivers must provide all the fields listed above except drvwrap. */ struct usb_device_driver { const char *name; + bool (*match) (struct usb_device *udev); int (*probe) (struct usb_device *udev); void (*disconnect) (struct usb_device *udev); @@ -1241,7 +1246,9 @@ struct usb_device_driver { int (*resume) (struct usb_device *udev, pm_message_t message); const struct attribute_group **dev_groups; struct usbdrv_wrap drvwrap; + const struct usb_device_id *id_table; unsigned int supports_autosuspend:1; + unsigned int generic_subclass:1; }; #define to_usb_device_driver(d) container_of(d, struct usb_device_driver, \ drvwrap.driver) @@ -1582,7 +1589,7 @@ struct urb { int error_count; /* (return) number of ISO errors */ void *context; /* (in) context for completion */ usb_complete_t complete; /* (in) completion routine */ - struct usb_iso_packet_descriptor iso_frame_desc[0]; + struct usb_iso_packet_descriptor iso_frame_desc[]; /* (in) ISO ONLY */ }; diff --git a/include/linux/usb/audio-v2.h b/include/linux/usb/audio-v2.h index ba4b3e3327ff..5e31740c7e40 100644 --- a/include/linux/usb/audio-v2.h +++ b/include/linux/usb/audio-v2.h @@ -153,7 +153,7 @@ struct uac2_feature_unit_descriptor { __u8 bSourceID; /* bmaControls is actually u32, * but u8 is needed for the hybrid parser */ - __u8 bmaControls[0]; /* variable length */ + __u8 bmaControls[]; /* variable length */ } __attribute__((packed)); /* 4.9.2 Class-Specific AS Interface Descriptor */ diff --git a/include/linux/usb/audio-v3.h b/include/linux/usb/audio-v3.h index 6b708434b7f9..c69a6f2e6837 100644 --- a/include/linux/usb/audio-v3.h +++ b/include/linux/usb/audio-v3.h @@ -109,7 +109,7 @@ struct uac3_feature_unit_descriptor { __u8 bSourceID; /* bmaControls is actually u32, * but u8 is needed for the hybrid parser */ - __u8 bmaControls[0]; /* variable length */ + __u8 bmaControls[]; /* variable length */ /* wFeatureDescrStr omitted */ } __attribute__((packed)); diff --git a/include/linux/usb/ehci_def.h b/include/linux/usb/ehci_def.h index a15ce99dfc2d..78e006355557 100644 --- a/include/linux/usb/ehci_def.h +++ b/include/linux/usb/ehci_def.h @@ -151,7 +151,7 @@ struct ehci_regs { #define PORT_OWNER (1<<13) /* true: companion hc owns this port */ #define PORT_POWER (1<<12) /* true: has power (see PPC) */ #define PORT_USB11(x) (((x)&(3<<10)) == (1<<10)) /* USB 1.1 device */ -/* 11:10 for detecting lowspeed devices (reset vs release ownership) */ +#define PORT_LS_MASK (3<<10) /* Link status (SE0, K or J */ /* 9 reserved */ #define PORT_LPM (1<<9) /* LPM transaction */ #define PORT_RESET (1<<8) /* reset port */ diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index 124462d65eac..9411c08a5c7e 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -767,7 +767,7 @@ struct usb_gadget_strings { struct usb_gadget_string_container { struct list_head list; - u8 *stash[0]; + u8 *stash[]; }; /* put descriptor for string with that id into buf (buflen >= 256) */ diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index 712b2a603645..e12105ed3834 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -228,7 +228,7 @@ struct usb_hcd { /* The HC driver's private data is stored at the end of * this structure. */ - unsigned long hcd_priv[0] + unsigned long hcd_priv[] __attribute__ ((aligned(sizeof(s64)))); }; diff --git a/include/linux/usb/role.h b/include/linux/usb/role.h index efac3af83d6b..0164fed31b06 100644 --- a/include/linux/usb/role.h +++ b/include/linux/usb/role.h @@ -13,8 +13,9 @@ enum usb_role { USB_ROLE_DEVICE, }; -typedef int (*usb_role_switch_set_t)(struct device *dev, enum usb_role role); -typedef enum usb_role (*usb_role_switch_get_t)(struct device *dev); +typedef int (*usb_role_switch_set_t)(struct usb_role_switch *sw, + enum usb_role role); +typedef enum usb_role (*usb_role_switch_get_t)(struct usb_role_switch *sw); /** * struct usb_role_switch_desc - USB Role Switch Descriptor @@ -25,6 +26,8 @@ typedef enum usb_role (*usb_role_switch_get_t)(struct device *dev); * @set: Callback for setting the role * @get: Callback for getting the role (optional) * @allow_userspace_control: If true userspace may change the role through sysfs + * @driver_data: Private data pointer + * @name: Name for the switch (optional) * * @usb2_port and @usb3_port will point to the USB host port and @udc to the USB * device controller behind the USB connector with the role switch. If @@ -40,6 +43,8 @@ struct usb_role_switch_desc { usb_role_switch_set_t set; usb_role_switch_get_t get; bool allow_userspace_control; + void *driver_data; + const char *name; }; @@ -57,6 +62,9 @@ struct usb_role_switch * usb_role_switch_register(struct device *parent, const struct usb_role_switch_desc *desc); void usb_role_switch_unregister(struct usb_role_switch *sw); + +void usb_role_switch_set_drvdata(struct usb_role_switch *sw, void *data); +void *usb_role_switch_get_drvdata(struct usb_role_switch *sw); #else static inline int usb_role_switch_set_role(struct usb_role_switch *sw, enum usb_role role) @@ -90,6 +98,17 @@ usb_role_switch_register(struct device *parent, } static inline void usb_role_switch_unregister(struct usb_role_switch *sw) { } + +static inline void +usb_role_switch_set_drvdata(struct usb_role_switch *sw, void *data) +{ +} + +static inline void *usb_role_switch_get_drvdata(struct usb_role_switch *sw) +{ + return NULL; +} + #endif #endif /* __LINUX_USB_ROLE_H */ diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index c358b3fd05c9..b00a2642a9cd 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -198,8 +198,6 @@ struct typec_operations { * @pd_revision: USB Power Delivery Specification revision if supported * @prefer_role: Initial role preference (DRP ports). * @accessory: Supported Accessory Modes - * @sw: Cable plug orientation switch - * @mux: Multiplexer switch for Alternate/Accessory Modes * @fwnode: Optional fwnode of the port * @driver_data: Private pointer for driver specific info * @ops: Port operations vector @@ -213,6 +211,7 @@ struct typec_capability { u16 pd_revision; /* 0300H = "3.0" */ int prefer_role; enum typec_accessory accessory[TYPEC_MAX_ACCESSORY]; + unsigned int orientation_aware:1; struct fwnode_handle *fwnode; void *driver_data; diff --git a/include/linux/usb/typec_altmode.h b/include/linux/usb/typec_altmode.h index 923ff3af0628..d834e236c6df 100644 --- a/include/linux/usb/typec_altmode.h +++ b/include/linux/usb/typec_altmode.h @@ -126,13 +126,6 @@ void typec_altmode_put_plug(struct typec_altmode *plug); struct typec_altmode *typec_match_altmode(struct typec_altmode **altmodes, size_t n, u16 svid, u8 mode); -struct typec_altmode * -typec_altmode_register_notifier(struct device *dev, u16 svid, u8 mode, - struct notifier_block *nb); - -void typec_altmode_unregister_notifier(struct typec_altmode *adev, - struct notifier_block *nb); - /** * typec_altmode_get_orientation - Get cable plug orientation * altmode: Handle to the alternate mode diff --git a/include/linux/usb/typec_mux.h b/include/linux/usb/typec_mux.h index be7292c0be5e..a9d9957933dc 100644 --- a/include/linux/usb/typec_mux.h +++ b/include/linux/usb/typec_mux.h @@ -3,6 +3,7 @@ #ifndef __USB_TYPEC_MUX #define __USB_TYPEC_MUX +#include <linux/property.h> #include <linux/usb/typec.h> struct device; @@ -17,11 +18,20 @@ typedef int (*typec_switch_set_fn_t)(struct typec_switch *sw, struct typec_switch_desc { struct fwnode_handle *fwnode; typec_switch_set_fn_t set; + const char *name; void *drvdata; }; -struct typec_switch *typec_switch_get(struct device *dev); +struct typec_switch *fwnode_typec_switch_get(struct fwnode_handle *fwnode); void typec_switch_put(struct typec_switch *sw); +int typec_switch_set(struct typec_switch *sw, + enum typec_orientation orientation); + +static inline struct typec_switch *typec_switch_get(struct device *dev) +{ + return fwnode_typec_switch_get(dev_fwnode(dev)); +} + struct typec_switch * typec_switch_register(struct device *parent, const struct typec_switch_desc *desc); @@ -42,12 +52,21 @@ typedef int (*typec_mux_set_fn_t)(struct typec_mux *mux, struct typec_mux_desc { struct fwnode_handle *fwnode; typec_mux_set_fn_t set; + const char *name; void *drvdata; }; -struct typec_mux * -typec_mux_get(struct device *dev, const struct typec_altmode_desc *desc); +struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode, + const struct typec_altmode_desc *desc); void typec_mux_put(struct typec_mux *mux); +int typec_mux_set(struct typec_mux *mux, struct typec_mux_state *state); + +static inline struct typec_mux * +typec_mux_get(struct device *dev, const struct typec_altmode_desc *desc) +{ + return fwnode_typec_mux_get(dev_fwnode(dev), desc); +} + struct typec_mux * typec_mux_register(struct device *parent, const struct typec_mux_desc *desc); void typec_mux_unregister(struct typec_mux *mux); diff --git a/include/linux/usb/typec_tbt.h b/include/linux/usb/typec_tbt.h new file mode 100644 index 000000000000..47c2d501ddce --- /dev/null +++ b/include/linux/usb/typec_tbt.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USB_TYPEC_TBT_H +#define __USB_TYPEC_TBT_H + +#include <linux/usb/typec_altmode.h> + +#define USB_TYPEC_VENDOR_INTEL 0x8087 +/* Alias for convenience */ +#define USB_TYPEC_TBT_SID USB_TYPEC_VENDOR_INTEL + +/* Connector state for Thunderbolt3 */ +#define TYPEC_TBT_MODE TYPEC_STATE_MODAL + +/** + * struct typec_thunderbolt_data - Thundebolt3 Alt Mode specific data + * @device_mode: Device Discover Mode VDO + * @cable_mode: Cable Discover Mode VDO + * @enter_vdo: Enter Mode VDO + */ +struct typec_thunderbolt_data { + u32 device_mode; + u32 cable_mode; + u32 enter_vdo; +}; + +/* TBT3 Device Discover Mode VDO bits */ +#define TBT_MODE BIT(0) +#define TBT_ADAPTER(_vdo_) (((_vdo_) & BIT(16)) >> 16) +#define TBT_ADAPTER_LEGACY 0 +#define TBT_ADAPTER_TBT3 1 +#define TBT_INTEL_SPECIFIC_B0 BIT(26) +#define TBT_VENDOR_SPECIFIC_B0 BIT(30) +#define TBT_VENDOR_SPECIFIC_B1 BIT(31) + +#define TBT_SET_ADAPTER(a) (((a) & 1) << 16) + +/* TBT3 Cable Discover Mode VDO bits */ +#define TBT_CABLE_SPEED(_vdo_) (((_vdo_) & GENMASK(18, 16)) >> 16) +#define TBT_CABLE_USB3_GEN1 1 +#define TBT_CABLE_USB3_PASSIVE 2 +#define TBT_CABLE_10_AND_20GBPS 3 +#define TBT_CABLE_ROUNDED BIT(19) +#define TBT_CABLE_OPTICAL BIT(21) +#define TBT_CABLE_RETIMER BIT(22) +#define TBT_CABLE_LINK_TRAINING BIT(23) + +#define TBT_SET_CABLE_SPEED(_s_) (((_s_) & GENMASK(2, 0)) << 16) + +/* TBT3 Device Enter Mode VDO bits */ +#define TBT_ENTER_MODE_CABLE_SPEED(s) TBT_SET_CABLE_SPEED(s) +#define TBT_ENTER_MODE_ACTIVE_CABLE BIT(24) + +#endif /* __USB_TYPEC_TBT_H */ diff --git a/include/linux/usb_usual.h b/include/linux/usb_usual.h index 000a5954b2e8..4a19ac3f24d0 100644 --- a/include/linux/usb_usual.h +++ b/include/linux/usb_usual.h @@ -92,6 +92,6 @@ enum { US_DO_ALL_FLAGS }; #include <linux/usb/storage.h> extern int usb_usual_ignore_device(struct usb_interface *intf); -extern struct usb_device_id usb_storage_usb_ids[]; +extern const struct usb_device_id usb_storage_usb_ids[]; #endif /* __LINUX_USB_USUAL_H */ diff --git a/include/linux/usbdevice_fs.h b/include/linux/usbdevice_fs.h index 79aab0065ec8..14ea197ce37f 100644 --- a/include/linux/usbdevice_fs.h +++ b/include/linux/usbdevice_fs.h @@ -69,7 +69,7 @@ struct usbdevfs_urb32 { compat_int_t error_count; compat_uint_t signr; compat_caddr_t usercontext; /* unused */ - struct usbdevfs_iso_packet_desc iso_frame_desc[0]; + struct usbdevfs_iso_packet_desc iso_frame_desc[]; }; struct usbdevfs_ioctl32 { diff --git a/include/uapi/linux/usb/raw_gadget.h b/include/uapi/linux/usb/raw_gadget.h new file mode 100644 index 000000000000..ea375082b3ac --- /dev/null +++ b/include/uapi/linux/usb/raw_gadget.h @@ -0,0 +1,167 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * USB Raw Gadget driver. + * + * See Documentation/usb/raw-gadget.rst for more details. + */ + +#ifndef _UAPI__LINUX_USB_RAW_GADGET_H +#define _UAPI__LINUX_USB_RAW_GADGET_H + +#include <asm/ioctl.h> +#include <linux/types.h> +#include <linux/usb/ch9.h> + +/* Maximum length of driver_name/device_name in the usb_raw_init struct. */ +#define UDC_NAME_LENGTH_MAX 128 + +/* + * struct usb_raw_init - argument for USB_RAW_IOCTL_INIT ioctl. + * @speed: The speed of the emulated USB device, takes the same values as + * the usb_device_speed enum: USB_SPEED_FULL, USB_SPEED_HIGH, etc. + * @driver_name: The name of the UDC driver. + * @device_name: The name of a UDC instance. + * + * The last two fields identify a UDC the gadget driver should bind to. + * For example, Dummy UDC has "dummy_udc" as its driver_name and "dummy_udc.N" + * as its device_name, where N in the index of the Dummy UDC instance. + * At the same time the dwc2 driver that is used on Raspberry Pi Zero, has + * "20980000.usb" as both driver_name and device_name. + */ +struct usb_raw_init { + __u8 driver_name[UDC_NAME_LENGTH_MAX]; + __u8 device_name[UDC_NAME_LENGTH_MAX]; + __u8 speed; +}; + +/* The type of event fetched with the USB_RAW_IOCTL_EVENT_FETCH ioctl. */ +enum usb_raw_event_type { + USB_RAW_EVENT_INVALID = 0, + + /* This event is queued when the driver has bound to a UDC. */ + USB_RAW_EVENT_CONNECT = 1, + + /* This event is queued when a new control request arrived to ep0. */ + USB_RAW_EVENT_CONTROL = 2, + + /* The list might grow in the future. */ +}; + +/* + * struct usb_raw_event - argument for USB_RAW_IOCTL_EVENT_FETCH ioctl. + * @type: The type of the fetched event. + * @length: Length of the data buffer. Updated by the driver and set to the + * actual length of the fetched event data. + * @data: A buffer to store the fetched event data. + * + * Currently the fetched data buffer is empty for USB_RAW_EVENT_CONNECT, + * and contains struct usb_ctrlrequest for USB_RAW_EVENT_CONTROL. + */ +struct usb_raw_event { + __u32 type; + __u32 length; + __u8 data[0]; +}; + +#define USB_RAW_IO_FLAGS_ZERO 0x0001 +#define USB_RAW_IO_FLAGS_MASK 0x0001 + +static inline int usb_raw_io_flags_valid(__u16 flags) +{ + return (flags & ~USB_RAW_IO_FLAGS_MASK) == 0; +} + +static inline int usb_raw_io_flags_zero(__u16 flags) +{ + return (flags & USB_RAW_IO_FLAGS_ZERO); +} + +/* + * struct usb_raw_ep_io - argument for USB_RAW_IOCTL_EP0/EP_WRITE/READ ioctls. + * @ep: Endpoint handle as returned by USB_RAW_IOCTL_EP_ENABLE for + * USB_RAW_IOCTL_EP_WRITE/READ. Ignored for USB_RAW_IOCTL_EP0_WRITE/READ. + * @flags: When USB_RAW_IO_FLAGS_ZERO is specified, the zero flag is set on + * the submitted USB request, see include/linux/usb/gadget.h for details. + * @length: Length of data. + * @data: Data to send for USB_RAW_IOCTL_EP0/EP_WRITE. Buffer to store received + * data for USB_RAW_IOCTL_EP0/EP_READ. + */ +struct usb_raw_ep_io { + __u16 ep; + __u16 flags; + __u32 length; + __u8 data[0]; +}; + +/* + * Initializes a Raw Gadget instance. + * Accepts a pointer to the usb_raw_init struct as an argument. + * Returns 0 on success or negative error code on failure. + */ +#define USB_RAW_IOCTL_INIT _IOW('U', 0, struct usb_raw_init) + +/* + * Instructs Raw Gadget to bind to a UDC and start emulating a USB device. + * Returns 0 on success or negative error code on failure. + */ +#define USB_RAW_IOCTL_RUN _IO('U', 1) + +/* + * A blocking ioctl that waits for an event and returns fetched event data to + * the user. + * Accepts a pointer to the usb_raw_event struct. + * Returns 0 on success or negative error code on failure. + */ +#define USB_RAW_IOCTL_EVENT_FETCH _IOR('U', 2, struct usb_raw_event) + +/* + * Queues an IN (OUT for READ) urb as a response to the last control request + * received on endpoint 0, provided that was an IN (OUT for READ) request and + * waits until the urb is completed. Copies received data to user for READ. + * Accepts a pointer to the usb_raw_ep_io struct as an argument. + * Returns length of trasferred data on success or negative error code on + * failure. + */ +#define USB_RAW_IOCTL_EP0_WRITE _IOW('U', 3, struct usb_raw_ep_io) +#define USB_RAW_IOCTL_EP0_READ _IOWR('U', 4, struct usb_raw_ep_io) + +/* + * Finds an endpoint that supports the transfer type specified in the + * descriptor and enables it. + * Accepts a pointer to the usb_endpoint_descriptor struct as an argument. + * Returns enabled endpoint handle on success or negative error code on failure. + */ +#define USB_RAW_IOCTL_EP_ENABLE _IOW('U', 5, struct usb_endpoint_descriptor) + +/* Disables specified endpoint. + * Accepts endpoint handle as an argument. + * Returns 0 on success or negative error code on failure. + */ +#define USB_RAW_IOCTL_EP_DISABLE _IOW('U', 6, __u32) + +/* + * Queues an IN (OUT for READ) urb as a response to the last control request + * received on endpoint usb_raw_ep_io.ep, provided that was an IN (OUT for READ) + * request and waits until the urb is completed. Copies received data to user + * for READ. + * Accepts a pointer to the usb_raw_ep_io struct as an argument. + * Returns length of trasferred data on success or negative error code on + * failure. + */ +#define USB_RAW_IOCTL_EP_WRITE _IOW('U', 7, struct usb_raw_ep_io) +#define USB_RAW_IOCTL_EP_READ _IOWR('U', 8, struct usb_raw_ep_io) + +/* + * Switches the gadget into the configured state. + * Returns 0 on success or negative error code on failure. + */ +#define USB_RAW_IOCTL_CONFIGURE _IO('U', 9) + +/* + * Constrains UDC VBUS power usage. + * Accepts current limit in 2 mA units as an argument. + * Returns 0 on success or negative error code on failure. + */ +#define USB_RAW_IOCTL_VBUS_DRAW _IOW('U', 10, __u32) + +#endif /* _UAPI__LINUX_USB_RAW_GADGET_H */ |