diff mbox series

[3/7] phy: spacemit: k1: restore port A state after calibration

Message ID 2ac21998330f5e0eafcae48e212d2180eadab815.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
To calibrate, the combo PHY sets DEVICE_TYPE_RC and the port A clock
gates and reset bits (0x3f) in PCIE_CLK_RES_CTRL and selects PCIe in
PMUA_USB_PHY_CTRL0, and leaves them so. Port A then stays configured
as a PCIe root complex with its application clocks running, and a
board that had selected USB 3 finds the lane switched to PCIe.

Linux enables the port A application clocks and deasserts its
resets only around the wait for R_TUNE_DONE, then asserts and
disables them again. Remember what PCIE_CLK_RES_CTRL and COMBO_PHY_SEL
held before calibrating and restore those bits afterwards, whether
or not calibration succeeded. Restoring the previous value rather
than clearing the bits keeps them set if the port A PCIe controller
had already enabled them. APP_HOLD_PHY_RST stays clear, as in Linux.

Also, as Linux does, skip the wait if R_TUNE_DONE is already set
and use the result found in the register.

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

Patch

diff --git a/drivers/phy/spacemit/phy-k1-pcie.c b/drivers/phy/spacemit/phy-k1-pcie.c
index 734d1f9b4f..308f7dec79 100644
--- a/drivers/phy/spacemit/phy-k1-pcie.c
+++ b/drivers/phy/spacemit/phy-k1-pcie.c
@@ -145,23 +145,37 @@  static void k1_combo_phy_sel(struct k1_pcie_phy *k1_phy, bool usb)
 
 int k1_pcie_combo_phy_calibrate(struct udevice *dev, struct k1_pcie_phy *k1_phy)
 {
+	void __iomem *apmu = k1_phy->apmu_base;
 	void __iomem *regs = k1_phy->regs;
+	u32 ctrl, sel;
 	int ret = 0;
 	u32 val;
 
 	if (k1_phy_rterm_valid())
 		return 0;
 
+	/*
+	 * Remember the port A settings changed below, so they can be
+	 * put back once calibration is done.
+	 */
+	ctrl = readl(apmu + PCIE_CLK_RES_CTRL);
+	sel = readl(apmu + PMUA_USB_PHY_CTRL0) & COMBO_PHY_SEL;
+
 	/*
 	 * Initialize the APMU control register: set RC mode, enable
 	 * clock gates (bits 0-5), then release the PHY hold.  The PHY
-	 * (global) reset was deasserted at probe.
+	 * (global) reset was deasserted at probe.  The hold is left
+	 * released, as Linux does.
 	 */
-	val = readl(k1_phy->apmu_base + PCIE_CLK_RES_CTRL);
-	val |= DEVICE_TYPE_RC | PCIE_APP_HOLD_PHY_RST | APMU_CLK_GATE_MASK;
-	writel(val, k1_phy->apmu_base + PCIE_CLK_RES_CTRL);
+	val = ctrl | DEVICE_TYPE_RC | PCIE_APP_HOLD_PHY_RST | APMU_CLK_GATE_MASK;
+	writel(val, apmu + PCIE_CLK_RES_CTRL);
 	val &= ~PCIE_APP_HOLD_PHY_RST;
-	writel(val, k1_phy->apmu_base + PCIE_CLK_RES_CTRL);
+	writel(val, apmu + PCIE_CLK_RES_CTRL);
+
+	/* Calibration may already have been done, e.g. by an earlier stage */
+	val = readl(regs + PCIE_RCAL_RESULT);
+	if (val & R_TUNE_DONE)
+		goto out;
 
 	/* Put the combo PHY into PCIe mode for calibration */
 	k1_combo_phy_sel(k1_phy, false);
@@ -173,9 +187,24 @@  int k1_pcie_combo_phy_calibrate(struct udevice *dev, struct k1_pcie_phy *k1_phy)
 	if (ret)
 		dev_err(dev, "PHY calib timeout, RCAL=0x%08x\n", val);
 
+out:
 	if (!ret)
 		k1_phy_rterm_set(val);
 
+	/*
+	 * Put back the RC mode, the port A clock gates and resets and the
+	 * PCIe/USB 3 mode select as they were found, so that calibrating
+	 * leaves port A as it was (Linux disables the clocks and asserts
+	 * the resets again).  If the PCIe port A controller had already
+	 * set them up, they stay set up.
+	 */
+	val = readl(apmu + PCIE_CLK_RES_CTRL);
+	val &= ~(DEVICE_TYPE_RC | APMU_CLK_GATE_MASK);
+	val |= ctrl & (DEVICE_TYPE_RC | APMU_CLK_GATE_MASK);
+	writel(val, apmu + PCIE_CLK_RES_CTRL);
+
+	k1_combo_phy_sel(k1_phy, sel);
+
 	return ret;
 }