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


Reply via email to