From 5b9dd736aa00396dcf9907ada93424956604a4f1 Mon Sep 17 00:00:00 2001
From: Ondrej Jirman <megi@xff.cz>
Date: Mon, 17 Mar 2025 10:57:59 +0100
Subject: [PATCH 366/480] phy: rockchip: naneng: Add fallback for old DTs

See https://lore.kernel.org/lkml/20250106070000.605284-1-amadeus@jmu.edu.cn/

Signed-off-by: Ondrej Jirman <megi@xff.cz>
---
 drivers/usb/dwc3/core.c | 46 ++++++++++++++++++++++++++++++++++++++---
 drivers/usb/dwc3/core.h | 12 +++++++++++
 drivers/usb/dwc3/drd.c  | 34 +++++++++++++++++++-----------
 3 files changed, 77 insertions(+), 15 deletions(-)

diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 40ed8850af71..75183a630b0b 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -150,7 +150,7 @@ void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode, bool ignore_susphy)
 	}
 
 	reg &= ~(DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_OTG));
-	reg |= DWC3_GCTL_PRTCAPDIR(mode);
+	reg |= DWC3_GCTL_PRTCAPDIR(mode & DWC3_GCTL_PRTCAP_OTG);
 	dwc3_writel(dwc->regs, DWC3_GCTL, reg);
 
 	dwc->current_dr_role = mode;
@@ -189,6 +189,7 @@ static void __dwc3_set_mode(struct work_struct *work)
 		dwc3_host_exit(dwc);
 		break;
 	case DWC3_GCTL_PRTCAP_DEVICE:
+	case DWC3_GCTL_PRTCAP_DEVICE_DISCONNECTED:
 		dwc3_gadget_exit(dwc);
 		dwc3_event_buffers_cleanup(dwc);
 		break;
