* [PATCH 1/7] phy: spacemit: k1: enable refclk and deassert the PHY reset at probe
2026-10-02 21:29 [PATCH 0/7] phy: spacemit: k1: fix the PCIe and combo PHY driver Dave Klotz
@ 2026-10-02 21:29 ` Dave Klotz
2026-10-02 21:29 ` [PATCH 2/7] phy: spacemit: k1: don't start the combo PHY PLL to calibrate Dave Klotz
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Dave Klotz @ 2026-10-02 21:29 UTC (permalink / raw)
To: u-boot; +Cc: Neil Armstrong, Yixun Lan, Tom Rini, Eric Chung
The driver gets the clocks and resets listed in the device tree with
clk_get_bulk() and reset_get_bulk(), but never enables or deasserts
any of them, and only warns if getting them fails. The combo PHY
instead clears GLOBAL_PHY_RST by hand in the APMU before calibrating,
and ports B and C rely on the PCIe controller driver to do the same
for them.
Linux gets the "phy" reset of every K1 PCIe PHY deasserted at probe
(devm_reset_control_get_exclusive_deasserted()) and leaves it that
way; the "refclk" oscillator is the parent of the PHY PLL and is
enabled with it. Do the same here: at probe, before any PHY register
is accessed, enable "refclk" and deassert the "phy" reset, and fail
the probe if either cannot be got or enabled. The combo PHY's PCIe
port A application clocks and resets are not part of this: the
calibration handles them, as it does in Linux.
The calibration no longer needs to clear GLOBAL_PHY_RST itself, since
that is the bit the "phy" reset controls.
Signed-off-by: Dave Klotz <DaveWK225@protonmail.com>
---
drivers/phy/spacemit/phy-k1-pcie.c | 69 ++++++++++++++++++++++--------
1 file changed, 50 insertions(+), 19 deletions(-)
diff --git a/drivers/phy/spacemit/phy-k1-pcie.c b/drivers/phy/spacemit/phy-k1-pcie.c
index e54742a2a9..b445ee9d01 100644
--- a/drivers/phy/spacemit/phy-k1-pcie.c
+++ b/drivers/phy/spacemit/phy-k1-pcie.c
@@ -92,7 +92,6 @@
#define PCIE_CLK_RES_CTRL 0x03cc
#define PCIE_APP_HOLD_PHY_RST BIT(30)
#define DEVICE_TYPE_RC BIT(31)
-#define GLOBAL_PHY_RST BIT(8)
#define APMU_CLK_GATE_MASK 0x3f
@@ -107,8 +106,8 @@ struct k1_pcie_phy {
void __iomem *apmu_base; /* only for combo PHY */
u32 pcie_lanes;
bool is_combo;
- struct clk_bulk clks;
- struct reset_ctl_bulk rst;
+ struct clk refclk;
+ struct reset_ctl phy_rst;
};
static bool k1_phy_rterm_valid(void)
@@ -155,13 +154,11 @@ int k1_pcie_combo_phy_calibrate(struct udevice *dev, struct k1_pcie_phy *k1_phy)
/*
* Initialize the APMU control register: set RC mode, enable
- * clock gates (bits 0-5), deassert PHY global reset, then
- * release the PHY hold. Without this the PHY registers are
- * not accessible and reads will hang.
+ * clock gates (bits 0-5), then release the PHY hold. The PHY
+ * (global) reset was deasserted at probe.
*/
val = readl(k1_phy->apmu_base + PCIE_CLK_RES_CTRL);
val |= DEVICE_TYPE_RC | PCIE_APP_HOLD_PHY_RST | APMU_CLK_GATE_MASK;
- val &= ~GLOBAL_PHY_RST;
writel(val, k1_phy->apmu_base + PCIE_CLK_RES_CTRL);
val &= ~PCIE_APP_HOLD_PHY_RST;
writel(val, k1_phy->apmu_base + PCIE_CLK_RES_CTRL);
@@ -384,6 +381,48 @@ static const struct phy_ops k1_pcie_phy_ops = {
.exit = k1_pcie_phy_exit,
};
+/*
+ * Enable the 24 MHz reference clock that feeds the PHY PLL, and
+ * deassert the PHY (global) reset, leaving it deasserted. This must
+ * happen before any PHY register is accessed.
+ */
+static int k1_pcie_phy_enable_resources(struct udevice *dev,
+ struct k1_pcie_phy *k1_phy)
+{
+ int ret;
+
+ ret = clk_get_by_name(dev, "refclk", &k1_phy->refclk);
+ if (ret) {
+ dev_err(dev, "failed to get refclk: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_enable(&k1_phy->refclk);
+ if (ret) {
+ dev_err(dev, "failed to enable refclk: %d\n", ret);
+ return ret;
+ }
+
+ ret = reset_get_by_name(dev, "phy", &k1_phy->phy_rst);
+ if (ret) {
+ dev_err(dev, "failed to get phy reset: %d\n", ret);
+ goto err_clk;
+ }
+
+ ret = reset_deassert(&k1_phy->phy_rst);
+ if (ret) {
+ dev_err(dev, "failed to deassert phy reset: %d\n", ret);
+ goto err_clk;
+ }
+
+ return 0;
+
+err_clk:
+ clk_disable(&k1_phy->refclk);
+
+ return ret;
+}
+
static int k1_pcie_phy_probe(struct udevice *dev)
{
struct k1_pcie_phy *k1_phy = dev_get_priv(dev);
@@ -425,13 +464,9 @@ static int k1_pcie_phy_probe(struct udevice *dev)
if (!k1_phy->apmu_base)
return -EINVAL;
- ret = clk_get_bulk(dev, &k1_phy->clks);
- if (ret)
- dev_warn(dev, "failed to get clocks: %d\n", ret);
-
- ret = reset_get_bulk(dev, &k1_phy->rst);
+ ret = k1_pcie_phy_enable_resources(dev, k1_phy);
if (ret)
- dev_warn(dev, "failed to get resets: %d\n", ret);
+ return ret;
ret = k1_pcie_combo_phy_calibrate(dev, k1_phy);
if (ret)
@@ -461,13 +496,9 @@ static int k1_pcie_phy_probe(struct udevice *dev)
return -ENOENT;
}
- ret = clk_get_bulk(dev, &k1_phy->clks);
+ ret = k1_pcie_phy_enable_resources(dev, k1_phy);
if (ret)
- dev_warn(dev, "failed to get clocks: %d\n", ret);
-
- ret = reset_get_bulk(dev, &k1_phy->rst);
- if (ret)
- dev_warn(dev, "failed to get resets: %d\n", ret);
+ return ret;
}
dev_dbg(dev, "probed (combo=%d lanes=%u rterm=0x%02x)\n",
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH 2/7] phy: spacemit: k1: don't start the combo PHY PLL to calibrate
2026-10-02 21:29 [PATCH 0/7] phy: spacemit: k1: fix the PCIe and combo PHY driver Dave Klotz
2026-10-02 21:29 ` [PATCH 1/7] phy: spacemit: k1: enable refclk and deassert the PHY reset at probe Dave Klotz
@ 2026-10-02 21:29 ` Dave Klotz
2026-10-02 21:30 ` [PATCH 3/7] phy: spacemit: k1: restore port A state after calibration Dave Klotz
` (5 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Dave Klotz @ 2026-10-02 21:29 UTC (permalink / raw)
To: u-boot; +Cc: Neil Armstrong, Yixun Lan, Tom Rini, Eric Chung
To measure the termination values, the combo PHY calibration also
programs the PHY as for PCIe: refclk driver mode, the 24 MHz
reference, the 100 MHz output and no spread spectrum, then writes
0xb78 over the whole of PCIE_PU_ADDR_CLK_CFG and CFG_FORCE_RCV_RETRY
over the whole of PCIE_RC_DONE_STATUS. 0xb78 includes
CFG_SW_PHY_INIT_DONE, so the port A PHY PLL is started in PCIe mode
and left running, whatever the combo PHY is used for afterwards. On a
board that uses it for USB 3, the PLL is then already running with
the PCIe timer and spread-spectrum settings when the USB 3 settings
are written.
Linux does none of this. k1_pcie_combo_phy_calibrate() deasserts the
PHY reset, clears APP_HOLD_PHY_RST, selects PCIe mode, enables the
port A application clocks and resets and waits for R_TUNE_DONE; the
PLL is programmed and started only when the PHY is initialised for
the mode it is used in. Do the same and drop the PHY register writes
from the calibration.
Doing so also removes the two whole-register writes, which cleared
every other field of those registers.
Signed-off-by: Dave Klotz <DaveWK225@protonmail.com>
---
drivers/phy/spacemit/phy-k1-pcie.c | 34 ------------------------------
1 file changed, 34 deletions(-)
diff --git a/drivers/phy/spacemit/phy-k1-pcie.c b/drivers/phy/spacemit/phy-k1-pcie.c
index b445ee9d01..734d1f9b4f 100644
--- a/drivers/phy/spacemit/phy-k1-pcie.c
+++ b/drivers/phy/spacemit/phy-k1-pcie.c
@@ -166,40 +166,6 @@ int k1_pcie_combo_phy_calibrate(struct udevice *dev, struct k1_pcie_phy *k1_phy)
/* Put the combo PHY into PCIe mode for calibration */
k1_combo_phy_sel(k1_phy, false);
- /* Set refclk override + clear CFG_REFCLK_MODE */
- val = readl(regs + PCIE_LTSSM_DIS_ENTRY);
- val |= OVRD_REFCLK_MODE;
- writel(val, regs + PCIE_LTSSM_DIS_ENTRY);
-
- val = readl(regs + PCIE_LTSSM_DIS_ENTRY);
- val &= ~CFG_REFCLK_MODE;
- writel(val, regs + PCIE_LTSSM_DIS_ENTRY);
-
- val = readl(regs + PCIE_LTSSM_DIS_ENTRY);
- val |= FIELD_PREP(CFG_REFCLK_MODE, RFCLK_MODE_DRIVER);
- writel(val, regs + PCIE_LTSSM_DIS_ENTRY);
-
- /* Configure PLL: select 24MHz reference, enable 100MHz output */
- val = readl(regs + PCIE_PU_PLL_1);
- val &= ~(FREF_SEL | REF_100_WSSC);
- writel(val, regs + PCIE_PU_PLL_1);
-
- val = readl(regs + PCIE_PU_PLL_1);
- val |= FIELD_PREP(FREF_SEL, FREF_24M);
- writel(val, regs + PCIE_PU_PLL_1);
-
- val = readl(regs + PCIE_PU_PLL_2);
- val |= GEN_REF100;
- writel(val, regs + PCIE_PU_PLL_2);
-
- val = readl(regs + PCIE_PU_PLL_1);
- val &= ~SSC_DEP_SEL;
- writel(val, regs + PCIE_PU_PLL_1);
-
- /* Start PHY init + force receiver */
- writel(0x00000B78, regs + PCIE_PU_ADDR_CLK_CFG);
- writel(CFG_FORCE_RCV_RETRY, regs + PCIE_RC_DONE_STATUS);
-
/* Wait for calibration to complete */
ret = readl_poll_sleep_timeout(regs + PCIE_RCAL_RESULT,
val, val & R_TUNE_DONE,
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH 3/7] phy: spacemit: k1: restore port A state after calibration
2026-10-02 21:29 [PATCH 0/7] phy: spacemit: k1: fix the PCIe and combo PHY driver Dave Klotz
2026-10-02 21:29 ` [PATCH 1/7] phy: spacemit: k1: enable refclk and deassert the PHY reset at probe Dave Klotz
2026-10-02 21:29 ` [PATCH 2/7] phy: spacemit: k1: don't start the combo PHY PLL to calibrate Dave Klotz
@ 2026-10-02 21:30 ` Dave Klotz
2026-10-02 21:30 ` [PATCH 4/7] phy: spacemit: k1: program the termination fields properly Dave Klotz
` (4 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Dave Klotz @ 2026-10-02 21:30 UTC (permalink / raw)
To: u-boot; +Cc: Neil Armstrong, Yixun Lan, Tom Rini, Eric Chung
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 --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
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH 4/7] phy: spacemit: k1: program the termination fields properly
2026-10-02 21:29 [PATCH 0/7] phy: spacemit: k1: fix the PCIe and combo PHY driver Dave Klotz
` (2 preceding siblings ...)
2026-10-02 21:30 ` [PATCH 3/7] phy: spacemit: k1: restore port A state after calibration Dave Klotz
@ 2026-10-02 21:30 ` Dave Klotz
2026-10-02 21:30 ` [PATCH 5/7] phy: spacemit: k1: set CLKSEL on every lane Dave Klotz
` (3 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Dave Klotz @ 2026-10-02 21:30 UTC (permalink / raw)
To: u-boot; +Cc: Neil Armstrong, Yixun Lan, Tom Rini, Eric Chung
The RX and TX termination values measured by the combo PHY are OR'd
into AFE_RTERM_REG (PCIE_RX_REG1) and TX_RTERM_REG (PCIE_TX_REG1)
without clearing those fields first, so any bit already set there,
by reset or by an earlier stage, ends up in the result. EN_RTERM,
which enables the refclock receiver termination, is never set.
Linux's k1_pcie_phy_init_pcie() clears each field before writing
the calibrated value and sets EN_RTERM, on every lane. Do the same.
Signed-off-by: Dave Klotz <DaveWK225@protonmail.com>
---
drivers/phy/spacemit/phy-k1-pcie.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/drivers/phy/spacemit/phy-k1-pcie.c b/drivers/phy/spacemit/phy-k1-pcie.c
index 308f7dec79..ab8349f7d1 100644
--- a/drivers/phy/spacemit/phy-k1-pcie.c
+++ b/drivers/phy/spacemit/phy-k1-pcie.c
@@ -231,10 +231,15 @@ static int k1_pcie_phy_init(struct phy *phy)
lane = (k1_phy->pcie_lanes == 1) ? 1 : 2;
- /* Apply rterm calibration value: LSB to RX_REG1 */
+ /*
+ * Apply rterm calibration value: LSB to RX_REG1, and enable
+ * refclock receiver termination
+ */
for (i = 0; i < lane; i++) {
val = readl(regs + PCIE_RX_REG1 + PHY_LANE_OFFSET * i);
+ val &= ~AFE_RTERM_REG;
val |= FIELD_PREP(AFE_RTERM_REG, k1_phy_rterm_rx());
+ val |= EN_RTERM;
writel(val, regs + PCIE_RX_REG1 + PHY_LANE_OFFSET * i);
}
@@ -248,6 +253,7 @@ static int k1_pcie_phy_init(struct phy *phy)
/* Apply rterm MSB to TX_REG1 */
for (i = 0; i < lane; i++) {
val = readl(regs + PCIE_TX_REG1 + PHY_LANE_OFFSET * i);
+ val &= ~TX_RTERM_REG;
val |= FIELD_PREP(TX_RTERM_REG, k1_phy_rterm_tx());
writel(val, regs + PCIE_TX_REG1 + PHY_LANE_OFFSET * i);
}
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH 5/7] phy: spacemit: k1: set CLKSEL on every lane
2026-10-02 21:29 [PATCH 0/7] phy: spacemit: k1: fix the PCIe and combo PHY driver Dave Klotz
` (3 preceding siblings ...)
2026-10-02 21:30 ` [PATCH 4/7] phy: spacemit: k1: program the termination fields properly Dave Klotz
@ 2026-10-02 21:30 ` Dave Klotz
2026-10-02 21:30 ` [PATCH 6/7] phy: spacemit: k1: don't overwrite whole PLL control registers Dave Klotz
` (2 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Dave Klotz @ 2026-10-02 21:30 UTC (permalink / raw)
To: u-boot; +Cc: Neil Armstrong, Yixun Lan, Tom Rini, Eric Chung
CLKSEL in PCIE_RC_CAL_REG2 selects the input clock of the lane's RC
calibration. It is set only on lane 0, so the second lane of ports B
and C keeps its reset value, and it is OR'd in, so a value already in
the field is merged with CLKSEL_24M rather than replaced.
Linux's k1_pcie_phy_init_pcie() sets CLKSEL to 24 MHz on every lane,
before toggling RC_CAL_TOGGLE on that lane. Do the same, clearing the
field before writing it. (Linux masks with "val &= CLKSEL", which also
clears every other field of the register; this keeps the other fields,
as this driver did before.)
Signed-off-by: Dave Klotz <DaveWK225@protonmail.com>
---
drivers/phy/spacemit/phy-k1-pcie.c | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/drivers/phy/spacemit/phy-k1-pcie.c b/drivers/phy/spacemit/phy-k1-pcie.c
index ab8349f7d1..0e18f29024 100644
--- a/drivers/phy/spacemit/phy-k1-pcie.c
+++ b/drivers/phy/spacemit/phy-k1-pcie.c
@@ -265,10 +265,13 @@ static int k1_pcie_phy_init(struct phy *phy)
writel(val, regs + PCIE_TX_REG1 + PHY_LANE_OFFSET * i);
}
- /* Set CLKSEL_24M on lane0 */
- val = readl(regs + PCIE_RC_CAL_REG2);
- val |= FIELD_PREP(CLKSEL, CLKSEL_24M);
- writel(val, regs + PCIE_RC_CAL_REG2);
+ /* Select the 24 MHz input clock for all lanes */
+ for (i = 0; i < lane; i++) {
+ val = readl(regs + PCIE_RC_CAL_REG2 + PHY_LANE_OFFSET * i);
+ val &= ~CLKSEL;
+ val |= FIELD_PREP(CLKSEL, CLKSEL_24M);
+ writel(val, regs + PCIE_RC_CAL_REG2 + PHY_LANE_OFFSET * i);
+ }
/* Toggle RC_CAL_TOGGLE for all lanes */
for (i = 0; i < lane; i++) {
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH 6/7] phy: spacemit: k1: don't overwrite whole PLL control registers
2026-10-02 21:29 [PATCH 0/7] phy: spacemit: k1: fix the PCIe and combo PHY driver Dave Klotz
` (4 preceding siblings ...)
2026-10-02 21:30 ` [PATCH 5/7] phy: spacemit: k1: set CLKSEL on every lane Dave Klotz
@ 2026-10-02 21:30 ` Dave Klotz
2026-10-02 21:30 ` [PATCH 7/7] phy: spacemit: k1: power on the combo PHY in USB 3 mode Dave Klotz
2026-10-02 22:06 ` [PATCH 0/7] phy: spacemit: k1: fix the PCIe and combo PHY driver Dave Klotz
7 siblings, 0 replies; 9+ messages in thread
From: Dave Klotz @ 2026-10-02 21:30 UTC (permalink / raw)
To: u-boot; +Cc: Neil Armstrong, Yixun Lan, Tom Rini, Eric Chung
To start the PHY PLL, init writes 0xb78 over the whole of
PCIE_PU_ADDR_CLK_CFG, and CFG_FORCE_RCV_RETRY over the whole of
PCIE_RC_DONE_STATUS, clearing every other field of both. 0xb78 is
CFG_SW_PHY_INIT_DONE, CFG_INTERNAL_TIMER_ADJ = 6 (PCIe) and four
undocumented low bits. The lane 1 register is written even when the
PHY has only one lane, as the combo PHY always does.
Linux's PLL prepare sets CFG_INTERNAL_TIMER_ADJ to TIMER_ADJ_PCIE on
each lane in use, sets CFG_FORCE_RCV_RETRY, clears REF_100_WSSC along
with selecting the 24 MHz reference in PCIE_PU_PLL_1, and only then
sets CFG_SW_PHY_INIT_DONE on each lane to start the PLL, all with
read-modify-write. Do the same here, using the existing named fields
instead of the magic value.
Signed-off-by: Dave Klotz <DaveWK225@protonmail.com>
---
drivers/phy/spacemit/phy-k1-pcie.c | 27 +++++++++++++++++++--------
1 file changed, 19 insertions(+), 8 deletions(-)
diff --git a/drivers/phy/spacemit/phy-k1-pcie.c b/drivers/phy/spacemit/phy-k1-pcie.c
index 0e18f29024..c3d29d6a2a 100644
--- a/drivers/phy/spacemit/phy-k1-pcie.c
+++ b/drivers/phy/spacemit/phy-k1-pcie.c
@@ -311,9 +311,9 @@ static int k1_pcie_phy_init(struct phy *phy)
val |= FIELD_PREP(CFG_REFCLK_MODE, RFCLK_MODE_DRIVER);
writel(val, regs + PHY_LANE_OFFSET + PCIE_LTSSM_DIS_ENTRY);
- /* Configure PLL: select 24MHz reference */
+ /* Configure PLL: select 24MHz reference, no 100MHz SSC input */
val = readl(regs + PCIE_PU_PLL_1);
- val &= ~FREF_SEL;
+ val &= ~(FREF_SEL | REF_100_WSSC);
writel(val, regs + PCIE_PU_PLL_1);
val = readl(regs + PCIE_PU_PLL_1);
@@ -330,15 +330,26 @@ static int k1_pcie_phy_init(struct phy *phy)
val &= ~SSC_DEP_SEL;
writel(val, regs + PCIE_PU_PLL_1);
- /* Set PU_ADDR_CLK_CFG for both lanes */
- val = 0x00000B78;
- writel(val, regs + PCIE_PU_ADDR_CLK_CFG);
- writel(val, regs + PHY_LANE_OFFSET + PCIE_PU_ADDR_CLK_CFG);
+ /* Set the PCIe internal timer adjustment for all lanes */
+ for (i = 0; i < lane; i++) {
+ val = readl(regs + PCIE_PU_ADDR_CLK_CFG + PHY_LANE_OFFSET * i);
+ val &= ~CFG_INTERNAL_TIMER_ADJ;
+ val |= FIELD_PREP(CFG_INTERNAL_TIMER_ADJ, TIMER_ADJ_PCIE);
+ writel(val, regs + PCIE_PU_ADDR_CLK_CFG + PHY_LANE_OFFSET * i);
+ }
- /* Force receiver done */
- val = CFG_FORCE_RCV_RETRY;
+ /* Force receiver retry */
+ 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 on all lanes */
+ for (i = 0; i < lane; i++) {
+ val = readl(regs + PCIE_PU_ADDR_CLK_CFG + PHY_LANE_OFFSET * i);
+ val |= CFG_SW_PHY_INIT_DONE;
+ writel(val, regs + PCIE_PU_ADDR_CLK_CFG + PHY_LANE_OFFSET * i);
+ }
+
/* Wait for PLL lock */
ret = readl_poll_timeout(regs + PCIE_PU_ADDR_CLK_CFG, val,
val & PLL_READY, PLL_TIMEOUT_US);
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH 7/7] phy: spacemit: k1: power on the combo PHY in USB 3 mode
2026-10-02 21:29 [PATCH 0/7] phy: spacemit: k1: fix the PCIe and combo PHY driver Dave Klotz
` (5 preceding siblings ...)
2026-10-02 21:30 ` [PATCH 6/7] phy: spacemit: k1: don't overwrite whole PLL control registers Dave Klotz
@ 2026-10-02 21:30 ` Dave Klotz
2026-10-02 22:06 ` [PATCH 0/7] phy: spacemit: k1: fix the PCIe and combo PHY driver Dave Klotz
7 siblings, 0 replies; 9+ messages in thread
From: Dave Klotz @ 2026-10-02 21:30 UTC (permalink / raw)
To: u-boot; +Cc: Neil Armstrong, Yixun Lan, Tom Rini, Eric Chung
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 --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,
};
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PATCH 0/7] phy: spacemit: k1: fix the PCIe and combo PHY driver
2026-10-02 21:29 [PATCH 0/7] phy: spacemit: k1: fix the PCIe and combo PHY driver Dave Klotz
` (6 preceding siblings ...)
2026-10-02 21:30 ` [PATCH 7/7] phy: spacemit: k1: power on the combo PHY in USB 3 mode Dave Klotz
@ 2026-10-02 22:06 ` Dave Klotz
7 siblings, 0 replies; 9+ messages in thread
From: Dave Klotz @ 2026-10-02 22:06 UTC (permalink / raw)
To: u-boot; +Cc: Neil Armstrong, Yixun Lan, Tom Rini, Eric Chung
Also tested on an OrangePi R2S (K1, 2 GiB, boots from eMMC), with the
same tree as the RV2 runs (upstream next plus these series and board
support that is not posted yet):
- the combo PHY is calibrated at probe and switched to USB 3, the PLL
locks and the xHCI starts ("Bus usb@c0a00000: 1 USB Device(s) found");
- nothing was plugged into the port, so no SuperSpeed device was
enumerated on this board (the RV2 enumerated one at 5 Gb/s);
- the PCIe PHYs are not used by U-Boot on this board.
^ permalink raw reply [flat|nested] 9+ messages in thread