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 <[email protected]> --- drivers/phy/spacemit/phy-k1-pcie.c | 39 ++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 5 deletions(-) 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; } -- 2.55.0