@@ -208,12 +209,43 @@ static void __dwc3_set_mode(struct work_struct *work)
 	 * Only perform GCTL.CoreSoftReset when there's DRD role switching.
 	 */
 	if (dwc->current_dr_role && ((DWC3_IP_IS(DWC3) ||
-			DWC3_VER_IS_PRIOR(DWC31, 190A)) &&
+			DWC3_VER_IS_PRIOR(DWC31, 190A) || dwc->usb3_phy_reset_quirk) &&
 			desired_dr_role != DWC3_GCTL_PRTCAP_OTG)) {
+		/*
+		 * RK3399 TypeC PHY needs to be powered off and powered on again
+		 * for it to apply the correct Type-C plug orientation setting
+		 * and reconfigure itself.
+		 *
+		 * For that purpose we observe complete USB disconnect via
+		 * extcon in drd.c and pass it to __dwc3_set_mode as
+		 * desired_dr_role == 0.
+		 *
+		 * We thus handle transitions between three states of
+		 * desired_dr_role here:
+		 *
+		 * - DWC3_GCTL_PRTCAP_HOST
+		 * - DWC3_GCTL_PRTCAP_DEVICE
+		 * - DWC3_GCTL_PRTCAP_DEVICE_DISCONNECTED - almost equivalent to
+		 *   DWC3_GCTL_PRTCAP_DEVICE, present only to distinguish
+		 *   disconnected state, and so that set_mode is called when
+		 *   user plugs in the device to the host.
+		 */
+		if (dwc->usb3_phy_powered && dwc->usb3_phy_reset_quirk)
+			for (int j = 0; j < dwc->num_usb3_ports; j++)
+				phy_power_off(dwc->usb3_generic_phy[j]);
+
 		reg = dwc3_readl(dwc->regs, DWC3_GCTL);
 		reg |= DWC3_GCTL_CORESOFTRESET;
 		dwc3_writel(dwc->regs, DWC3_GCTL, reg);
 
+		if (dwc->usb3_phy_reset_quirk) {
+			for (int j = 0; j < dwc->num_usb3_ports; j++) {
+				ret = phy_power_on(dwc->usb3_generic_phy[j]);
+				//XXX: bleh
+				dwc->usb3_phy_powered = ret >= 0;
+			}
+		}
+
 		/*
 		 * Wait for internal clocks to synchronized. DWC_usb31 and
 		 * DWC_usb32 may need at least 50ms (less for DWC_usb3). To
@@ -255,6 +287,7 @@ static void __dwc3_set_mode(struct work_struct *work)
 		}
 		break;
 	case DWC3_GCTL_PRTCAP_DEVICE:
+	case DWC3_GCTL_PRTCAP_DEVICE_DISCONNECTED:
 		dwc3_core_soft_reset(dwc);
 
 		dwc3_event_buffers_setup(dwc);
@@ -1835,6 +1868,8 @@ static void dwc3_get_properties(struct dwc3 *dwc)
 
 	dwc->dis_split_quirk = device_property_read_bool(dev,
 				"snps,dis-split-quirk");
+	dwc->usb3_phy_reset_quirk = device_property_read_bool(dev,
+				"snps,usb3-phy-reset-quirk");
 
 	dwc->lpm_nyet_threshold = lpm_nyet_threshold;
 	dwc->tx_de_emphasis = tx_de_emphasis;
@@ -2407,6 +2442,7 @@ static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg)
 
 	switch (dwc->current_dr_role) {
 	case DWC3_GCTL_PRTCAP_DEVICE:
+	case DWC3_GCTL_PRTCAP_DEVICE_DISCONNECTED:
 		if (pm_runtime_suspended(dwc->dev))
 			break;
 		dwc3_gadget_suspend(dwc);
@@ -2467,11 +2503,12 @@ static int dwc3_resume_common(struct dwc3 *dwc, pm_message_t msg)
 
 	switch (dwc->current_dr_role) {
 	case DWC3_GCTL_PRTCAP_DEVICE:
+	case DWC3_GCTL_PRTCAP_DEVICE_DISCONNECTED:
 		ret = dwc3_core_init_for_resume(dwc);
 		if (ret)
 			return ret;
 
-		dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE, true);
+		dwc3_set_prtcap(dwc, dwc->current_dr_role, true);
 		dwc3_gadget_resume(dwc);
 		break;
 	case DWC3_GCTL_PRTCAP_HOST:
@@ -2535,6 +2572,7 @@ static int dwc3_runtime_checks(struct dwc3 *dwc)
 {
 	switch (dwc->current_dr_role) {
 	case DWC3_GCTL_PRTCAP_DEVICE:
+	case DWC3_GCTL_PRTCAP_DEVICE_DISCONNECTED:
 		if (dwc->connected)
 			return -EBUSY;
 		break;
@@ -2573,6 +2611,7 @@ static int dwc3_runtime_resume(struct device *dev)
 
 	switch (dwc->current_dr_role) {
 	case DWC3_GCTL_PRTCAP_DEVICE:
+	case DWC3_GCTL_PRTCAP_DEVICE_DISCONNECTED:
 		if (dwc->pending_events) {
 			pm_runtime_put(dwc->dev);
 			dwc->pending_events = false;
@@ -2596,6 +2635,7 @@ static int dwc3_runtime_idle(struct device *dev)
 
 	switch (dwc->current_dr_role) {
 	case DWC3_GCTL_PRTCAP_DEVICE:
+	case DWC3_GCTL_PRTCAP_DEVICE_DISCONNECTED:
 		if (dwc3_runtime_checks(dwc))
 			return -EBUSY;
 		break;
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index f955d8475db7..dc1099a85542 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -264,6 +264,12 @@
 #define DWC3_GCTL_PRTCAP_HOST	1
 #define DWC3_GCTL_PRTCAP_DEVICE	2
 #define DWC3_GCTL_PRTCAP_OTG	3
+/* This is not a real register value, but a special state used for
+ * current_dr_role to mean DWC3_GCTL_PRTCAP_DEVICE in disconnected
+ * state. Value is chosen so that masking with register width
+ * produces DWC3_GCTL_PRTCAP_DEVICE value.
+ */
+#define DWC3_GCTL_PRTCAP_DEVICE_DISCONNECTED	6
 
 #define DWC3_GCTL_CORESOFTRESET		BIT(11)
 #define DWC3_GCTL_SOFITPSYNC		BIT(10)
@@ -1151,6 +1157,10 @@ struct dwc3_scratchpad_array {
  * @sys_wakeup: set if the device may do system wakeup.
  * @wakeup_configured: set if the device is configured for remote wakeup.
  * @suspended: set to track suspend event due to U3/L2.
+ * @usb3_phy_reset_quirk: set to power cycle the USB3 PHY during mode
+ *                        changes. Useful on RK3399 that needs this
+ *                        to apply Type-C orientation changes in
+ *                        Type-C phy driver.
  * @susphy_state: state of DWC3_GUSB2PHYCFG_SUSPHY + DWC3_GUSB3PIPECTL_SUSPHY
  *		  before PM suspend.
  * @imod_interval: set the interrupt moderation interval in 250ns
@@ -1389,6 +1399,8 @@ struct dwc3 {
 	unsigned		suspended:1;
 	unsigned		susphy_state:1;
 
+	unsigned		usb3_phy_reset_quirk:1;
+
 	u16			imod_interval;
 
 	int			max_cfg_eps;
diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c
index 7977860932b1..d01506997a1d 100644
--- a/drivers/usb/dwc3/drd.c
+++ b/drivers/usb/dwc3/drd.c
@@ -417,15 +417,28 @@ void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus)
 
 static void dwc3_drd_update(struct dwc3 *dwc)
 {
-	int id;
+	u32 mode = DWC3_GCTL_PRTCAP_DEVICE_DISCONNECTED;
+	int ret;
 
 	if (dwc->edev) {
-		id = extcon_get_state(dwc->edev, EXTCON_USB_HOST);
-		if (id < 0)
-			id = 0;
-		dwc3_set_mode(dwc, id ?
-			      DWC3_GCTL_PRTCAP_HOST :
-			      DWC3_GCTL_PRTCAP_DEVICE);
+		ret = extcon_get_state(dwc->edev, EXTCON_USB_HOST);
+		if (ret > 0)
+			mode = DWC3_GCTL_PRTCAP_HOST;
+
+		if (dwc->usb3_phy_reset_quirk) {
+			/*
+			 * With this quirk enabled, we want to pass 0
+			 * to dwc3_set_mode to signal no USB connection
+			 * state.
+			 */
+			ret = extcon_get_state(dwc->edev, EXTCON_USB);
+			if (ret > 0)
+				mode = DWC3_GCTL_PRTCAP_DEVICE;
+		} else {
+			mode = DWC3_GCTL_PRTCAP_DEVICE;
+		}
+
+		dwc3_set_mode(dwc, mode);
 	}
 }
 
@@ -434,9 +447,7 @@ static int dwc3_drd_notifier(struct notifier_block *nb,
 {
 	struct dwc3 *dwc = container_of(nb, struct dwc3, edev_nb);
 
-	dwc3_set_mode(dwc, event ?
-		      DWC3_GCTL_PRTCAP_HOST :
-		      DWC3_GCTL_PRTCAP_DEVICE);
+	dwc3_drd_update(dwc);
 
 	return NOTIFY_DONE;
 }
@@ -547,8 +558,7 @@ int dwc3_drd_init(struct dwc3 *dwc)
 
 	if (dwc->edev) {
 		dwc->edev_nb.notifier_call = dwc3_drd_notifier;
-		ret = extcon_register_notifier(dwc->edev, EXTCON_USB_HOST,
-					       &dwc->edev_nb);
+		ret = extcon_register_notifier_all(dwc->edev, &dwc->edev_nb);
 		if (ret < 0) {
 			dev_err(dwc->dev, "couldn't register cable notifier\n");
 			return ret;
-- 
2.49.0

