From a1f841a33c4e0b90721468a28030cecdb1cfd953 Mon Sep 17 00:00:00 2001
From: Marek Vasut <marek.vasut+renesas@mailbox.org>
Date: Sun, 8 Sep 2024 23:09:04 +0200
Subject: [PATCH] phy: rcar: Split init and set_mode operations

The current init operation also sets the PHY into USB host mode.
Split the mode configuration into set_mode callback instead and
implement support for device and OTG modes as well.

The OTG mode performs auto-detection and selects either host or
device mode. In case the OTG mode is configured, submode field
can be used to select full PHY (re)initialization or only mode
auto-detection. The full (re)initialization is only necessary
once, on start up.

Since the OTG mode may enable IRQ generation in the PHY, disable
that IRQ generation in the exit callback again.

Reviewed-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
Signed-off-by: Marek Vasut <marek.vasut+renesas@mailbox.org>
---
 drivers/phy/phy-rcar-gen3.c | 90 ++++++++++++++++++++++++++++++++++---
 1 file changed, 85 insertions(+), 5 deletions(-)

diff --git a/drivers/phy/phy-rcar-gen3.c b/drivers/phy/phy-rcar-gen3.c
index 7c292cae0e..b278f995f3 100644
--- a/drivers/phy/phy-rcar-gen3.c
+++ b/drivers/phy/phy-rcar-gen3.c
@@ -8,6 +8,7 @@
 #include <clk.h>
 #include <div64.h>
 #include <dm.h>
+#include <dm/device_compat.h>
 #include <fdtdec.h>
 #include <generic-phy.h>
 #include <malloc.h>
@@ -31,8 +32,13 @@
 #define USB2_LINECTRL1		0x610
 #define USB2_ADPCTRL		0x630
 
+/* INT_ENABLE */
+#define USB2_INT_ENABLE_UCOM_INTEN	BIT(3)
+#define USB2_INT_ENABLE_USBH_INTB_EN	BIT(2)
+#define USB2_INT_ENABLE_USBH_INTA_EN	BIT(1)
+
 /* USBCTR */
-#define USB2_USBCTR_PLL_RST	BIT(1)
+#define USB2_USBCTR_PLL_RST		BIT(1)
 
 /* SPD_RSM_TIMSET */
 #define USB2_SPD_RSM_TIMSET_INIT	0x014e029b
@@ -43,11 +49,23 @@
 /* COMMCTRL */
 #define USB2_COMMCTRL_OTG_PERI		BIT(31)	/* 1 = Peripheral mode */
 
+/* OBINTSTA and OBINTEN */
+#define USB2_OBINT_SESSVLDCHG		BIT(12)
+#define USB2_OBINT_IDDIGCHG		BIT(11)
+
+/* VBCTRL */
+#define USB2_VBCTRL_DRVVBUSSEL		BIT(8)
+
 /* LINECTRL1 */
+#define USB2_LINECTRL1_DPRPD_EN		BIT(19)
 #define USB2_LINECTRL1_DP_RPD		BIT(18)
+#define USB2_LINECTRL1_DMRPD_EN		BIT(17)
 #define USB2_LINECTRL1_DM_RPD		BIT(16)
 
 /* ADPCTRL */
+#define USB2_ADPCTRL_OTGSESSVLD		BIT(20)
+#define USB2_ADPCTRL_IDDIG		BIT(19)
+#define USB2_ADPCTRL_IDPULLUP		BIT(5)	/* 1 = ID sampling is enabled */
 #define USB2_ADPCTRL_DRVVBUS		BIT(4)
 
 struct rcar_gen3_phy {
@@ -65,12 +83,14 @@ static int rcar_gen3_phy_phy_init(struct phy *phy)
 	writel(USB2_SPD_RSM_TIMSET_INIT, priv->regs + USB2_SPD_RSM_TIMSET);
 	writel(USB2_OC_TIMSET_INIT, priv->regs + USB2_OC_TIMSET);
 
-	setbits_le32(priv->regs + USB2_LINECTRL1,
-		     USB2_LINECTRL1_DP_RPD | USB2_LINECTRL1_DM_RPD);
+	return 0;
+}
 
