diff mbox series

[7/7] phy: spacemit: k1: power on the combo PHY in USB 3 mode

Message ID e80f6161ee95cee2c84e6bb1d98ddee1070e7a13.1790975597.git.DaveWK225@protonmail.com
State New
Delegated to: Tim Ouyang
Headers show
Series phy: spacemit: k1: fix the PCIe and combo PHY driver | expand

Commit Message

Dave Klotz Oct. 2, 2026, 9:30 p.m. UTC
In USB 3 mode the combo PHY's init only sets COMBO_PHY_SEL. Nothing
programs or starts the PHY PLL, so the DWC3 controller gets no PIPE
clock and its xHCI reset (USBCMD.HCRST) never completes.

Linux's k1_pcie_phy_init_usb() selects USB 3 mode and zeroes
USB3_TEST_CTRL, its combo PHY probe leaves APP_HOLD_PHY_RST clear,
and its PLL prepare for USB sets CFG_INTERNAL_TIMER_ADJ to the USB
value, selects 5000 ppm spread spectrum and the 24 MHz reference
without the 100 MHz SSC input, sets CFG_SW_PHY_INIT_DONE and waits
for PLL_READY. Do the same, and also clear CFG_FORCE_RCV_RETRY, which
is for PCIe only and may have been left set by an earlier stage.
Return -ETIMEDOUT if the PLL does not lock.

Do it at power-on rather than at init: the DWC3 glue initializes
its USB 3 PHY before it enables the controller's clock and releases
its resets, and powers it on afterwards, while Linux starts this PLL
with the controller already running. PCIe mode is unchanged.

Signed-off-by: Dave Klotz <DaveWK225@protonmail.com>
---
 drivers/phy/spacemit/phy-k1-pcie.c | 84 ++++++++++++++++++++++++++++--
 1 file changed, 80 insertions(+), 4 deletions(-)
diff mbox series

Patch

diff --git a/drivers/phy/spacemit/phy-k1-pcie.c b/drivers/phy/spacemit/phy-k1-pcie.c
index c3d29d6a2a..6ba8575ef6 100644
--- a/drivers/phy/spacemit/phy-k1-pcie.c
+++ b/drivers/phy/spacemit/phy-k1-pcie.c
@@ -39,6 +39,7 @@ 
 #define PCIE_PU_ADDR_CLK_CFG		0x0008
 #define PLL_READY			BIT(0)
 #define CFG_INTERNAL_TIMER_ADJ		GENMASK(10, 7)
+#define TIMER_ADJ_USB			0x2
 #define TIMER_ADJ_PCIE			0x6
 #define CFG_SW_PHY_INIT_DONE		BIT(11)
 
@@ -58,6 +59,7 @@ 
 #define FREF_24M			0x1
 #define SSC_DEP_SEL			GENMASK(19, 16)
 #define SSC_DEP_NONE			0x0
+#define SSC_DEP_5000PPM			0xa
 
 /* PCIe PHY configuration */
 #define PCIE_PU_PLL_2			0x004c
@@ -80,6 +82,9 @@ 
 #define TX_RTERM_REG			GENMASK(15, 12)
 #define TX_RTERM_SEL			BIT(25)
 
+/* Zeroed for the combo PHY operating in USB 3 mode */
+#define USB3_TEST_CTRL			0x0068
+
 /* PHY calibration values */
 #define PCIE_RCAL_RESULT		0x0084
 #define RTERM_VALUE_RX			GENMASK(3, 0)
@@ -216,11 +221,9 @@  static int k1_pcie_phy_init(struct phy *phy)
 	u32 val;
 	int i, lane;
 
-	/* If combo PHY is configured for USB 3 mode */
-	if (k1_phy->is_combo && phy->id == PHY_TYPE_USB3) {
-		k1_combo_phy_sel(k1_phy, true);
+	/* USB 3 mode is set up at power-on, see k1_pcie_phy_power_on() */
+	if (k1_phy->is_combo && phy->id == PHY_TYPE_USB3)
 		return 0;
-	}
 
 	/* For combo PHY, ensure it's in PCIe mode */
 	if (k1_phy->is_combo)
