3175 lines
93 KiB
Diff
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 = <®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 <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
|
|
|