-	clrbits_le32(priv->regs + USB2_COMMCTRL, USB2_COMMCTRL_OTG_PERI);
+static int rcar_gen3_phy_phy_exit(struct phy *phy)
+{
+	struct rcar_gen3_phy *priv = dev_get_priv(phy->dev);
 
-	setbits_le32(priv->regs + USB2_ADPCTRL, USB2_ADPCTRL_DRVVBUS);
+	writel(0, priv->regs + USB2_INT_ENABLE);
 
 	return 0;
 }
@@ -102,10 +122,70 @@ static int rcar_gen3_phy_phy_power_off(struct phy *phy)
 	return regulator_set_enable(priv->vbus_supply, false);
 }
 
+static int rcar_gen3_phy_phy_set_mode(struct phy *phy, enum phy_mode mode,
+				      int submode)
+{
+	const u32 adpdevmask = USB2_ADPCTRL_IDDIG | USB2_ADPCTRL_OTGSESSVLD;
+	struct rcar_gen3_phy *priv = dev_get_priv(phy->dev);
+	u32 adpctrl;
+
+	if (mode == PHY_MODE_USB_OTG) {
+		if (submode) {
+			/* OTG submode is used as initialization indicator */
+			writel(USB2_INT_ENABLE_UCOM_INTEN |
+			       USB2_INT_ENABLE_USBH_INTB_EN |
+			       USB2_INT_ENABLE_USBH_INTA_EN,
+			       priv->regs + USB2_INT_ENABLE);
+			setbits_le32(priv->regs + USB2_VBCTRL,
+				     USB2_VBCTRL_DRVVBUSSEL);
+			writel(USB2_OBINT_SESSVLDCHG | USB2_OBINT_IDDIGCHG,
+			       priv->regs + USB2_OBINTSTA);
+			setbits_le32(priv->regs + USB2_OBINTEN,
+				     USB2_OBINT_SESSVLDCHG |
+				     USB2_OBINT_IDDIGCHG);
+			setbits_le32(priv->regs + USB2_ADPCTRL,
+				     USB2_ADPCTRL_IDPULLUP);
+			clrsetbits_le32(priv->regs + USB2_LINECTRL1,
+					USB2_LINECTRL1_DP_RPD |
+					USB2_LINECTRL1_DM_RPD |
+					USB2_LINECTRL1_DPRPD_EN |
+					USB2_LINECTRL1_DMRPD_EN,
+					USB2_LINECTRL1_DPRPD_EN |
+					USB2_LINECTRL1_DMRPD_EN);
+		}
+
+		adpctrl = readl(priv->regs + USB2_ADPCTRL);
+		if ((adpctrl & adpdevmask) == adpdevmask)
+			mode = PHY_MODE_USB_DEVICE;
+		else
+			mode = PHY_MODE_USB_HOST;
+	}
+
+	if (mode == PHY_MODE_USB_HOST) {
+		clrbits_le32(priv->regs + USB2_COMMCTRL, USB2_COMMCTRL_OTG_PERI);
+		setbits_le32(priv->regs + USB2_LINECTRL1,
+			     USB2_LINECTRL1_DP_RPD | USB2_LINECTRL1_DM_RPD);
+		setbits_le32(priv->regs + USB2_ADPCTRL, USB2_ADPCTRL_DRVVBUS);
+	} else if (mode == PHY_MODE_USB_DEVICE) {
+		setbits_le32(priv->regs + USB2_COMMCTRL, USB2_COMMCTRL_OTG_PERI);
+		clrsetbits_le32(priv->regs + USB2_LINECTRL1,
+				USB2_LINECTRL1_DP_RPD | USB2_LINECTRL1_DM_RPD,
+				USB2_LINECTRL1_DM_RPD);
+		clrbits_le32(priv->regs + USB2_ADPCTRL, USB2_ADPCTRL_DRVVBUS);
+	} else {
+		dev_err(phy->dev, "Unknown mode %d\n", mode);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
 static const struct phy_ops rcar_gen3_phy_phy_ops = {
 	.init		= rcar_gen3_phy_phy_init,
+	.exit		= rcar_gen3_phy_phy_exit,
 	.power_on	= rcar_gen3_phy_phy_power_on,
 	.power_off	= rcar_gen3_phy_phy_power_off,
+	.set_mode	= rcar_gen3_phy_phy_set_mode,
 };
 
 static int rcar_gen3_phy_probe(struct udevice *dev)
-- 
2.39.5