From 95aafdb42d1f3aa59f114a7f076949abac53c38d Mon Sep 17 00:00:00 2001 From: Lionel VITTE Date: Mon, 5 Oct 2020 13:19:50 +0200 Subject: [PATCH 16/22] ARM-stm32mp1-r2-rc8-PHY-USB --- .../bindings/connector/usb-connector.txt | 2 + .../bindings/phy/phy-stm32-usbphyc.txt | 60 +- .../devicetree/bindings/usb/dwc2.txt | 8 + .../devicetree/bindings/usb/generic-ehci.yaml | 5 + .../bindings/usb/st,typec-stusb.txt | 48 + drivers/phy/phy-core.c | 48 +- drivers/phy/st/phy-stm32-usbphyc.c | 497 ++++++++-- drivers/usb/dwc2/Kconfig | 1 + drivers/usb/dwc2/Makefile | 2 +- drivers/usb/dwc2/core.c | 123 ++- drivers/usb/dwc2/core.h | 21 + drivers/usb/dwc2/drd.c | 196 ++++ drivers/usb/dwc2/gadget.c | 7 +- drivers/usb/dwc2/hcd.c | 6 +- drivers/usb/dwc2/hw.h | 8 + drivers/usb/dwc2/params.c | 43 + drivers/usb/dwc2/platform.c | 131 ++- drivers/usb/gadget/function/f_acm.c | 16 + drivers/usb/gadget/function/f_serial.c | 16 + drivers/usb/gadget/function/u_serial.c | 53 +- drivers/usb/gadget/function/u_serial.h | 2 + drivers/usb/host/ehci-platform.c | 7 + drivers/usb/renesas_usbhs/rcar2.c | 2 +- drivers/usb/renesas_usbhs/rza2.c | 2 +- drivers/usb/typec/Kconfig | 9 + drivers/usb/typec/Makefile | 1 + drivers/usb/typec/class.c | 15 + drivers/usb/typec/typec_stusb.c | 910 ++++++++++++++++++ include/linux/phy/phy.h | 9 +- include/linux/usb/typec.h | 1 + 30 files changed, 2102 insertions(+), 147 deletions(-) create mode 100644 Documentation/devicetree/bindings/usb/st,typec-stusb.txt create mode 100644 drivers/usb/dwc2/drd.c create mode 100644 drivers/usb/typec/typec_stusb.c diff --git a/Documentation/devicetree/bindings/connector/usb-connector.txt b/Documentation/devicetree/bindings/connector/usb-connector.txt index d357987181ee0..5c0bb0395bc92 100644 --- a/Documentation/devicetree/bindings/connector/usb-connector.txt +++ b/Documentation/devicetree/bindings/connector/usb-connector.txt @@ -34,6 +34,8 @@ Optional properties for usb-b-connector: Optional properties for usb-c-connector: - power-role: should be one of "source", "sink" or "dual"(DRP) if typec connector has power support. +- power-opmode: should be one of "default", "1.5A", "3.0A" or + "usb_power_delivery" if typec connector has power support. - try-power-role: preferred power role if "dual"(DRP) can support Try.SNK or Try.SRC, should be "sink" for Try.SNK or "source" for Try.SRC. - data-role: should be one of "host", "device", "dual"(DRD) if typec diff --git a/Documentation/devicetree/bindings/phy/phy-stm32-usbphyc.txt b/Documentation/devicetree/bindings/phy/phy-stm32-usbphyc.txt index 725ae71ae6535..3953489e3a6bb 100644 --- a/Documentation/devicetree/bindings/phy/phy-stm32-usbphyc.txt +++ b/Documentation/devicetree/bindings/phy/phy-stm32-usbphyc.txt @@ -23,8 +23,11 @@ Required properties: - compatible: must be "st,stm32mp1-usbphyc" - reg: address and length of the usb phy control register set - clocks: phandle + clock specifier for the PLL phy clock +- vdda1v1-supply: phandle to the regulator providing 1V1 power to the PHY +- vdda1v8-supply: phandle to the regulator providing 1V8 power to the PHY - #address-cells: number of address cells for phys sub-nodes, must be <1> - #size-cells: number of size cells for phys sub-nodes, must be <0> +- #clock-cells: number of clock cells for ck_usbo_48m consumer, must be <0> Optional properties: - assigned-clocks: phandle + clock specifier for the PLL phy clock @@ -34,40 +37,83 @@ Optional properties: Required nodes: one sub-node per port the controller provides. Phy sub-nodes -============== +============= Required properties: - reg: phy port index - phy-supply: phandle to the regulator providing 3V3 power to the PHY, see phy-bindings.txt in the same directory. -- vdda1v1-supply: phandle to the regulator providing 1V1 power to the PHY -- vdda1v8-supply: phandle to the regulator providing 1V8 power to the PHY - #phy-cells: see phy-bindings.txt in the same directory, must be <0> for PHY port#1 and must be <1> for PHY port#2, to select USB controller +Optional properties: +- st,phy-tuning : phandle to the usb phy tuning node, see Phy tuning node below +- vbus-supply: phandle to the regulator providing 5V vbus to the USB connector + +Phy tuning node +=============== + +It may be necessary to adjust the phy settings to compensate parasitics, which +can be due to USB connector/receptacle, routing, ESD protection component, ... + +Here is the list of all optional parameters to tune the interface of the phy +(HS for High-Speed, FS for Full-Speed, LS for Low-Speed) + +Optional properties: +- st,current-boost: <1> current boosting of 1mA + <2> current boosting of 2mA +- st,no-lsfs-fb-cap: disables the LS/FS feedback capacitor +- st,hs-slew-ctrl: slows the HS driver slew rate by 10% +- st,hs-dc-level: <0> decreases the HS driver DC level by 5 to 7mV + <1> increases the HS driver DC level by 5 to 7mV + <2> increases the HS driver DC level by 10 to 14mV +- st,fs-rftime-tuning: enables the FS rise/fall tuning option +- st,hs-rftime-reduction: enables the HS rise/fall reduction feature +- st,hs-current-trim: controls HS driver current trimming for choke +- st,hs-impedance-trim: controls HS driver impedance tuning for choke +- st,squelch-level: adjusts the squelch DC threshold value +- st,hs-rx-gain-eq: enables the HS Rx gain equalizer +- st,hs-rx-offset: adjusts the HS Rx offset +- st,no-hs-ftime-ctrl: disables the HS fall time control of single + ended signals during pre-emphasis +- st,no-lsfs-sc: disables the short circuit protection in LS/FS driver +- st,hs-tx-staggering: enables the basic staggering in HS Tx mode + Example: + usb_phy_tuning: usb-phy-tuning { + st,current-boost = <2>; + st,no-lfs-fb-cap; + st,hs-dc-level = <2>; + st,hs-rftime-reduction; + st,hs-current-trim = <5>; + st,hs-impedance-trim = <0>; + st,squelch-level = <1>; + st,no-hs-ftime-ctrl; + st,hs-tx-staggering; + }; + usbphyc: usb-phy@5a006000 { compatible = "st,stm32mp1-usbphyc"; reg = <0x5a006000 0x1000>; clocks = <&rcc_clk USBPHY_K>; resets = <&rcc_rst USBPHY_R>; + vdda1v1-supply = <®11>; + vdda1v8-supply = <®18>; #address-cells = <1>; #size-cells = <0>; + #clock-cells = <0>; usbphyc_port0: usb-phy@0 { reg = <0>; phy-supply = <&vdd_usb>; - vdda1v1-supply = <®11>; - vdda1v8-supply = <®18> #phy-cells = <0>; }; usbphyc_port1: usb-phy@1 { reg = <1>; phy-supply = <&vdd_usb>; - vdda1v1-supply = <®11>; - vdda1v8-supply = <®18> #phy-cells = <1>; + st,phy-tuning = <&usb_phy_tuning>; }; }; diff --git a/Documentation/devicetree/bindings/usb/dwc2.txt b/Documentation/devicetree/bindings/usb/dwc2.txt index aafff3a6904d0..1a65303d0ca5c 100644 --- a/Documentation/devicetree/bindings/usb/dwc2.txt +++ b/Documentation/devicetree/bindings/usb/dwc2.txt @@ -23,6 +23,9 @@ Required properties: configured in HS mode; - "st,stm32f7-hsotg": The DWC2 USB HS controller instance in STM32F7 SoCs configured in HS mode; + - "st,stm32mp1-fsotg": The DWC2 USB controller instance in STM32MP1 SoCs, + configured in FS mode (using dedicated FS transceiver). + - "st,stm32mp1-hsotg": The DWC2 USB controller instance in STM32MP1 SoCs; - reg : Should contain 1 register range (address and length) - interrupts : Should contain 1 interrupt - clocks: clock provider specifier @@ -46,6 +49,11 @@ Refer to phy/phy-bindings.txt for generic phy consumer properties on for remote wakeup during suspend. - snps,reset-phy-on-wake: If present indicates that we need to reset the PHY when we detect a wakeup. This is due to a hardware errata. +- usb33d-supply: external VBUS and ID sensing comparators supply, in order to + perform OTG operation, used on STM32MP1 SoCs. +- usb-role-switch: use USB Role Switch to support dynamic dual role switch. + Refer to usb/generic.txt +- wakeup-source: bool flag to indicate this device has wakeup capabilities Deprecated properties: - g-use-dma: gadget DMA mode is automatically detected diff --git a/Documentation/devicetree/bindings/usb/generic-ehci.yaml b/Documentation/devicetree/bindings/usb/generic-ehci.yaml index 1ca64c85191aa..b71c15dc8c11f 100644 --- a/Documentation/devicetree/bindings/usb/generic-ehci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-ehci.yaml @@ -69,6 +69,11 @@ properties: phy-names: const: usb + wakeup-source: + $ref: /schemas/types.yaml#/definitions/flag + description: + Indicate this device has wakeup capabilities. + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/usb/st,typec-stusb.txt b/Documentation/devicetree/bindings/usb/st,typec-stusb.txt new file mode 100644 index 0000000000000..415e14e1409ea --- /dev/null +++ b/Documentation/devicetree/bindings/usb/st,typec-stusb.txt @@ -0,0 +1,48 @@ +STMicroelectronics STUSB Type-C Controller family + +Required properties: + - compatible: should be "st,stusb1600". + - reg: I2C slave address of the device. + +Optional properties: + - vdd-supply: main power supply [4.1V;22V]. + - vsys-supply: low power supply [3.0V;5.5V]. + - vconn-supply: power supply [2.7;5.5V] used to supply VConn on CC pin in + source or dual power role. + - interrupts: interrupt specifier triggered by ALERT# signal. + Please refer to ../interrupt-controller/interrupt.txt + - pinctrl state named "default" may be defined to configure pin for #ALERT + signal + +USB-C connector attached to STUSB Type-C port controller can be described in +an optional connector sub-node. Refer to ../connector/usb-connector.txt. +In case role switch can be used, an optional port sub-node can be added. Refer +to ../graph.txt. + +Example : + + typec: stusb1600@28 { + compatible = "st,stusb1600"; + reg = <0x28>; + vdd-supply = <&vbus_drd>; + vsys-supply = <&vdd_usb>; + interrupts = <11 IRQ_TYPE_EDGE_FALLING>; + interrupt-parent = <&gpioi>; + pinctrl-names = "default"; + pinctrl-0 = <&stusb1600_pins_a>; + + usb_con: connector { + compatible = "usb-c-connector"; + label = "USB-C"; + power-role = "dual"; + power-opmode = "1.5A"; + data-role = "dual"; + + port { + con_usbotg_hs_ep: endpoint { + remote-endpoint = <&usbotg_hs_ep>; + }; + }; + }; + }; + diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c index b04f4fe85ac2d..8dfb4868c8c3d 100644 --- a/drivers/phy/phy-core.c +++ b/drivers/phy/phy-core.c @@ -29,7 +29,7 @@ static void devm_phy_release(struct device *dev, void *res) { struct phy *phy = *(struct phy **)res; - phy_put(phy); + phy_put(dev, phy); } static void devm_phy_provider_release(struct device *dev, void *res) @@ -566,12 +566,12 @@ struct phy *of_phy_get(struct device_node *np, const char *con_id) EXPORT_SYMBOL_GPL(of_phy_get); /** - * phy_put() - release the PHY - * @phy: the phy returned by phy_get() + * of_phy_put() - release the PHY + * @phy: the phy returned by of_phy_get() * - * Releases a refcount the caller received from phy_get(). + * Releases a refcount the caller received from of_phy_get(). */ -void phy_put(struct phy *phy) +void of_phy_put(struct phy *phy) { if (!phy || IS_ERR(phy)) return; @@ -584,6 +584,20 @@ void phy_put(struct phy *phy) module_put(phy->ops->owner); put_device(&phy->dev); } +EXPORT_SYMBOL_GPL(of_phy_put); + +/** + * phy_put() - release the PHY + * @dev: device that wants to release this phy + * @phy: the phy returned by phy_get() + * + * Releases a refcount the caller received from phy_get(). + */ +void phy_put(struct device *dev, struct phy *phy) +{ + device_link_remove(dev, &phy->dev); + of_phy_put(phy); +} EXPORT_SYMBOL_GPL(phy_put); /** @@ -651,6 +665,7 @@ struct phy *phy_get(struct device *dev, const char *string) { int index = 0; struct phy *phy; + struct device_link *link; if (string == NULL) { dev_WARN(dev, "missing string\n"); @@ -672,6 +687,13 @@ struct phy *phy_get(struct device *dev, const char *string) get_device(&phy->dev); + link = device_link_add(dev, &phy->dev, DL_FLAG_STATELESS); + if (!link) { + dev_err(dev, "failed to create device link to %s\n", + dev_name(phy->dev.parent)); + return ERR_PTR(-EINVAL); + } + return phy; } EXPORT_SYMBOL_GPL(phy_get); @@ -765,6 +787,7 @@ struct phy *devm_of_phy_get(struct device *dev, struct device_node *np, const char *con_id) { struct phy **ptr, *phy; + struct device_link *link; ptr = devres_alloc(devm_phy_release, sizeof(*ptr), GFP_KERNEL); if (!ptr) @@ -778,6 +801,13 @@ struct phy *devm_of_phy_get(struct device *dev, struct device_node *np, devres_free(ptr); } + link = device_link_add(dev, &phy->dev, DL_FLAG_STATELESS); + if (!link) { + dev_err(dev, "failed to create device link to %s\n", + dev_name(phy->dev.parent)); + return ERR_PTR(-EINVAL); + } + return phy; } EXPORT_SYMBOL_GPL(devm_of_phy_get); @@ -798,6 +828,7 @@ struct phy *devm_of_phy_get_by_index(struct device *dev, struct device_node *np, int index) { struct phy **ptr, *phy; + struct device_link *link; ptr = devres_alloc(devm_phy_release, sizeof(*ptr), GFP_KERNEL); if (!ptr) @@ -819,6 +850,13 @@ struct phy *devm_of_phy_get_by_index(struct device *dev, struct device_node *np, *ptr = phy; devres_add(dev, ptr); + link = device_link_add(dev, &phy->dev, DL_FLAG_STATELESS); + if (!link) { + dev_err(dev, "failed to create device link to %s\n", + dev_name(phy->dev.parent)); + return ERR_PTR(-EINVAL); + } + return phy; } EXPORT_SYMBOL_GPL(devm_of_phy_get_by_index); diff --git a/drivers/phy/st/phy-stm32-usbphyc.c b/drivers/phy/st/phy-stm32-usbphyc.c index 56bdea4b0bd90..0eb699344c071 100644 --- a/drivers/phy/st/phy-stm32-usbphyc.c +++ b/drivers/phy/st/phy-stm32-usbphyc.c @@ -7,8 +7,9 @@ */ #include #include +#include #include -#include +#include #include #include #include @@ -17,6 +18,8 @@ #define STM32_USBPHYC_PLL 0x0 #define STM32_USBPHYC_MISC 0x8 +#define STM32_USBPHYC_MONITOR(X) (0x108 + (X * 0x100)) +#define STM32_USBPHYC_TUNE(X) (0x10C + (X * 0x100)) #define STM32_USBPHYC_VERSION 0x3F4 /* STM32_USBPHYC_PLL bit fields */ @@ -32,19 +35,93 @@ /* STM32_USBPHYC_MISC bit fields */ #define SWITHOST BIT(0) -/* STM32_USBPHYC_VERSION bit fields */ -#define MINREV GENMASK(3, 0) -#define MAJREV GENMASK(7, 4) +/* STM32_USBPHYC_MONITOR bit fields */ +#define STM32_USBPHYC_MON_OUT GENMASK(3, 0) +#define STM32_USBPHYC_MON_SEL GENMASK(8, 4) +#define STM32_USBPHYC_MON_SEL_LOCKP 0x1F +#define STM32_USBPHYC_MON_OUT_LOCKP BIT(3) + +/* STM32_USBPHYC_TUNE bit fields */ +#define INCURREN BIT(0) +#define INCURRINT BIT(1) +#define LFSCAPEN BIT(2) +#define HSDRVSLEW BIT(3) +#define HSDRVDCCUR BIT(4) +#define HSDRVDCLEV BIT(5) +#define HSDRVCURINCR BIT(6) +#define FSDRVRFADJ BIT(7) +#define HSDRVRFRED BIT(8) +#define HSDRVCHKITRM GENMASK(12, 9) +#define HSDRVCHKZTRM GENMASK(14, 13) +#define OTPCOMP GENMASK(19, 15) +#define SQLCHCTL GENMASK(21, 20) +#define HDRXGNEQEN BIT(22) +#define HSRXOFF GENMASK(24, 23) +#define HSFALLPREEM BIT(25) +#define SHTCCTCTLPROT BIT(26) +#define STAGSEL BIT(27) + +enum boosting_vals { + BOOST_1_MA = 1, + BOOST_2_MA, + BOOST_MAX, +}; + +enum dc_level_vals { + DC_MINUS_5_TO_7_MV, + DC_PLUS_5_TO_7_MV, + DC_PLUS_10_TO_14_MV, + DC_MAX, +}; + +enum current_trim { + CUR_NOMINAL, + CUR_PLUS_1_56_PCT, + CUR_PLUS_3_12_PCT, + CUR_PLUS_4_68_PCT, + CUR_PLUS_6_24_PCT, + CUR_PLUS_7_8_PCT, + CUR_PLUS_9_36_PCT, + CUR_PLUS_10_92_PCT, + CUR_PLUS_12_48_PCT, + CUR_PLUS_14_04_PCT, + CUR_PLUS_15_6_PCT, + CUR_PLUS_17_16_PCT, + CUR_PLUS_19_01_PCT, + CUR_PLUS_20_58_PCT, + CUR_PLUS_22_16_PCT, + CUR_PLUS_23_73_PCT, + CUR_MAX, +}; + +enum impedance_trim { + IMP_NOMINAL, + IMP_MINUS_2_OHMS, + IMP_MINUS_4_OMHS, + IMP_MINUS_6_OHMS, + IMP_MAX, +}; + +enum squelch_level { + SQLCH_NOMINAL, + SQLCH_PLUS_7_MV, + SQLCH_MINUS_5_MV, + SQLCH_PLUS_14_MV, + SQLCH_MAX, +}; -static const char * const supplies_names[] = { - "vdda1v1", /* 1V1 */ - "vdda1v8", /* 1V8 */ +enum rx_offset { + NO_RX_OFFSET, + RX_OFFSET_PLUS_5_MV, + RX_OFFSET_PLUS_10_MV, + RX_OFFSET_MINUS_5_MV, + RX_OFFSET_MAX, }; -#define NUM_SUPPLIES ARRAY_SIZE(supplies_names) +/* STM32_USBPHYC_VERSION bit fields */ +#define MINREV GENMASK(3, 0) +#define MAJREV GENMASK(7, 4) -#define PLL_LOCK_TIME_US 100 -#define PLL_PWR_DOWN_TIME_US 5 #define PLL_FVCO_MHZ 2880 #define PLL_INFF_MIN_RATE_HZ 19200000 #define PLL_INFF_MAX_RATE_HZ 38400000 @@ -58,7 +135,7 @@ struct pll_params { struct stm32_usbphyc_phy { struct phy *phy; struct stm32_usbphyc *usbphyc; - struct regulator_bulk_data supplies[NUM_SUPPLIES]; + struct regulator *vbus; u32 index; bool active; }; @@ -70,6 +147,10 @@ struct stm32_usbphyc { struct reset_control *rst; struct stm32_usbphyc_phy **phys; int nphys; + struct regulator *vdda1v1; + struct regulator *vdda1v8; + atomic_t n_pll_cons; + struct clk_hw clk48_hw; int switch_setup; }; @@ -83,6 +164,41 @@ static inline void stm32_usbphyc_clr_bits(void __iomem *reg, u32 bits) writel_relaxed(readl_relaxed(reg) & ~bits, reg); } +static int stm32_usbphyc_regulators_enable(struct stm32_usbphyc *usbphyc) +{ + int ret; + + ret = regulator_enable(usbphyc->vdda1v1); + if (ret) + return ret; + + ret = regulator_enable(usbphyc->vdda1v8); + if (ret) + goto vdda1v1_disable; + + return 0; + +vdda1v1_disable: + regulator_disable(usbphyc->vdda1v1); + + return ret; +} + +static int stm32_usbphyc_regulators_disable(struct stm32_usbphyc *usbphyc) +{ + int ret; + + ret = regulator_disable(usbphyc->vdda1v8); + if (ret) + return ret; + + ret = regulator_disable(usbphyc->vdda1v1); + if (ret) + return ret; + + return 0; +} + static void stm32_usbphyc_get_pll_params(u32 clk_rate, struct pll_params *pll_params) { @@ -142,83 +258,106 @@ static int stm32_usbphyc_pll_init(struct stm32_usbphyc *usbphyc) return 0; } -static bool stm32_usbphyc_has_one_phy_active(struct stm32_usbphyc *usbphyc) +static int __stm32_usbphyc_pll_disable(struct stm32_usbphyc *usbphyc) { - int i; + void __iomem *pll_reg = usbphyc->base + STM32_USBPHYC_PLL; + u32 pllen; - for (i = 0; i < usbphyc->nphys; i++) - if (usbphyc->phys[i]->active) - return true; + stm32_usbphyc_clr_bits(pll_reg, PLLEN); - return false; + /* Wait for minimum width of powerdown pulse (ENABLE = Low) */ + if (readl_relaxed_poll_timeout(pll_reg, pllen, !(pllen & PLLEN), 5, 50)) + dev_err(usbphyc->dev, "PLL not reset\n"); + + return stm32_usbphyc_regulators_disable(usbphyc); +} + +static int stm32_usbphyc_pll_disable(struct stm32_usbphyc *usbphyc) +{ + /* Check if a phy port is still active or clk48 in use */ + if (atomic_dec_return(&usbphyc->n_pll_cons) > 0) + return 0; + + return __stm32_usbphyc_pll_disable(usbphyc); } static int stm32_usbphyc_pll_enable(struct stm32_usbphyc *usbphyc) { void __iomem *pll_reg = usbphyc->base + STM32_USBPHYC_PLL; - bool pllen = (readl_relaxed(pll_reg) & PLLEN); + bool pllen = readl_relaxed(pll_reg) & PLLEN; int ret; - /* Check if one phy port has already configured the pll */ - if (pllen && stm32_usbphyc_has_one_phy_active(usbphyc)) + /* + * Check if a phy port or clk48 prepare has configured the pll + * and ensure the PLL is enabled + */ + if(atomic_inc_return(&usbphyc->n_pll_cons) > 1 && pllen) return 0; if (pllen) { - stm32_usbphyc_clr_bits(pll_reg, PLLEN); - /* Wait for minimum width of powerdown pulse (ENABLE = Low) */ - udelay(PLL_PWR_DOWN_TIME_US); + /* + * PLL shouldn't be enabled without known consumer, + * disable it and reinit n_pll_cons + */ + dev_warn(usbphyc->dev, "PLL enabled without known consumers\n"); + + ret = __stm32_usbphyc_pll_disable(usbphyc); + if (ret) + return ret; } + ret = stm32_usbphyc_regulators_enable(usbphyc); + if (ret) + goto dec_n_pll_cons; + ret = stm32_usbphyc_pll_init(usbphyc); if (ret) - return ret; + goto reg_disable; stm32_usbphyc_set_bits(pll_reg, PLLEN); - /* Wait for maximum lock time */ - udelay(PLL_LOCK_TIME_US); - - if (!(readl_relaxed(pll_reg) & PLLEN)) { - dev_err(usbphyc->dev, "PLLEN not set\n"); - return -EIO; - } - return 0; -} - -static int stm32_usbphyc_pll_disable(struct stm32_usbphyc *usbphyc) -{ - void __iomem *pll_reg = usbphyc->base + STM32_USBPHYC_PLL; - /* Check if other phy port active */ - if (stm32_usbphyc_has_one_phy_active(usbphyc)) - return 0; - - stm32_usbphyc_clr_bits(pll_reg, PLLEN); - /* Wait for minimum width of powerdown pulse (ENABLE = Low) */ - udelay(PLL_PWR_DOWN_TIME_US); +reg_disable: + stm32_usbphyc_regulators_disable(usbphyc); - if (readl_relaxed(pll_reg) & PLLEN) { - dev_err(usbphyc->dev, "PLL not reset\n"); - return -EIO; - } +dec_n_pll_cons: + atomic_dec(&usbphyc->n_pll_cons); - return 0; + return ret; } static int stm32_usbphyc_phy_init(struct phy *phy) { struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy); struct stm32_usbphyc *usbphyc = usbphyc_phy->usbphyc; + u32 reg_mon = STM32_USBPHYC_MONITOR(usbphyc_phy->index); + u32 monsel = FIELD_PREP(STM32_USBPHYC_MON_SEL, + STM32_USBPHYC_MON_SEL_LOCKP); + u32 monout; int ret; ret = stm32_usbphyc_pll_enable(usbphyc); if (ret) return ret; + /* Check that PLL Lock input to PHY is High */ + writel_relaxed(monsel, usbphyc->base + reg_mon); + ret = readl_relaxed_poll_timeout(usbphyc->base + reg_mon, monout, + monout & STM32_USBPHYC_MON_OUT_LOCKP, + 100, 1000); + if (ret) { + dev_err(usbphyc->dev, "PLL Lock input to PHY is Low (val=%x)\n", + (u32)(monout & STM32_USBPHYC_MON_OUT)); + goto pll_disable; + } + usbphyc_phy->active = true; return 0; + +pll_disable: + return stm32_usbphyc_pll_disable(usbphyc); } static int stm32_usbphyc_phy_exit(struct phy *phy) @@ -235,14 +374,20 @@ static int stm32_usbphyc_phy_power_on(struct phy *phy) { struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy); - return regulator_bulk_enable(NUM_SUPPLIES, usbphyc_phy->supplies); + if (usbphyc_phy->vbus) + return regulator_enable(usbphyc_phy->vbus); + + return 0; } static int stm32_usbphyc_phy_power_off(struct phy *phy) { struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy); - return regulator_bulk_disable(NUM_SUPPLIES, usbphyc_phy->supplies); + if (usbphyc_phy->vbus) + return regulator_disable(usbphyc_phy->vbus); + + return 0; } static const struct phy_ops stm32_usbphyc_phy_ops = { @@ -253,6 +398,163 @@ static const struct phy_ops stm32_usbphyc_phy_ops = { .owner = THIS_MODULE, }; +static int stm32_usbphyc_clk48_prepare(struct clk_hw *hw) +{ + struct stm32_usbphyc *usbphyc = container_of(hw, struct stm32_usbphyc, + clk48_hw); + + return stm32_usbphyc_pll_enable(usbphyc); +} + +static void stm32_usbphyc_clk48_unprepare(struct clk_hw *hw) +{ + struct stm32_usbphyc *usbphyc = container_of(hw, struct stm32_usbphyc, + clk48_hw); + + stm32_usbphyc_pll_disable(usbphyc); +} + +static unsigned long stm32_usbphyc_clk48_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return 48000000; +} + +static const struct clk_ops usbphyc_clk48_ops = { + .prepare = stm32_usbphyc_clk48_prepare, + .unprepare = stm32_usbphyc_clk48_unprepare, + .recalc_rate = stm32_usbphyc_clk48_recalc_rate, +}; + +static void stm32_usbphyc_clk48_unregister(void *data) +{ + struct stm32_usbphyc *usbphyc = data; + + of_clk_del_provider(usbphyc->dev->of_node); + clk_hw_unregister(&usbphyc->clk48_hw); +} + +static int stm32_usbphyc_clk48_register(struct stm32_usbphyc *usbphyc) +{ + struct device_node *node = usbphyc->dev->of_node; + struct clk_init_data init = { }; + int ret = 0; + + init.name = "ck_usbo_48m"; + init.ops = &usbphyc_clk48_ops; + init.flags = CLK_IGNORE_UNUSED | CLK_IS_CRITICAL; + + usbphyc->clk48_hw.init = &init; + + ret = clk_hw_register(usbphyc->dev, &usbphyc->clk48_hw); + if (ret) + return ret; + + ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, + &usbphyc->clk48_hw); + if (ret) + clk_hw_unregister(&usbphyc->clk48_hw); + + return ret; +} + +static void stm32_usbphyc_phy_tuning(struct stm32_usbphyc *usbphyc, + struct device_node *np, u32 index) +{ + struct device_node *tune_np; + u32 reg = STM32_USBPHYC_TUNE(index); + u32 otpcomp, val, tune = 0; + int ret; + + tune_np = of_parse_phandle(np, "st,phy-tuning", 0); + if (!tune_np) + return; + + /* Backup OTP compensation code */ + otpcomp = FIELD_GET(OTPCOMP, readl_relaxed(usbphyc->base + reg)); + + ret = of_property_read_u32(tune_np, "st,current-boost", &val); + if (!ret && val < BOOST_MAX) { + val = (val == BOOST_2_MA) ? 1 : 0; + tune |= INCURREN | FIELD_PREP(INCURRINT, val); + } else if (ret != -EINVAL) { + dev_warn(usbphyc->dev, + "phy%d: invalid st,current-boost value\n", index); + } + + if (!of_property_read_bool(tune_np, "st,no-lsfs-fb-cap")) + tune |= LFSCAPEN; + + if (of_property_read_bool(tune_np, "st,hs-slew-ctrl")) + tune |= HSDRVSLEW; + + ret = of_property_read_u32(tune_np, "st,hs-dc-level", &val); + if (!ret && val < DC_MAX) { + if (val == DC_MINUS_5_TO_7_MV) { + tune |= HSDRVDCCUR; + } else { + val = (val == DC_PLUS_10_TO_14_MV) ? 1 : 0; + tune |= HSDRVCURINCR | FIELD_PREP(HSDRVDCLEV, val); + } + } else if (ret != -EINVAL) { + dev_warn(usbphyc->dev, + "phy%d: invalid st,hs-dc-level value\n", index); + } + + if (of_property_read_bool(tune_np, "st,fs-rftime-tuning")) + tune |= FSDRVRFADJ; + + if (of_property_read_bool(tune_np, "st,hs-rftime-reduction")) + tune |= HSDRVRFRED; + + ret = of_property_read_u32(tune_np, "st,hs-current-trim", &val); + if (!ret && val < CUR_MAX) + tune |= FIELD_PREP(HSDRVCHKITRM, val); + else if (ret != -EINVAL) + dev_warn(usbphyc->dev, + "phy%d: invalid st,hs-current-trim value\n", index); + + ret = of_property_read_u32(tune_np, "st,hs-impedance-trim", &val); + if (!ret && val < IMP_MAX) + tune |= FIELD_PREP(HSDRVCHKZTRM, val); + else if (ret != -EINVAL) + dev_warn(usbphyc->dev, + "phy%d: invalid hs-impedance-trim value\n", index); + + ret = of_property_read_u32(tune_np, "st,squelch-level", &val); + if (!ret && val < SQLCH_MAX) + tune |= FIELD_PREP(SQLCHCTL, val); + else if (ret != -EINVAL) + dev_warn(usbphyc->dev, + "phy%d: invalid st,squelch-level value\n", index); + + if (of_property_read_bool(tune_np, "st,hs-rx-gain-eq")) + tune |= HDRXGNEQEN; + + ret = of_property_read_u32(tune_np, "st,hs-rx-offset", &val); + if (!ret && val < RX_OFFSET_MAX) + tune |= FIELD_PREP(HSRXOFF, val); + else if (ret != -EINVAL) + dev_warn(usbphyc->dev, + "phy%d: invalid st,hs-rx-offset value\n", index); + + if (of_property_read_bool(tune_np, "st,no-hs-ftime-ctrl")) + tune |= HSFALLPREEM; + + if (!of_property_read_bool(tune_np, "st,no-lsfs-sc")) + tune |= SHTCCTCTLPROT; + + if (of_property_read_bool(tune_np, "st,hs-tx-staggering")) + tune |= STAGSEL; + + of_node_put(tune_np); + + /* Restore OTP compensation code */ + tune |= FIELD_PREP(OTPCOMP, otpcomp); + + writel_relaxed(tune, usbphyc->base + reg); +} + static void stm32_usbphyc_switch_setup(struct stm32_usbphyc *usbphyc, u32 utmi_switch) { @@ -313,7 +615,7 @@ static int stm32_usbphyc_probe(struct platform_device *pdev) struct device_node *child, *np = dev->of_node; struct resource *res; struct phy_provider *phy_provider; - u32 version; + u32 pllen, version; int ret, port = 0; usbphyc = devm_kzalloc(dev, sizeof(*usbphyc), GFP_KERNEL); @@ -330,7 +632,8 @@ static int stm32_usbphyc_probe(struct platform_device *pdev) usbphyc->clk = devm_clk_get(dev, 0); if (IS_ERR(usbphyc->clk)) { ret = PTR_ERR(usbphyc->clk); - dev_err(dev, "clk get failed: %d\n", ret); + if (ret != -EPROBE_DEFER) + dev_err(dev, "clk get failed: %d\n", ret); return ret; } @@ -345,6 +648,24 @@ static int stm32_usbphyc_probe(struct platform_device *pdev) reset_control_assert(usbphyc->rst); udelay(2); reset_control_deassert(usbphyc->rst); + } else { + ret = PTR_ERR(usbphyc->rst); + if (ret == -EPROBE_DEFER) + goto clk_disable; + + stm32_usbphyc_clr_bits(usbphyc->base + STM32_USBPHYC_PLL, + PLLEN); + } + + /* + * Wait for minimum width of powerdown pulse (ENABLE = Low): + * we have to ensure the PLL is disabled before phys initialization. + */ + if (readl_relaxed_poll_timeout(usbphyc->base + STM32_USBPHYC_PLL, + pllen, !(pllen & PLLEN), 5, 50)) { + dev_warn(usbphyc->dev, "PLL not reset\n"); + ret = -EPROBE_DEFER; + goto clk_disable; } usbphyc->switch_setup = -EINVAL; @@ -356,11 +677,26 @@ static int stm32_usbphyc_probe(struct platform_device *pdev) goto clk_disable; } + usbphyc->vdda1v1 = devm_regulator_get(dev, "vdda1v1"); + if (IS_ERR(usbphyc->vdda1v1)) { + ret = PTR_ERR(usbphyc->vdda1v1); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get vdda1v1 supply: %d\n", ret); + goto clk_disable; + } + + usbphyc->vdda1v8 = devm_regulator_get(dev, "vdda1v8"); + if (IS_ERR(usbphyc->vdda1v8)) { + ret = PTR_ERR(usbphyc->vdda1v8); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get vdda1v8 supply: %d\n", ret); + goto clk_disable; + } + for_each_child_of_node(np, child) { struct stm32_usbphyc_phy *usbphyc_phy; struct phy *phy; u32 index; - int i; phy = devm_phy_create(dev, child, &stm32_usbphyc_phy_ops); if (IS_ERR(phy)) { @@ -378,24 +714,15 @@ static int stm32_usbphyc_probe(struct platform_device *pdev) goto put_child; } - for (i = 0; i < NUM_SUPPLIES; i++) - usbphyc_phy->supplies[i].supply = supplies_names[i]; - - ret = devm_regulator_bulk_get(&phy->dev, NUM_SUPPLIES, - usbphyc_phy->supplies); - if (ret) { - if (ret != -EPROBE_DEFER) - dev_err(&phy->dev, - "failed to get regulators: %d\n", ret); - goto put_child; - } - ret = of_property_read_u32(child, "reg", &index); if (ret || index > usbphyc->nphys) { dev_err(&phy->dev, "invalid reg property: %d\n", ret); goto put_child; } + /* Configure phy tuning */ + stm32_usbphyc_phy_tuning(usbphyc, child, index); + usbphyc->phys[port] = usbphyc_phy; phy_set_bus_width(phy, 8); phy_set_drvdata(phy, usbphyc_phy); @@ -405,6 +732,14 @@ static int stm32_usbphyc_probe(struct platform_device *pdev) usbphyc->phys[port]->index = index; usbphyc->phys[port]->active = false; + usbphyc->phys[port]->vbus = devm_regulator_get_optional(&phy->dev, "vbus"); + if (IS_ERR(usbphyc->phys[port]->vbus)) { + ret = PTR_ERR(usbphyc->phys[port]->vbus); + if (ret == -EPROBE_DEFER) + goto put_child; + usbphyc->phys[port]->vbus = NULL; + } + port++; } @@ -416,6 +751,13 @@ static int stm32_usbphyc_probe(struct platform_device *pdev) goto clk_disable; } + ret = stm32_usbphyc_clk48_register(usbphyc); + if (ret) { + dev_err(dev, + "failed to register ck_usbo_48m clock: %d\n", ret); + goto clk_disable; + } + version = readl_relaxed(usbphyc->base + STM32_USBPHYC_VERSION); dev_info(dev, "registered rev:%lu.%lu\n", FIELD_GET(MAJREV, version), FIELD_GET(MINREV, version)); @@ -433,12 +775,34 @@ static int stm32_usbphyc_probe(struct platform_device *pdev) static int stm32_usbphyc_remove(struct platform_device *pdev) { struct stm32_usbphyc *usbphyc = dev_get_drvdata(&pdev->dev); + int port; + + stm32_usbphyc_clk48_unregister(usbphyc); + + /* Ensure PHYs are not active, to allow PLL disabling */ + for (port = 0; port < usbphyc->nphys; port++) + if (usbphyc->phys[port]->active) + stm32_usbphyc_phy_exit(usbphyc->phys[port]->phy); clk_disable_unprepare(usbphyc->clk); return 0; } +#ifdef CONFIG_PM_SLEEP +static int stm32_usbphyc_resume(struct device *dev) +{ + struct stm32_usbphyc *usbphyc = dev_get_drvdata(dev); + + if (usbphyc->switch_setup >= 0) + stm32_usbphyc_switch_setup(usbphyc, usbphyc->switch_setup); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(stm32_usbphyc_pm_ops, NULL, stm32_usbphyc_resume); + static const struct of_device_id stm32_usbphyc_of_match[] = { { .compatible = "st,stm32mp1-usbphyc", }, { }, @@ -451,6 +815,7 @@ static struct platform_driver stm32_usbphyc_driver = { .driver = { .of_match_table = stm32_usbphyc_of_match, .name = "stm32-usbphyc", + .pm = &stm32_usbphyc_pm_ops, } }; module_platform_driver(stm32_usbphyc_driver); diff --git a/drivers/usb/dwc2/Kconfig b/drivers/usb/dwc2/Kconfig index 16e1aa304edc4..c131719367eca 100644 --- a/drivers/usb/dwc2/Kconfig +++ b/drivers/usb/dwc2/Kconfig @@ -5,6 +5,7 @@ config USB_DWC2 depends on HAS_DMA depends on USB || USB_GADGET depends on HAS_IOMEM + select USB_ROLE_SWITCH help Say Y here if your system has a Dual Role Hi-Speed USB controller based on the DesignWare HSOTG IP Core. diff --git a/drivers/usb/dwc2/Makefile b/drivers/usb/dwc2/Makefile index 440320cc20a47..2bcd6945df461 100644 --- a/drivers/usb/dwc2/Makefile +++ b/drivers/usb/dwc2/Makefile @@ -3,7 +3,7 @@ ccflags-$(CONFIG_USB_DWC2_DEBUG) += -DDEBUG ccflags-$(CONFIG_USB_DWC2_VERBOSE) += -DVERBOSE_DEBUG obj-$(CONFIG_USB_DWC2) += dwc2.o -dwc2-y := core.o core_intr.o platform.o +dwc2-y := core.o core_intr.o platform.o drd.o dwc2-y += params.o ifneq ($(filter y,$(CONFIG_USB_DWC2_HOST) $(CONFIG_USB_DWC2_DUAL_ROLE)),) diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index 78a4925aa1185..d1c6a8a9d9789 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -83,6 +83,7 @@ int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg) gr->pcgcctl1 = dwc2_readl(hsotg, PCGCCTL1); gr->glpmcfg = dwc2_readl(hsotg, GLPMCFG); gr->gi2cctl = dwc2_readl(hsotg, GI2CCTL); + gr->ggpio = dwc2_readl(hsotg, GGPIO); gr->pcgcctl = dwc2_readl(hsotg, PCGCTL); gr->valid = true; @@ -112,21 +113,82 @@ int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg) gr->valid = false; dwc2_writel(hsotg, 0xffffffff, GINTSTS); + dwc2_writel(hsotg, gr->gahbcfg, GAHBCFG); + dwc2_writel(hsotg, gr->gusbcfg, GUSBCFG); dwc2_writel(hsotg, gr->gotgctl, GOTGCTL); dwc2_writel(hsotg, gr->gintmsk, GINTMSK); - dwc2_writel(hsotg, gr->gusbcfg, GUSBCFG); - dwc2_writel(hsotg, gr->gahbcfg, GAHBCFG); dwc2_writel(hsotg, gr->grxfsiz, GRXFSIZ); dwc2_writel(hsotg, gr->gnptxfsiz, GNPTXFSIZ); dwc2_writel(hsotg, gr->gdfifocfg, GDFIFOCFG); dwc2_writel(hsotg, gr->pcgcctl1, PCGCCTL1); dwc2_writel(hsotg, gr->glpmcfg, GLPMCFG); dwc2_writel(hsotg, gr->pcgcctl, PCGCTL); + dwc2_writel(hsotg, gr->ggpio, GGPIO); dwc2_writel(hsotg, gr->gi2cctl, GI2CCTL); return 0; } +int dwc2_backup_registers(struct dwc2_hsotg *hsotg) +{ + int ret; + + /* Backup all registers */ + ret = dwc2_backup_global_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to backup global registers\n", + __func__); + return ret; + } + + if (dwc2_is_host_mode(hsotg)) { + ret = dwc2_backup_host_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to backup host registers\n", + __func__); + return ret; + } + } else { + ret = dwc2_backup_device_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to backup device registers\n", + __func__); + return ret; + } + } + + return 0; +} + +int dwc2_restore_registers(struct dwc2_hsotg *hsotg) +{ + int ret; + + ret = dwc2_restore_global_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to restore registers\n", + __func__); + return ret; + } + if (dwc2_is_host_mode(hsotg)) { + ret = dwc2_restore_host_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to restore host registers\n", + __func__); + return ret; + } + } else { + ret = dwc2_restore_device_registers(hsotg, 0); + if (ret) { + dev_err(hsotg->dev, "%s: failed to restore device registers\n", + __func__); + return ret; + } + } + + return 0; +} + /** * dwc2_exit_partial_power_down() - Exit controller from Partial Power Down. * @@ -136,7 +198,6 @@ int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg) int dwc2_exit_partial_power_down(struct dwc2_hsotg *hsotg, bool restore) { u32 pcgcctl; - int ret = 0; if (hsotg->params.power_down != DWC2_POWER_DOWN_PARAM_PARTIAL) return -ENOTSUPP; @@ -154,31 +215,11 @@ int dwc2_exit_partial_power_down(struct dwc2_hsotg *hsotg, bool restore) dwc2_writel(hsotg, pcgcctl, PCGCTL); udelay(100); - if (restore) { - ret = dwc2_restore_global_registers(hsotg); - if (ret) { - dev_err(hsotg->dev, "%s: failed to restore registers\n", - __func__); - return ret; - } - if (dwc2_is_host_mode(hsotg)) { - ret = dwc2_restore_host_registers(hsotg); - if (ret) { - dev_err(hsotg->dev, "%s: failed to restore host registers\n", - __func__); - return ret; - } - } else { - ret = dwc2_restore_device_registers(hsotg, 0); - if (ret) { - dev_err(hsotg->dev, "%s: failed to restore device registers\n", - __func__); - return ret; - } - } - } - return ret; + if (restore) + return dwc2_restore_registers(hsotg); + + return 0; } /** @@ -189,34 +230,14 @@ int dwc2_exit_partial_power_down(struct dwc2_hsotg *hsotg, bool restore) int dwc2_enter_partial_power_down(struct dwc2_hsotg *hsotg) { u32 pcgcctl; - int ret = 0; + int ret; if (!hsotg->params.power_down) return -ENOTSUPP; - /* Backup all registers */ - ret = dwc2_backup_global_registers(hsotg); - if (ret) { - dev_err(hsotg->dev, "%s: failed to backup global registers\n", - __func__); + ret = dwc2_backup_registers(hsotg); + if (ret) return ret; - } - - if (dwc2_is_host_mode(hsotg)) { - ret = dwc2_backup_host_registers(hsotg); - if (ret) { - dev_err(hsotg->dev, "%s: failed to backup host registers\n", - __func__); - return ret; - } - } else { - ret = dwc2_backup_device_registers(hsotg); - if (ret) { - dev_err(hsotg->dev, "%s: failed to backup device registers\n", - __func__); - return ret; - } - } /* * Clear any pending interrupts since dwc2 will not be able to @@ -238,7 +259,7 @@ int dwc2_enter_partial_power_down(struct dwc2_hsotg *hsotg) pcgcctl |= PCGCTL_STOPPCLK; dwc2_writel(hsotg, pcgcctl, PCGCTL); - return ret; + return 0; } /** diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index d08d070a0fb6f..04d6b4937d769 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 Vbuslevel + * 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; @@ -680,6 +685,7 @@ struct dwc2_hw_params { * @grxfsiz: Backup of GRXFSIZ register * @gnptxfsiz: Backup of GNPTXFSIZ register * @gi2cctl: Backup of GI2CCTL register + * @ggpio: Backup of GGPIO register * @glpmcfg: Backup of GLPMCFG register * @gdfifocfg: Backup of GDFIFOCFG register * @pcgcctl: Backup of PCGCCTL register @@ -696,6 +702,7 @@ struct dwc2_gregs_backup { u32 grxfsiz; u32 gnptxfsiz; u32 gi2cctl; + u32 ggpio; u32 glpmcfg; u32 pcgcctl; u32 pcgcctl1; @@ -855,6 +862,7 @@ struct dwc2_hregs_backup { * - USB_DR_MODE_PERIPHERAL * - USB_DR_MODE_HOST * - USB_DR_MODE_OTG + * @role_sw: usb_role_switch handle * @hcd_enabled: Host mode sub-driver initialization indicator. * @gadget_enabled: Peripheral mode sub-driver initialization indicator. * @ll_hw_enabled: Status of low-level hardware resources. @@ -874,6 +882,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 @@ -1047,6 +1057,7 @@ struct dwc2_hsotg { struct dwc2_core_params params; enum usb_otg_state op_state; enum usb_dr_mode dr_mode; + struct usb_role_switch *role_sw; unsigned int hcd_enabled:1; unsigned int gadget_enabled:1; unsigned int ll_hw_enabled:1; @@ -1061,6 +1072,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; @@ -1315,6 +1327,8 @@ void dwc2_disable_global_interrupts(struct dwc2_hsotg *hcd); void dwc2_hib_restore_common(struct dwc2_hsotg *hsotg, int rem_wakeup, int is_host); +int dwc2_backup_registers(struct dwc2_hsotg *hsotg); +int dwc2_restore_registers(struct dwc2_hsotg *hsotg); int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg); int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg); @@ -1364,6 +1378,11 @@ static inline int dwc2_is_device_mode(struct dwc2_hsotg *hsotg) return (dwc2_readl(hsotg, GINTSTS) & GINTSTS_CURMODE_HOST) == 0; } +int dwc2_drd_init(struct dwc2_hsotg *hsotg); +void dwc2_drd_suspend(struct dwc2_hsotg *hsotg); +void dwc2_drd_resume(struct dwc2_hsotg *hsotg); +void dwc2_drd_exit(struct dwc2_hsotg *hsotg); + /* * Dump core registers and SPRAM */ @@ -1380,6 +1399,7 @@ int dwc2_hsotg_resume(struct dwc2_hsotg *dwc2); int dwc2_gadget_init(struct dwc2_hsotg *hsotg); void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2, bool reset); +void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg); void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg); void dwc2_hsotg_disconnect(struct dwc2_hsotg *dwc2); int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, int testmode); @@ -1405,6 +1425,7 @@ static inline int dwc2_gadget_init(struct dwc2_hsotg *hsotg) { return 0; } static inline void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2, bool reset) {} +static inline void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg) {} static inline void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg) {} static inline void dwc2_hsotg_disconnect(struct dwc2_hsotg *dwc2) {} static inline int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, diff --git a/drivers/usb/dwc2/drd.c b/drivers/usb/dwc2/drd.c new file mode 100644 index 0000000000000..728ca64450960 --- /dev/null +++ b/drivers/usb/dwc2/drd.c @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * drd.c - DesignWare USB2 DRD Controller Dual-role support + * + * Copyright (C) 2019 STMicroelectronics + * + * Author(s): Amelie Delaunay + */ + +#include +#include +#include +#include +#include "core.h" + +static void dwc2_ovr_init(struct dwc2_hsotg *hsotg) +{ + unsigned long flags; + u32 gotgctl; + + spin_lock_irqsave(&hsotg->lock, flags); + + gotgctl = dwc2_readl(hsotg, GOTGCTL); + gotgctl |= GOTGCTL_BVALOEN | GOTGCTL_AVALOEN | GOTGCTL_VBVALOEN; + gotgctl |= GOTGCTL_DBNCE_FLTR_BYPASS; + gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL); + dwc2_writel(hsotg, gotgctl, GOTGCTL); + + spin_unlock_irqrestore(&hsotg->lock, flags); + + dwc2_force_mode(hsotg, (hsotg->dr_mode == USB_DR_MODE_HOST)); +} + +static int dwc2_ovr_avalid(struct dwc2_hsotg *hsotg, bool valid) +{ + u32 gotgctl = dwc2_readl(hsotg, GOTGCTL); + + /* Check if A-Session is already in the right state */ + if ((valid && (gotgctl & GOTGCTL_ASESVLD)) || + (!valid && !(gotgctl & GOTGCTL_ASESVLD))) + return -EALREADY; + + if (valid) + gotgctl |= GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL; + else + gotgctl &= ~(GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL); + dwc2_writel(hsotg, gotgctl, GOTGCTL); + + return 0; +} + +static int dwc2_ovr_bvalid(struct dwc2_hsotg *hsotg, bool valid) +{ + u32 gotgctl = dwc2_readl(hsotg, GOTGCTL); + + /* Check if B-Session is already in the right state */ + if ((valid && (gotgctl & GOTGCTL_BSESVLD)) || + (!valid && !(gotgctl & GOTGCTL_BSESVLD))) + return -EALREADY; + + if (valid) + gotgctl |= GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL; + else + gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL); + dwc2_writel(hsotg, gotgctl, GOTGCTL); + + return 0; +} + +static int dwc2_drd_role_sw_set(struct device *dev, enum usb_role role) +{ + struct dwc2_hsotg *hsotg = dev_get_drvdata(dev); + unsigned long flags; + int already = 0; + + /* Skip session not in line with dr_mode */ + if ((role == USB_ROLE_DEVICE && hsotg->dr_mode == USB_DR_MODE_HOST) || + (role == USB_ROLE_HOST && hsotg->dr_mode == USB_DR_MODE_PERIPHERAL)) + return -EINVAL; + +#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \ + IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) + /* Skip session if core is in test mode */ + if (role == USB_ROLE_NONE && hsotg->test_mode) { + dev_dbg(hsotg->dev, "Core is in test mode\n"); + return -EBUSY; + } +#endif + + /* + * In case of USB_DR_MODE_PERIPHERAL, clock is disabled at the end of + * the probe and enabled on udc_start. + * If role-switch set is called before the udc_start, we need to enable + * the clock to read/write GOTGCTL and GUSBCFG registers to override + * mode and sessions. It is the case if cable is plugged at boot. + */ + if (!hsotg->ll_hw_enabled && hsotg->clk) { + int ret = clk_prepare_enable(hsotg->clk); + if (ret) + return ret; + } + + spin_lock_irqsave(&hsotg->lock, flags); + + if (role == USB_ROLE_HOST) { + already = dwc2_ovr_avalid(hsotg, true); + } else if (role == USB_ROLE_DEVICE) { + already = dwc2_ovr_bvalid(hsotg, true); + /* This clear DCTL.SFTDISCON bit */ + dwc2_hsotg_core_connect(hsotg); + } else { + if (dwc2_is_device_mode(hsotg)) { + if (!dwc2_ovr_bvalid(hsotg, false)) + /* This set DCTL.SFTDISCON bit */ + dwc2_hsotg_core_disconnect(hsotg); + } else { + dwc2_ovr_avalid(hsotg, false); + } + } + + spin_unlock_irqrestore(&hsotg->lock, flags); + + if (!already && hsotg->dr_mode == USB_DR_MODE_OTG) + /* This will raise a Connector ID Status Change Interrupt */ + dwc2_force_mode(hsotg, role == USB_ROLE_HOST); + + if (!hsotg->ll_hw_enabled && hsotg->clk) + clk_disable_unprepare(hsotg->clk); + + dev_dbg(hsotg->dev, "%s-session valid\n", + role == USB_ROLE_NONE ? "No" : + role == USB_ROLE_HOST ? "A" : "B"); + + return 0; +} + +int dwc2_drd_init(struct dwc2_hsotg *hsotg) +{ + int ret; + + if (device_property_read_bool(hsotg->dev, "usb-role-switch")) { + struct usb_role_switch_desc role_sw_desc = {0}; + struct usb_role_switch *role_sw; + + role_sw_desc.fwnode = dev_fwnode(hsotg->dev); + role_sw_desc.set = dwc2_drd_role_sw_set; + role_sw_desc.allow_userspace_control = true; + + role_sw = usb_role_switch_register(hsotg->dev, &role_sw_desc); + if (IS_ERR(role_sw)) { + ret = PTR_ERR(role_sw); + dev_err(hsotg->dev, + "unable to register role switch: %d\n", ret); + return ret; + } + + hsotg->role_sw = role_sw; + + /* Enable override and initialize values */ + dwc2_ovr_init(hsotg); + } + + return 0; +} + +void dwc2_drd_suspend(struct dwc2_hsotg *hsotg) +{ + u32 gintsts, gintmsk; + + if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) { + gintmsk = dwc2_readl(hsotg, GINTMSK); + gintmsk &= ~GINTSTS_CONIDSTSCHNG; + dwc2_writel(hsotg, gintmsk, GINTMSK); + gintsts = dwc2_readl(hsotg, GINTSTS); + dwc2_writel(hsotg, gintsts | GINTSTS_CONIDSTSCHNG, GINTSTS); + } +} + +void dwc2_drd_resume(struct dwc2_hsotg *hsotg) +{ + u32 gintsts, gintmsk; + + if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) { + gintsts = dwc2_readl(hsotg, GINTSTS); + dwc2_writel(hsotg, gintsts | GINTSTS_CONIDSTSCHNG, GINTSTS); + gintmsk = dwc2_readl(hsotg, GINTMSK); + gintmsk |= GINTSTS_CONIDSTSCHNG; + dwc2_writel(hsotg, gintmsk, GINTMSK); + } +} + +void dwc2_drd_exit(struct dwc2_hsotg *hsotg) +{ + if (hsotg->role_sw) + usb_role_switch_unregister(hsotg->role_sw); +} diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index f7528f732b2aa..8e448e8b1fe4b 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3531,7 +3531,7 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, dwc2_readl(hsotg, DOEPCTL0)); } -static void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg) +void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg) { /* set the soft-disconnect bit */ dwc2_set_bit(hsotg, DCTL, DCTL_SFTDISCON); @@ -3540,7 +3540,8 @@ static void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg) void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg) { /* remove the soft-disconnect and let's go */ - dwc2_clear_bit(hsotg, DCTL, DCTL_SFTDISCON); + if (!hsotg->role_sw || (dwc2_readl(hsotg, GOTGCTL) & GOTGCTL_BSESVLD)) + dwc2_clear_bit(hsotg, DCTL, DCTL_SFTDISCON); } /** @@ -4924,7 +4925,7 @@ int dwc2_hsotg_suspend(struct dwc2_hsotg *hsotg) hsotg->gadget.speed = USB_SPEED_UNKNOWN; spin_unlock_irqrestore(&hsotg->lock, flags); - for (ep = 0; ep < hsotg->num_of_eps; ep++) { + for (ep = 1; ep < hsotg->num_of_eps; ep++) { if (hsotg->eps_in[ep]) dwc2_hsotg_ep_disable_lock(&hsotg->eps_in[ep]->ep); if (hsotg->eps_out[ep]) diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 81afe553aa666..f89e6d003ec8b 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -1735,7 +1735,8 @@ static void dwc2_hcd_cleanup_channels(struct dwc2_hsotg *hsotg) * release_channel_ddma(), which is called from ep_disable when * device disconnects */ - channel->qh = NULL; + if (hsotg->params.host_dma && hsotg->params.dma_desc_enable) + channel->qh = NULL; } /* All channels have been freed, mark them available */ if (hsotg->params.uframe_sched) { @@ -3611,7 +3612,8 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, if (wvalue != USB_PORT_FEAT_TEST && (!windex || windex > 1)) goto error; - if (!hsotg->flags.b.port_connect_status) { + if (!hsotg->flags.b.port_connect_status && + !dwc2_is_host_mode(hsotg)) { /* * The port is disconnected, which means the core is * either in device mode or it soon will be. Just diff --git a/drivers/usb/dwc2/hw.h b/drivers/usb/dwc2/hw.h index 510e87ec0be84..c4027bbcedec4 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 31e090ac9f1ec..b4cff8b079ec2 100644 --- a/drivers/usb/dwc2/params.c +++ b/drivers/usb/dwc2/params.c @@ -163,6 +163,45 @@ static void dwc2_set_stm32f7_hsotg_params(struct dwc2_hsotg *hsotg) p->host_perio_tx_fifo_size = 256; } +static void dwc2_set_stm32mp1_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->ahbcfg = GAHBCFG_HBSTLEN_INCR16 << GAHBCFG_HBSTLEN_SHIFT; + p->power_down = DWC2_POWER_DOWN_PARAM_NONE; + p->host_support_fs_ls_low_power = true; + p->host_ls_low_power_phy_clk = true; +} + +static void dwc2_set_stm32mp1_hsotg_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + struct device_node *np = hsotg->dev->of_node; + + p->otg_cap = DWC2_CAP_PARAM_NO_HNP_SRP_CAPABLE; + p->activate_stm_id_vb_detection = + !of_property_read_bool(np, "usb-role-switch"); + p->host_rx_fifo_size = 440; + p->host_nperio_tx_fifo_size = 256; + p->host_perio_tx_fifo_size = 256; + p->ahbcfg = GAHBCFG_HBSTLEN_INCR16 << GAHBCFG_HBSTLEN_SHIFT; + p->power_down = DWC2_POWER_DOWN_PARAM_NONE; + p->lpm = false; + p->lpm_clock_gating = false; + p->besl = false; + p->hird_threshold_en = false; +} + 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 +225,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,stm32mp1-fsotg", + .data = dwc2_set_stm32mp1_fsotg_params }, + { .compatible = "st,stm32mp1-hsotg", + .data = dwc2_set_stm32mp1_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 4e14c4f7fed7a..0acbf58020ea8 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -312,6 +312,11 @@ static int dwc2_driver_remove(struct platform_device *dev) if (hsotg->gadget_enabled) dwc2_hsotg_remove(hsotg); + dwc2_drd_exit(hsotg); + + if (hsotg->params.activate_stm_id_vb_detection) + regulator_disable(hsotg->usb33d); + if (hsotg->ll_hw_enabled) dwc2_lowlevel_hw_disable(hsotg); @@ -465,10 +470,40 @@ 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); + dev_err(hsotg->dev, + "can't get voltage level detector supply\n"); + goto error; + } + retval = regulator_enable(hsotg->usb33d); + if (retval) { + dev_err(hsotg->dev, + "can't enable voltage level detector supply\n"); + goto error; + } + + ggpio = dwc2_readl(hsotg, GGPIO); + ggpio |= GGPIO_STM32_OTG_GCCFG_IDEN; + ggpio |= GGPIO_STM32_OTG_GCCFG_VBDEN; + dwc2_writel(hsotg, ggpio, GGPIO); + } + + retval = dwc2_drd_init(hsotg); + if (retval) { + if (retval != -EPROBE_DEFER) + dev_err(hsotg->dev, "failed to initialize dual-role\n"); + goto error_init; + } + if (hsotg->dr_mode != USB_DR_MODE_HOST) { retval = dwc2_gadget_init(hsotg); if (retval) - goto error; + goto error_drd; hsotg->gadget_enabled = 1; } @@ -494,7 +529,7 @@ static int dwc2_driver_probe(struct platform_device *dev) if (retval) { if (hsotg->gadget_enabled) dwc2_hsotg_remove(hsotg); - goto error; + goto error_drd; } hsotg->hcd_enabled = 1; } @@ -521,6 +556,11 @@ static int dwc2_driver_probe(struct platform_device *dev) #endif /* CONFIG_USB_DWC2_PERIPHERAL || CONFIG_USB_DWC2_DUAL_ROLE */ return 0; +error_drd: + dwc2_drd_exit(hsotg); +error_init: + if (hsotg->params.activate_stm_id_vb_detection) + regulator_disable(hsotg->usb33d); error: dwc2_lowlevel_hw_disable(hsotg); return retval; @@ -535,12 +575,61 @@ static int __maybe_unused dwc2_suspend(struct device *dev) if (is_device_mode) dwc2_hsotg_suspend(dwc2); + dwc2_drd_suspend(dwc2); + + if (dwc2->params.power_down == DWC2_POWER_DOWN_PARAM_NONE) { + /* + * Backup host registers when power_down param is 'none', if + * controller power is disabled. + * This shouldn't be needed, when using other power_down modes. + */ + ret = dwc2_backup_registers(dwc2); + if (ret) { + dev_err(dwc2->dev, "backup regs failed %d\n", ret); + return ret; + } + } + + 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); dwc2->phy_off_for_suspend = true; } + if (device_may_wakeup(dev) || device_wakeup_path(dev)) + enable_irq_wake(dwc2->irq); + return ret; } @@ -549,6 +638,9 @@ static int __maybe_unused dwc2_resume(struct device *dev) struct dwc2_hsotg *dwc2 = dev_get_drvdata(dev); int ret = 0; + if (device_may_wakeup(dev) || device_wakeup_path(dev)) + disable_irq_wake(dwc2->irq); + if (dwc2->phy_off_for_suspend && dwc2->ll_hw_enabled) { ret = __dwc2_lowlevel_hw_enable(dwc2); if (ret) @@ -556,6 +648,41 @@ 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); + } + + if (dwc2->params.power_down == DWC2_POWER_DOWN_PARAM_NONE) { + ret = dwc2_restore_registers(dwc2); + if (ret) { + dev_err(dwc2->dev, "restore regs failed %d\n", ret); + return ret; + } + } + + dwc2_drd_resume(dwc2); + if (dwc2_is_device_mode(dwc2)) ret = dwc2_hsotg_resume(dwc2); diff --git a/drivers/usb/gadget/function/f_acm.c b/drivers/usb/gadget/function/f_acm.c index 9fc98de836249..7800593735659 100644 --- a/drivers/usb/gadget/function/f_acm.c +++ b/drivers/usb/gadget/function/f_acm.c @@ -723,6 +723,20 @@ static void acm_free_func(struct usb_function *f) kfree(acm); } +static void acm_resume(struct usb_function *f) +{ + struct f_acm *acm = func_to_acm(f); + + gserial_resume(&acm->port); +} + +static void acm_suspend(struct usb_function *f) +{ + struct f_acm *acm = func_to_acm(f); + + gserial_suspend(&acm->port); +} + static struct usb_function *acm_alloc_func(struct usb_function_instance *fi) { struct f_serial_opts *opts; @@ -750,6 +764,8 @@ static struct usb_function *acm_alloc_func(struct usb_function_instance *fi) acm->port_num = opts->port_num; acm->port.func.unbind = acm_unbind; acm->port.func.free_func = acm_free_func; + acm->port.func.resume = acm_resume; + acm->port.func.suspend = acm_suspend; return &acm->port.func; } diff --git a/drivers/usb/gadget/function/f_serial.c b/drivers/usb/gadget/function/f_serial.c index c860f30a0ea2b..b2fb6b7424e98 100644 --- a/drivers/usb/gadget/function/f_serial.c +++ b/drivers/usb/gadget/function/f_serial.c @@ -327,6 +327,20 @@ static void gser_unbind(struct usb_configuration *c, struct usb_function *f) usb_free_all_descriptors(f); } +static void gser_resume(struct usb_function *f) +{ + struct f_gser *gser = func_to_gser(f); + + gserial_resume(&gser->port); +} + +static void gser_suspend(struct usb_function *f) +{ + struct f_gser *gser = func_to_gser(f); + + gserial_suspend(&gser->port); +} + static struct usb_function *gser_alloc(struct usb_function_instance *fi) { struct f_gser *gser; @@ -348,6 +362,8 @@ static struct usb_function *gser_alloc(struct usb_function_instance *fi) gser->port.func.set_alt = gser_set_alt; gser->port.func.disable = gser_disable; gser->port.func.free_func = gser_free; + gser->port.func.resume = gser_resume; + gser->port.func.suspend = gser_suspend; return &gser->port.func; } diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c index 038c445a4e9b5..3c144621ad935 100644 --- a/drivers/usb/gadget/function/u_serial.c +++ b/drivers/usb/gadget/function/u_serial.c @@ -119,6 +119,8 @@ struct gs_port { wait_queue_head_t drain_wait; /* wait while writes drain */ bool write_busy; wait_queue_head_t close_wait; + bool suspended; /* port suspended */ + bool start_delayed; /* delay start when suspended */ /* REVISIT this state ... */ struct usb_cdc_line_coding port_line_coding; /* 8-N-1 etc */ @@ -666,13 +668,19 @@ static int gs_open(struct tty_struct *tty, struct file *file) /* if connected, start the I/O stream */ if (port->port_usb) { - struct gserial *gser = port->port_usb; + /* if port is suspended, wait resume to start I/0 stream */ + if (!port->suspended) { + struct gserial *gser = port->port_usb; - pr_debug("gs_open: start ttyGS%d\n", port->port_num); - gs_start_io(port); + pr_debug("gs_open: start ttyGS%d\n", port->port_num); + gs_start_io(port); - if (gser->connect) - gser->connect(gser); + if (gser->connect) + gser->connect(gser); + } else { + pr_debug("delay start of ttyGS%d\n", port->port_num); + port->start_delayed = true; + } } pr_debug("gs_open: ttyGS%d (%p,%p)\n", port->port_num, tty, file); @@ -720,7 +728,7 @@ static void gs_close(struct tty_struct *tty, struct file *file) port->port.count = 0; gser = port->port_usb; - if (gser && gser->disconnect) + if (gser && !port->suspended && gser->disconnect) gser->disconnect(gser); /* wait for circular write buffer to drain, disconnect, or at @@ -744,6 +752,7 @@ static void gs_close(struct tty_struct *tty, struct file *file) else kfifo_reset(&port->port_write_buf); + port->start_delayed = false; port->port.tty = NULL; port->openclose = false; @@ -1395,6 +1404,38 @@ void gserial_disconnect(struct gserial *gser) } EXPORT_SYMBOL_GPL(gserial_disconnect); +void gserial_suspend(struct gserial *gser) +{ + struct gs_port *port = gser->ioport; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock, flags); + port->suspended = true; + spin_unlock_irqrestore(&port->port_lock, flags); +} +EXPORT_SYMBOL_GPL(gserial_suspend); + +void gserial_resume(struct gserial *gser) +{ + struct gs_port *port = gser->ioport; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock, flags); + port->suspended = false; + if (!port->start_delayed) { + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + + pr_debug("delayed start ttyGS%d\n", port->port_num); + gs_start_io(port); + if (gser->connect) + gser->connect(gser); + port->start_delayed = false; + spin_unlock_irqrestore(&port->port_lock, flags); +} +EXPORT_SYMBOL_GPL(gserial_resume); + static int userial_init(void) { unsigned i; diff --git a/drivers/usb/gadget/function/u_serial.h b/drivers/usb/gadget/function/u_serial.h index 9acaac1cbb75a..223a4be05afe7 100644 --- a/drivers/usb/gadget/function/u_serial.h +++ b/drivers/usb/gadget/function/u_serial.h @@ -60,6 +60,8 @@ void gserial_free_line(unsigned char port_line); /* connect/disconnect is handled by individual functions */ int gserial_connect(struct gserial *, u8 port_num); void gserial_disconnect(struct gserial *); +void gserial_suspend(struct gserial *p); +void gserial_resume(struct gserial *p); /* functions are bound to configurations by a config or gadget driver */ int gser_bind_config(struct usb_configuration *c, u8 port_num); diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c index e4fc3f66d43bf..db436d432e723 100644 --- a/drivers/usb/host/ehci-platform.c +++ b/drivers/usb/host/ehci-platform.c @@ -35,6 +35,7 @@ #include #include #include +#include #include "ehci.h" @@ -430,6 +431,9 @@ static int ehci_platform_suspend(struct device *dev) if (pdata->power_suspend) pdata->power_suspend(pdev); + if (device_may_wakeup(dev) || device_wakeup_path(dev)) + enable_irq_wake(hcd->irq); + return ret; } @@ -441,6 +445,9 @@ static int ehci_platform_resume(struct device *dev) struct ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); struct device *companion_dev; + if (device_may_wakeup(dev) || device_wakeup_path(dev)) + disable_irq_wake(hcd->irq); + if (pdata->power_on) { int err = pdata->power_on(pdev); if (err < 0) diff --git a/drivers/usb/renesas_usbhs/rcar2.c b/drivers/usb/renesas_usbhs/rcar2.c index 440d213e1749d..791908f8cf73e 100644 --- a/drivers/usb/renesas_usbhs/rcar2.c +++ b/drivers/usb/renesas_usbhs/rcar2.c @@ -34,7 +34,7 @@ static int usbhs_rcar2_hardware_exit(struct platform_device *pdev) struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); if (priv->phy) { - phy_put(priv->phy); + phy_put(&pdev->dev, priv->phy); priv->phy = NULL; } diff --git a/drivers/usb/renesas_usbhs/rza2.c b/drivers/usb/renesas_usbhs/rza2.c index 021749594389e..3eed3334a17f1 100644 --- a/drivers/usb/renesas_usbhs/rza2.c +++ b/drivers/usb/renesas_usbhs/rza2.c @@ -29,7 +29,7 @@ static int usbhs_rza2_hardware_exit(struct platform_device *pdev) { struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); - phy_put(priv->phy); + phy_put(&pdev->dev, priv->phy); priv->phy = NULL; return 0; diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig index 895e2418de53b..3a1ee89be74e4 100644 --- a/drivers/usb/typec/Kconfig +++ b/drivers/usb/typec/Kconfig @@ -61,6 +61,15 @@ config TYPEC_TPS6598X If you choose to build this driver as a dynamically linked module, the module will be called tps6598x.ko. +config TYPEC_STUSB + tristate "STMicroelectronics STUSB Type-C controller driver" + depends on I2C + select USB_ROLE_SWITCH + help + The STMicroelectronics STUSB Type-C controller driver that works + with Type-C Port Controller Manager to provide USB Type-C + functionalities. + source "drivers/usb/typec/mux/Kconfig" source "drivers/usb/typec/altmodes/Kconfig" diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile index 6696b7263d61a..c9136020e4f3e 100644 --- a/drivers/usb/typec/Makefile +++ b/drivers/usb/typec/Makefile @@ -5,4 +5,5 @@ obj-$(CONFIG_TYPEC) += altmodes/ obj-$(CONFIG_TYPEC_TCPM) += tcpm/ obj-$(CONFIG_TYPEC_UCSI) += ucsi/ obj-$(CONFIG_TYPEC_TPS6598X) += tps6598x.o +obj-$(CONFIG_TYPEC_STUSB) += typec_stusb.o obj-$(CONFIG_TYPEC) += mux/ diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index a400b65cf17bb..6391d01015d17 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -1387,6 +1387,21 @@ void typec_set_pwr_opmode(struct typec_port *port, } EXPORT_SYMBOL_GPL(typec_set_pwr_opmode); +/** + * typec_find_power_opmode - Get the typec port power operation mode + * @name: port power operation mode string + * + * This routine is used to find the typec_pwr_opmodes by its string name. + * + * Returns typec_pwr_opmodes if success, otherwise negative error code. + */ +int typec_find_port_power_opmode(const char *name) +{ + return match_string(typec_pwr_opmodes, + ARRAY_SIZE(typec_pwr_opmodes), name); +} +EXPORT_SYMBOL_GPL(typec_find_port_power_opmode); + /** * typec_find_port_power_role - Get the typec port power capability * @name: port power capability string diff --git a/drivers/usb/typec/typec_stusb.c b/drivers/usb/typec/typec_stusb.c new file mode 100644 index 0000000000000..e760ba304f854 --- /dev/null +++ b/drivers/usb/typec/typec_stusb.c @@ -0,0 +1,910 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * STMicroelectronics STUSB Type-C controller family driver + * + * Copyright (C) 2019, STMicroelectronics + * Author(s): Amelie Delaunay + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STUSB_ALERT_STATUS 0x0B /* RC */ +#define STUSB_ALERT_STATUS_MASK_CTRL 0x0C /* RW */ +#define STUSB_CC_CONNECTION_STATUS_TRANS 0x0D /* RC */ +#define STUSB_CC_CONNECTION_STATUS 0x0E /* RO */ +#define STUSB_MONITORING_STATUS_TRANS 0x0F /* RC */ +#define STUSB_MONITORING_STATUS 0x10 /* RO */ +#define STUSB_CC_OPERATION_STATUS 0x11 /* RO */ +#define STUSB_HW_FAULT_STATUS_TRANS 0x12 /* RC */ +#define STUSB_HW_FAULT_STATUS 0x13 /* RO */ +#define STUSB_CC_CAPABILITY_CTRL 0x18 /* RW */ +#define STUSB_CC_VCONN_SWITCH_CTRL 0x1E /* RW */ +#define STUSB_VCONN_MONITORING_CTRL 0x20 /* RW */ +#define STUSB_VBUS_MONITORING_RANGE_CTRL 0x22 /* RW */ +#define STUSB_RESET_CTRL 0x23 /* RW */ +#define STUSB_VBUS_DISCHARGE_TIME_CTRL 0x25 /* RW */ +#define STUSB_VBUS_DISCHARGE_STATUS 0x26 /* RO */ +#define STUSB_VBUS_ENABLE_STATUS 0x27 /* RO */ +#define STUSB_CC_POWER_MODE_CTRL 0x28 /* RW */ +#define STUSB_VBUS_MONITORING_CTRL 0x2E /* RW */ +#define STUSB1600_REG_MAX 0x2F /* RO - Reserved */ + +/* STUSB_ALERT_STATUS/STUSB_ALERT_STATUS_MASK_CTRL bitfields */ +#define STUSB_HW_FAULT BIT(4) +#define STUSB_MONITORING BIT(5) +#define STUSB_CC_CONNECTION BIT(6) +#define STUSB_ALL_ALERTS GENMASK(6, 4) + +/* STUSB_CC_CONNECTION_STATUS_TRANS bitfields */ +#define STUSB_CC_ATTACH_TRANS BIT(0) + +/* STUSB_CC_CONNECTION_STATUS bitfields */ +#define STUSB_CC_ATTACH BIT(0) +#define STUSB_CC_VCONN_SUPPLY BIT(1) +#define STUSB_CC_DATA_ROLE(s) (!!((s) & BIT(2))) +#define STUSB_CC_POWER_ROLE(s) (!!((s) & BIT(3))) +#define STUSB_CC_ATTACHED_MODE GENMASK(7, 5) + +/* STUSB_MONITORING_STATUS_TRANS bitfields */ +#define STUSB_VCONN_PRESENCE_TRANS BIT(0) +#define STUSB_VBUS_PRESENCE_TRANS BIT(1) +#define STUSB_VBUS_VSAFE0V_TRANS BIT(2) +#define STUSB_VBUS_VALID_TRANS BIT(3) + +/* STUSB_MONITORING_STATUS bitfields */ +#define STUSB_VCONN_PRESENCE BIT(0) +#define STUSB_VBUS_PRESENCE BIT(1) +#define STUSB_VBUS_VSAFE0V BIT(2) +#define STUSB_VBUS_VALID BIT(3) + +/* STUSB_CC_OPERATION_STATUS bitfields */ +#define STUSB_TYPEC_FSM_STATE GENMASK(4, 0) +#define STUSB_SINK_POWER_STATE GENMASK(6, 5) +#define STUSB_CC_ATTACHED BIT(7) + +/* STUSB_HW_FAULT_STATUS_TRANS bitfields */ +#define STUSB_VCONN_SW_OVP_FAULT_TRANS BIT(0) +#define STUSB_VCONN_SW_OCP_FAULT_TRANS BIT(1) +#define STUSB_VCONN_SW_RVP_FAULT_TRANS BIT(2) +#define STUSB_VPU_VALID_TRANS BIT(4) +#define STUSB_VPU_OVP_FAULT_TRANS BIT(5) +#define STUSB_THERMAL_FAULT BIT(7) + +/* STUSB_HW_FAULT_STATUS bitfields */ +#define STUSB_VCONN_SW_OVP_FAULT_CC2 BIT(0) +#define STUSB_VCONN_SW_OVP_FAULT_CC1 BIT(1) +#define STUSB_VCONN_SW_OCP_FAULT_CC2 BIT(2) +#define STUSB_VCONN_SW_OCP_FAULT_CC1 BIT(3) +#define STUSB_VCONN_SW_RVP_FAULT_CC2 BIT(4) +#define STUSB_VCONN_SW_RVP_FAULT_CC1 BIT(5) +#define STUSB_VPU_VALID BIT(6) +#define STUSB_VPU_OVP_FAULT BIT(7) + +/* STUSB_CC_CAPABILITY_CTRL bitfields */ +#define STUSB_CC_VCONN_SUPPLY_EN BIT(0) +#define STUSB_CC_VCONN_DISCHARGE_EN BIT(4) +#define STUSB_CC_CURRENT_ADVERTISED GENMASK(7, 6) + +/* STUSB_VCONN_SWITCH_CTRL bitfields */ +#define STUSB_CC_VCONN_SWITCH_ILIM GENMASK(3, 0) + +/* STUSB_VCONN_MONITORING_CTRL bitfields */ +#define STUSB_VCONN_UVLO_THRESHOLD BIT(6) +#define STUSB_VCONN_MONITORING_EN BIT(7) + +/* STUSB_VBUS_MONITORING_RANGE_CTRL bitfields */ +#define STUSB_SHIFT_LOW_VBUS_LIMIT GENMASK(3, 0) +#define STUSB_SHIFT_HIGH_VBUS_LIMIT GENMASK(7, 4) + +/* STUSB_RESET_CTRL bitfields */ +#define STUSB_SW_RESET_EN BIT(0) + +/* STUSB_VBUS_DISCHARGE_TIME_CTRL bitfields */ +#define STUSBXX02_VBUS_DISCHARGE_TIME_TO_PDO GENMASK(3, 0) +#define STUSB_VBUS_DISCHARGE_TIME_TO_0V GENMASK(7, 4) + +/* STUSB_VBUS_DISCHARGE_STATUS bitfields */ +#define STUSB_VBUS_DISCHARGE_EN BIT(7) + +/* STUSB_VBUS_ENABLE_STATUS bitfields */ +#define STUSB_VBUS_SOURCE_EN BIT(0) +#define STUSB_VBUS_SINK_EN BIT(1) + +/* STUSB_CC_POWER_MODE_CTRL bitfields */ +#define STUSB_CC_POWER_MODE GENMASK(2, 0) + +/* STUSB_VBUS_MONITORING_CTRL bitfields */ +#define STUSB_VDD_UVLO_DISABLE BIT(0) +#define STUSB_VBUS_VSAFE0V_THRESHOLD GENMASK(2, 1) +#define STUSB_VBUS_RANGE_DISABLE BIT(4) +#define STUSB_VDD_OVLO_DISABLE BIT(6) + +enum stusb_pwr_mode { + SOURCE_WITH_ACCESSORY, + SINK_WITH_ACCESSORY, + SINK_WITHOUT_ACCESSORY, + DUAL_WITH_ACCESSORY, + DUAL_WITH_ACCESSORY_AND_TRY_SRC, + DUAL_WITH_ACCESSORY_AND_TRY_SNK, +}; + +enum stusb_attached_mode { + NO_DEVICE_ATTACHED, + SINK_ATTACHED, + SOURCE_ATTACHED, + DEBUG_ACCESSORY_ATTACHED, + AUDIO_ACCESSORY_ATTACHED, +}; + +struct stusb { + struct device *dev; + struct regmap *regmap; + struct regulator *vdd_supply; + struct regulator *vsys_supply; + struct regulator *vconn_supply; + struct regulator *main_supply; + + struct typec_port *port; + struct typec_capability capability; + struct typec_partner *partner; + + enum typec_port_type port_type; + enum typec_pwr_opmode pwr_opmode; + bool vbus_on; + + struct usb_role_switch *role_sw; + struct work_struct wq_role_sw; +}; + +static bool stusb_reg_writeable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STUSB_ALERT_STATUS_MASK_CTRL: + case STUSB_CC_CAPABILITY_CTRL: + case STUSB_CC_VCONN_SWITCH_CTRL: + case STUSB_VCONN_MONITORING_CTRL: + case STUSB_VBUS_MONITORING_RANGE_CTRL: + case STUSB_RESET_CTRL: + case STUSB_VBUS_DISCHARGE_TIME_CTRL: + case STUSB_CC_POWER_MODE_CTRL: + case STUSB_VBUS_MONITORING_CTRL: + return true; + default: + return false; + } +} + +static bool stusb_reg_readable(struct device *dev, unsigned int reg) +{ + if (reg <= 0x0A || + (reg >= 0x14 && reg <= 0x17) || + (reg >= 0x19 && reg <= 0x1D) || + (reg >= 0x29 && reg <= 0x2D) || + (reg == 0x1F || reg == 0x21 || reg == 0x24 || reg == 0x2F)) + return false; + else + return true; +} + +static bool stusb_reg_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STUSB_ALERT_STATUS: + case STUSB_CC_CONNECTION_STATUS_TRANS: + case STUSB_CC_CONNECTION_STATUS: + case STUSB_MONITORING_STATUS_TRANS: + case STUSB_MONITORING_STATUS: + case STUSB_CC_OPERATION_STATUS: + case STUSB_HW_FAULT_STATUS_TRANS: + case STUSB_HW_FAULT_STATUS: + case STUSB_VBUS_DISCHARGE_STATUS: + case STUSB_VBUS_ENABLE_STATUS: + return true; + default: + return false; + } +} + +static bool stusb_reg_precious(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STUSB_ALERT_STATUS: + case STUSB_CC_CONNECTION_STATUS_TRANS: + case STUSB_MONITORING_STATUS_TRANS: + case STUSB_HW_FAULT_STATUS_TRANS: + return true; + default: + return false; + } +} + +static const struct regmap_config stusb1600_regmap_config = { + .reg_bits = 8, + .reg_stride = 1, + .val_bits = 8, + .max_register = STUSB1600_REG_MAX, + .writeable_reg = stusb_reg_writeable, + .readable_reg = stusb_reg_readable, + .volatile_reg = stusb_reg_volatile, + .precious_reg = stusb_reg_precious, + .cache_type = REGCACHE_RBTREE, +}; + +static bool stusb_get_vconn(struct stusb *chip) +{ + u32 val; + int ret; + + ret = regmap_read(chip->regmap, STUSB_CC_CAPABILITY_CTRL, &val); + if (ret) { + dev_err(chip->dev, "Unable to get Vconn status: %d\n", ret); + return false; + } + + return !!FIELD_GET(STUSB_CC_VCONN_SUPPLY_EN, val); +} + +static int stusb_set_vconn(struct stusb *chip, bool on) +{ + int ret; + + /* Manage VCONN input supply */ + if (chip->vconn_supply) { + if (on) { + ret = regulator_enable(chip->vconn_supply); + if (ret) { + dev_err(chip->dev, + "failed to enable vconn supply: %d\n", + ret); + return ret; + } + } else { + regulator_disable(chip->vconn_supply); + } + } + + /* Manage VCONN monitoring and power path */ + ret = regmap_update_bits(chip->regmap, STUSB_VCONN_MONITORING_CTRL, + STUSB_VCONN_MONITORING_EN, + on ? STUSB_VCONN_MONITORING_EN : 0); + if (ret) + goto vconn_reg_disable; + + return 0; + +vconn_reg_disable: + if (chip->vconn_supply && on) + regulator_disable(chip->vconn_supply); + + return ret; +} + +static enum typec_pwr_opmode stusb_get_pwr_opmode(struct stusb *chip) +{ + u32 val; + int ret; + + ret = regmap_read(chip->regmap, STUSB_CC_CAPABILITY_CTRL, &val); + if (ret) { + dev_err(chip->dev, "Unable to get pwr opmode: %d\n", ret); + return TYPEC_PWR_MODE_USB; + } + + return FIELD_GET(STUSB_CC_CURRENT_ADVERTISED, val); +} + +static enum typec_accessory stusb_get_accessory(u32 status) +{ + enum stusb_attached_mode mode; + + mode = FIELD_GET(STUSB_CC_ATTACHED_MODE, status); + + switch (mode) { + case DEBUG_ACCESSORY_ATTACHED: + return TYPEC_ACCESSORY_DEBUG; + case AUDIO_ACCESSORY_ATTACHED: + return TYPEC_ACCESSORY_AUDIO; + default: + return TYPEC_ACCESSORY_NONE; + } +} + +static enum typec_role stusb_get_vconn_role(u32 status) +{ + if (FIELD_GET(STUSB_CC_VCONN_SUPPLY, status)) + return TYPEC_SOURCE; + else + return TYPEC_SINK; +} + +static void stusb_set_role_sw(struct work_struct *work) +{ + struct stusb *chip = container_of(work, struct stusb, wq_role_sw); + enum usb_role usb_role = USB_ROLE_NONE; + u32 conn_status, vbus_status; + bool id, vbus; + int ret; + + /* Check ID and Vbus to update state */ + ret = regmap_read(chip->regmap, STUSB_CC_CONNECTION_STATUS, + &conn_status); + if (ret) + return; + + ret = regmap_read(chip->regmap, STUSB_VBUS_ENABLE_STATUS, + &vbus_status); + if (ret) + return; + + /* 0 = Device, 1 = Host */ + id = STUSB_CC_DATA_ROLE(conn_status); + + if (STUSB_CC_POWER_ROLE(conn_status)) /* Source */ + vbus = !!(vbus_status & STUSB_VBUS_SOURCE_EN); + else /* Sink */ + vbus = !!(vbus_status & STUSB_VBUS_SINK_EN); + + dev_dbg(chip->dev, "role=%s vbus=%sable\n", + id ? "Host" : "Device", vbus ? "en" : "dis"); + + /* + * !vbus = detached, so neither B-Session Valid nor A-Session Valid + * !vbus = USB_ROLE_NONE + * vbus = attached, so either B-Session Valid or A-Session Valid + * vbus && !id = B-Session Valid = USB_ROLE_DEVICE + * vbus && id = A-Session Valid = USB_ROLE_HOST + */ + if (vbus && id) /* Attached and A-Session Valid */ + usb_role = USB_ROLE_HOST; + if (vbus && !id) /* Attached and B-Session Valid */ + usb_role = USB_ROLE_DEVICE; + + usb_role_switch_set_role(chip->role_sw, usb_role); +} + +static int stusb_attach(struct stusb *chip, u32 status) +{ + struct typec_partner_desc desc; + int ret; + + if ((STUSB_CC_POWER_ROLE(status) == TYPEC_SOURCE) && + chip->vdd_supply) { + ret = regulator_enable(chip->vdd_supply); + if (ret) { + dev_err(chip->dev, + "Failed to enable Vbus supply: %d\n", ret); + return ret; + } + chip->vbus_on = true; + } + + desc.usb_pd = false; + desc.accessory = stusb_get_accessory(status); + desc.identity = NULL; + + chip->partner = typec_register_partner(chip->port, &desc); + if (IS_ERR(chip->partner)) { + ret = PTR_ERR(chip->partner); + goto vbus_disable; + } + + typec_set_pwr_role(chip->port, STUSB_CC_POWER_ROLE(status)); + typec_set_pwr_opmode(chip->port, stusb_get_pwr_opmode(chip)); + typec_set_vconn_role(chip->port, stusb_get_vconn_role(status)); + typec_set_data_role(chip->port, STUSB_CC_DATA_ROLE(status)); + + if (chip->role_sw) + queue_work(system_power_efficient_wq, &chip->wq_role_sw); + + return 0; + +vbus_disable: + if (chip->vbus_on) { + regulator_disable(chip->vdd_supply); + chip->vbus_on = false; + } + + return ret; +} + +static void stusb_detach(struct stusb *chip, u32 status) +{ + typec_unregister_partner(chip->partner); + chip->partner = NULL; + + typec_set_pwr_role(chip->port, STUSB_CC_POWER_ROLE(status)); + typec_set_pwr_opmode(chip->port, TYPEC_PWR_MODE_USB); + typec_set_vconn_role(chip->port, stusb_get_vconn_role(status)); + typec_set_data_role(chip->port, STUSB_CC_DATA_ROLE(status)); + + if (chip->vbus_on) { + regulator_disable(chip->vdd_supply); + chip->vbus_on = false; + } + + if (chip->role_sw) + queue_work(system_power_efficient_wq, &chip->wq_role_sw); +} + +static irqreturn_t stusb_irq_handler(int irq, void *data) +{ + struct stusb *chip = data; + u32 pending, trans, status; + int ret; + + ret = regmap_read(chip->regmap, STUSB_ALERT_STATUS, &pending); + if (ret) + return IRQ_NONE; + + if (pending & STUSB_CC_CONNECTION) { + ret = regmap_read(chip->regmap, + STUSB_CC_CONNECTION_STATUS_TRANS, &trans); + if (ret) + goto err; + ret = regmap_read(chip->regmap, STUSB_CC_CONNECTION_STATUS, + &status); + if (ret) + goto err; + + if (trans & STUSB_CC_ATTACH_TRANS) { + if (status & STUSB_CC_ATTACH) { + ret = stusb_attach(chip, status); + if (ret) + goto err; + } else { + stusb_detach(chip, status); + } + } + } +err: + return IRQ_HANDLED; +} + +static int stusb_irq_init(struct stusb *chip, int irq) +{ + u32 status; + int ret; + + ret = regmap_read(chip->regmap, STUSB_CC_CONNECTION_STATUS, &status); + if (ret) + return ret; + + if (status & STUSB_CC_ATTACH) { + ret = stusb_attach(chip, status); + if (ret) + dev_err(chip->dev, "attach failed: %d\n", ret); + } + + ret = devm_request_threaded_irq(chip->dev, irq, NULL, stusb_irq_handler, + IRQF_ONESHOT, dev_name(chip->dev), + chip); + if (ret) + goto partner_unregister; + + /* Unmask CC_CONNECTION events */ + ret = regmap_write_bits(chip->regmap, STUSB_ALERT_STATUS_MASK_CTRL, + STUSB_CC_CONNECTION, 0); + if (ret) + goto partner_unregister; + + return 0; + +partner_unregister: + if (chip->partner) { + typec_unregister_partner(chip->partner); + chip->partner = NULL; + } + + return ret; +} + +static int stusb_init(struct stusb *chip) +{ + u32 val; + int ret; + + /* Change the default Type-C power mode */ + if (chip->port_type == TYPEC_PORT_SRC) + ret = regmap_update_bits(chip->regmap, + STUSB_CC_POWER_MODE_CTRL, + STUSB_CC_POWER_MODE, + SOURCE_WITH_ACCESSORY); + else if (chip->port_type == TYPEC_PORT_SNK) + ret = regmap_update_bits(chip->regmap, + STUSB_CC_POWER_MODE_CTRL, + STUSB_CC_POWER_MODE, + SINK_WITH_ACCESSORY); + else /* (capability->type == TYPEC_PORT_DRP) */ + ret = regmap_update_bits(chip->regmap, + STUSB_CC_POWER_MODE_CTRL, + STUSB_CC_POWER_MODE, + DUAL_WITH_ACCESSORY); + if (ret) + return ret; + + if (chip->port_type == TYPEC_PORT_SNK) + goto skip_src; + + /* Change the default Type-C Source power operation mode capability */ + ret = regmap_update_bits(chip->regmap, STUSB_CC_CAPABILITY_CTRL, + STUSB_CC_CURRENT_ADVERTISED, + FIELD_PREP(STUSB_CC_CURRENT_ADVERTISED, + chip->pwr_opmode)); + if (ret) + return ret; + + /* Manage Type-C Source Vconn supply */ + if (stusb_get_vconn(chip)) { + ret = stusb_set_vconn(chip, true); + if (ret) + return ret; + } + +skip_src: + /* Mask all events interrupts - to be unmasked with interrupt support */ + ret = regmap_update_bits(chip->regmap, STUSB_ALERT_STATUS_MASK_CTRL, + STUSB_ALL_ALERTS, STUSB_ALL_ALERTS); + if (ret) + return ret; + + /* Read status at least once to clear any stale interrupts */ + regmap_read(chip->regmap, STUSB_ALERT_STATUS, &val); + regmap_read(chip->regmap, STUSB_CC_CONNECTION_STATUS_TRANS, &val); + regmap_read(chip->regmap, STUSB_MONITORING_STATUS_TRANS, &val); + regmap_read(chip->regmap, STUSB_HW_FAULT_STATUS_TRANS, &val); + + return 0; +} + +static int stusb_fw_get_caps(struct stusb *chip) +{ + struct fwnode_handle *fwnode = device_get_named_child_node(chip->dev, + "connector"); + const char *cap_str; + int ret; + + if (!fwnode) + return -EINVAL; + + chip->capability.fwnode = fwnode; + + ret = fwnode_property_read_string(fwnode, "power-role", &cap_str); + if (!ret) { + chip->port_type = typec_find_port_power_role(cap_str); + if (chip->port_type < 0) + return -EINVAL; + + chip->capability.type = chip->port_type; + } + + if (chip->port_type == TYPEC_PORT_SNK) + goto sink; + + if (chip->port_type == TYPEC_PORT_DRP) + chip->capability.prefer_role = TYPEC_SINK; + + ret = fwnode_property_read_string(fwnode, "power-opmode", &cap_str); + if (!ret) { + chip->pwr_opmode = typec_find_port_power_opmode(cap_str); + + /* Power delivery not yet supported */ + if (chip->pwr_opmode < 0 || + chip->pwr_opmode == TYPEC_PWR_MODE_PD) { + dev_err(chip->dev, "bad power operation mode: %d\n", + chip->pwr_opmode); + return -EINVAL; + } + + } else { + chip->pwr_opmode = stusb_get_pwr_opmode(chip); + } + +sink: + return 0; +} + +static int stusb_get_caps(struct stusb *chip, bool *try) +{ + enum typec_port_type *type = &chip->capability.type; + enum typec_port_data *data = &chip->capability.data; + enum typec_accessory *accessory = chip->capability.accessory; + u32 val; + int ret; + + chip->capability.revision = USB_TYPEC_REV_1_2; + + ret = regmap_read(chip->regmap, STUSB_CC_POWER_MODE_CTRL, &val); + if (ret) + return ret; + + *try = false; + + switch (FIELD_GET(STUSB_CC_POWER_MODE, val)) { + case SOURCE_WITH_ACCESSORY: + *type = TYPEC_PORT_SRC; + *data = TYPEC_PORT_DFP; + *accessory++ = TYPEC_ACCESSORY_AUDIO; + *accessory++ = TYPEC_ACCESSORY_DEBUG; + break; + case SINK_WITH_ACCESSORY: + *type = TYPEC_PORT_SNK; + *data = TYPEC_PORT_UFP; + *accessory++ = TYPEC_ACCESSORY_AUDIO; + *accessory++ = TYPEC_ACCESSORY_DEBUG; + break; + case SINK_WITHOUT_ACCESSORY: + *type = TYPEC_PORT_SNK; + *data = TYPEC_PORT_UFP; + break; + case DUAL_WITH_ACCESSORY: + *type = TYPEC_PORT_DRP; + *data = TYPEC_PORT_DRD; + *accessory++ = TYPEC_ACCESSORY_AUDIO; + *accessory++ = TYPEC_ACCESSORY_DEBUG; + break; + case DUAL_WITH_ACCESSORY_AND_TRY_SRC: + case DUAL_WITH_ACCESSORY_AND_TRY_SNK: + *type = TYPEC_PORT_DRP; + *data = TYPEC_PORT_DRD; + *accessory++ = TYPEC_ACCESSORY_AUDIO; + *accessory++ = TYPEC_ACCESSORY_DEBUG; + *try = true; + break; + default: + return -EINVAL; + } + + chip->port_type = *type; + + return stusb_fw_get_caps(chip); +} + +static const struct of_device_id stusb_of_match[] = { + { .compatible = "st,stusb1600", .data = &stusb1600_regmap_config}, + {}, +}; + +static int stusb_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct stusb *chip; + const struct of_device_id *match; + struct regmap_config *regmap_config; + bool try_role; + struct fwnode_handle *fwnode; + int ret; + + chip = devm_kzalloc(&client->dev, sizeof(struct stusb), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + i2c_set_clientdata(client, chip); + + match = i2c_of_match_device(stusb_of_match, client); + regmap_config = (struct regmap_config *)match->data; + chip->regmap = devm_regmap_init_i2c(client, regmap_config); + if (IS_ERR(chip->regmap)) { + ret = PTR_ERR(chip->regmap); + dev_err(&client->dev, + "Failed to allocate register map:%d\n", ret); + return ret; + } + + chip->dev = &client->dev; + + chip->vsys_supply = devm_regulator_get_optional(chip->dev, "vsys"); + if (IS_ERR(chip->vsys_supply)) { + ret = PTR_ERR(chip->vsys_supply); + if (ret != -ENODEV) + return ret; + chip->vsys_supply = NULL; + } + + chip->vdd_supply = devm_regulator_get_optional(chip->dev, "vdd"); + if (IS_ERR(chip->vdd_supply)) { + ret = PTR_ERR(chip->vdd_supply); + if (ret != -ENODEV) + return ret; + chip->vdd_supply = NULL; + } + + chip->vconn_supply = devm_regulator_get_optional(chip->dev, "vconn"); + if (IS_ERR(chip->vconn_supply)) { + ret = PTR_ERR(chip->vconn_supply); + if (ret != -ENODEV) + return ret; + chip->vconn_supply = NULL; + } + + /* + * When both VDD and VSYS power supplies are present, the low power + * supply VSYS is selected when VSYS voltage is above 3.1 V. + * Otherwise VDD is selected. + */ + if (chip->vdd_supply && + (!chip->vsys_supply || + (regulator_get_voltage(chip->vsys_supply) <= 3100000))) + chip->main_supply = chip->vdd_supply; + else + chip->main_supply = chip->vsys_supply; + + if (chip->main_supply) { + ret = regulator_enable(chip->main_supply); + if (ret) { + dev_err(chip->dev, + "Failed to enable main supply: %d\n", ret); + return ret; + } + } + + ret = stusb_get_caps(chip, &try_role); + if (ret) { + dev_err(chip->dev, "Failed to get port caps: %d\n", ret); + goto main_reg_disable; + } + + ret = stusb_init(chip); + if (ret) { + dev_err(chip->dev, "Failed to init port: %d\n", ret); + goto main_reg_disable; + } + + chip->port = typec_register_port(chip->dev, &chip->capability); + if (!chip->port) { + ret = -ENODEV; + goto all_reg_disable; + } + + /* + * Default power operation mode initialization: will be updated upon + * attach/detach interrupt + */ + typec_set_pwr_opmode(chip->port, chip->pwr_opmode); + + if (!client->irq) { + /* + * If Source or Dual power role, need to enable VDD supply + * providing Vbus if present. In case of interrupt support, + * VDD supply will be dynamically managed upon attach/detach + * interrupt. + */ + if ((chip->port_type != TYPEC_PORT_SNK) && chip->vdd_supply) { + ret = regulator_enable(chip->vdd_supply); + if (ret) { + dev_err(chip->dev, + "Failed to enable VDD supply: %d\n", + ret); + goto port_unregister; + } + chip->vbus_on = true; + } + + return 0; + } + + fwnode = device_get_named_child_node(chip->dev, "connector"); + chip->role_sw = fwnode_usb_role_switch_get(fwnode); + if (IS_ERR(chip->role_sw)) { + ret = PTR_ERR(chip->role_sw); + if (ret != -EPROBE_DEFER) + dev_err(chip->dev, + "Failed to get usb role switch: %d\n", ret); + goto port_unregister; + } + + if (chip->role_sw) + INIT_WORK(&chip->wq_role_sw, stusb_set_role_sw); + + ret = stusb_irq_init(chip, client->irq); + if (ret) + goto role_sw_put; + + return 0; + +role_sw_put: + if (chip->role_sw) { + cancel_work_sync(&chip->wq_role_sw); + usb_role_switch_put(chip->role_sw); + } +port_unregister: + typec_unregister_port(chip->port); +all_reg_disable: + if (stusb_get_vconn(chip)) + stusb_set_vconn(chip, false); +main_reg_disable: + if (chip->main_supply) + regulator_disable(chip->main_supply); + + return ret; +} + +static int stusb_remove(struct i2c_client *client) +{ + struct stusb *chip = i2c_get_clientdata(client); + + if (chip->partner) { + typec_unregister_partner(chip->partner); + chip->partner = NULL; + } + + if (chip->vbus_on) + regulator_disable(chip->vdd_supply); + + if (chip->role_sw) { + cancel_work_sync(&chip->wq_role_sw); + usb_role_switch_put(chip->role_sw); + } + + typec_unregister_port(chip->port); + + if (stusb_get_vconn(chip)) + stusb_set_vconn(chip, false); + + if (chip->main_supply) + regulator_disable(chip->main_supply); + + return 0; +} + +static int __maybe_unused stusb_suspend(struct device *dev) +{ + struct stusb *chip = dev_get_drvdata(dev); + + /* Mask interrupts */ + return regmap_update_bits(chip->regmap, STUSB_ALERT_STATUS_MASK_CTRL, + STUSB_ALL_ALERTS, STUSB_ALL_ALERTS); +} + +static int __maybe_unused stusb_resume(struct device *dev) +{ + struct stusb *chip = dev_get_drvdata(dev); + u32 status; + int ret; + + ret = regcache_sync(chip->regmap); + if (ret) + return ret; + + /* Check if attach/detach occurred during low power */ + ret = regmap_read(chip->regmap, STUSB_CC_CONNECTION_STATUS, &status); + if (ret) + return ret; + + if (chip->partner && !(status & STUSB_CC_ATTACH)) + stusb_detach(chip, status); + + if (!chip->partner && (status & STUSB_CC_ATTACH)) { + ret = stusb_attach(chip, status); + if (ret) + dev_err(chip->dev, "attach failed: %d\n", ret); + } + + /* Unmask interrupts */ + return regmap_write_bits(chip->regmap, STUSB_ALERT_STATUS_MASK_CTRL, + STUSB_CC_CONNECTION, 0); +} + +static SIMPLE_DEV_PM_OPS(stusb_pm_ops, stusb_suspend, stusb_resume); + +static struct i2c_driver stusb_driver = { + .driver = { + .name = "typec_stusb", + .pm = &stusb_pm_ops, + .of_match_table = stusb_of_match, + }, + .probe = stusb_probe, + .remove = stusb_remove, +}; +module_i2c_driver(stusb_driver); + +MODULE_AUTHOR("Amelie Delaunay "); +MODULE_DESCRIPTION("STMicroelectronics STUSB Type-C controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/phy/phy.h b/include/linux/phy/phy.h index 15032f1450631..99ec370a6ff50 100644 --- a/include/linux/phy/phy.h +++ b/include/linux/phy/phy.h @@ -233,7 +233,8 @@ struct phy *devm_of_phy_get(struct device *dev, struct device_node *np, const char *con_id); struct phy *devm_of_phy_get_by_index(struct device *dev, struct device_node *np, int index); -void phy_put(struct phy *phy); +void of_phy_put(struct phy *phy); +void phy_put(struct device *dev, struct phy *phy); void devm_phy_put(struct device *dev, struct phy *phy); struct phy *of_phy_get(struct device_node *np, const char *con_id); struct phy *of_phy_simple_xlate(struct device *dev, @@ -418,7 +419,11 @@ static inline struct phy *devm_of_phy_get_by_index(struct device *dev, return ERR_PTR(-ENOSYS); } -static inline void phy_put(struct phy *phy) +static inline void of_phy_put(struct phy *phy) +{ +} + +static inline void phy_put(struct device *dev, struct phy *phy) { } diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index 7df4ecabc78a2..2671776a1f8ea 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -241,6 +241,7 @@ int typec_set_orientation(struct typec_port *port, enum typec_orientation typec_get_orientation(struct typec_port *port); int typec_set_mode(struct typec_port *port, int mode); +int typec_find_port_power_opmode(const char *name); int typec_find_port_power_role(const char *name); int typec_find_power_role(const char *name); int typec_find_port_data_role(const char *name); -- 2.17.1