@@ -364,6 +367,78 @@  static int k1_pcie_phy_init(struct phy *phy)
 	return 0;
 }
 
+/* Only called for the combo PHY */
+static int k1_combo_phy_usb3_power_on(struct phy *phy)
+{
+	struct k1_pcie_phy *k1_phy = dev_get_priv(phy->dev);
+	void __iomem *apmu = k1_phy->apmu_base;
+	void __iomem *regs = k1_phy->regs;
+	int ret;
+	u32 val;
+
+	/*
+	 * Point the lane at the USB 3 controller.  Left in PCIe mode, the
+	 * PHY PLL does not lock, the controller gets no PIPE clock and the
+	 * xHCI reset never completes.
+	 */
+	k1_combo_phy_sel(k1_phy, true);
+
+	/* Make sure the PHY is not held in reset by the PCIe port A logic */
+	val = readl(apmu + PCIE_CLK_RES_CTRL);
+	val &= ~PCIE_APP_HOLD_PHY_RST;
+	writel(val, apmu + PCIE_CLK_RES_CTRL);
+
+	/* We're not doing any testing */
+	writel(0, regs + USB3_TEST_CTRL);
+
+	/* Configure the PLL for USB 3 */
+	val = readl(regs + PCIE_PU_ADDR_CLK_CFG);
+	val &= ~CFG_INTERNAL_TIMER_ADJ;
+	val |= FIELD_PREP(CFG_INTERNAL_TIMER_ADJ, TIMER_ADJ_USB);
+	writel(val, regs + PCIE_PU_ADDR_CLK_CFG);
+
+	/* 5000 ppm spread spectrum, 24 MHz reference, no 100 MHz input */
+	val = readl(regs + PCIE_PU_PLL_1);
+	val &= ~(SSC_DEP_SEL | REF_100_WSSC | FREF_SEL);
+	val |= FIELD_PREP(SSC_DEP_SEL, SSC_DEP_5000PPM);
+	val |= FIELD_PREP(FREF_SEL, FREF_24M);
+	writel(val, regs + PCIE_PU_PLL_1);
+
+	/* Forcing receiver retry is for PCIe only */
+	val = readl(regs + PCIE_RC_DONE_STATUS);
+	val &= ~CFG_FORCE_RCV_RETRY;
+	writel(val, regs + PCIE_RC_DONE_STATUS);
+
+	/* PLL configuration is done; start the PLL */
+	val = readl(regs + PCIE_PU_ADDR_CLK_CFG);
+	val |= CFG_SW_PHY_INIT_DONE;
+	writel(val, regs + PCIE_PU_ADDR_CLK_CFG);
+
+	ret = readl_poll_timeout(regs + PCIE_PU_ADDR_CLK_CFG, val,
+				 val & PLL_READY, PLL_TIMEOUT_US);
+	if (ret)
+		dev_err(phy->dev, "USB 3 PLL lock timeout, reg[0x08]=0x%08x\n",
+			val);
+
+	return ret;
+}
+
+/*
+ * The DWC3 glue initializes its USB 3 PHY before it enables the
+ * controller's clock and releases its resets, and powers the PHY on
+ * afterwards.  Linux starts the PHY PLL with the controller already
+ * running, so for USB 3 the PHY is set up here rather than at init.
+ */
+static int k1_pcie_phy_power_on(struct phy *phy)
+{
+	struct k1_pcie_phy *k1_phy = dev_get_priv(phy->dev);
+
+	if (k1_phy->is_combo && phy->id == PHY_TYPE_USB3)
+		return k1_combo_phy_usb3_power_on(phy);
+
+	return 0;
+}
+
 static int k1_pcie_phy_exit(struct phy *phy)
 {
 	return 0;
@@ -393,6 +468,7 @@  static int k1_pcie_phy_of_xlate(struct phy *phy,
 static const struct phy_ops k1_pcie_phy_ops = {
 	.of_xlate	= k1_pcie_phy_of_xlate,
 	.init		= k1_pcie_phy_init,
+	.power_on	= k1_pcie_phy_power_on,
 	.exit		= k1_pcie_phy_exit,
 };