@@ -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;
}
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(-)