meta-st-stm32mp/recipes-kernel/linux/linux-stm32mp/5.4/5.4.56/0016-ARM-stm32mp1-r2-PHY-US...

3175 lines
93 KiB
Diff

From 95aafdb42d1f3aa59f114a7f076949abac53c38d Mon Sep 17 00:00:00 2001
From: Lionel VITTE <lionel.vitte@st.com>
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 = <&reg11>;
+ vdda1v8-supply = <&reg18>;
#address-cells = <1>;
#size-cells = <0>;
+ #clock-cells = <0>;
usbphyc_port0: usb-phy@0 {
reg = <0>;
phy-supply = <&vdd_usb>;
- vdda1v1-supply = <&reg11>;
- vdda1v8-supply = <&reg18>
#phy-cells = <0>;
};
usbphyc_port1: usb-phy@1 {
reg = <1>;
phy-supply = <&vdd_usb>;
- vdda1v1-supply = <&reg11>;
- vdda1v8-supply = <&reg18>
#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 <linux/bitfield.h>
#include <linux/clk.h>
+#include <linux/clk-provider.h>
#include <linux/delay.h>
-#include <linux/io.h>
+#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_platform.h>
@@ -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 <amelie.delaunay@st.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/iopoll.h>
+#include <linux/platform_device.h>
+#include <linux/usb/role.h>
+#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 <linux/usb/hcd.h>
#include <linux/usb/ehci_pdriver.h>
#include <linux/usb/of.h>
+#include <linux/pm_wakeirq.h>
#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 <amelie.delaunay@st.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/usb/role.h>
+#include <linux/usb/typec.h>
+
+#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 <amelie.delaunay@st.com>");
+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