Add PHY driver for Spacemit K1 SoC supporting: - Combo PHY (PCIe/USB3) with auto-calibration of RX/TX termination - PCIe-only PHY (2 lanes) using combo PHY calibration values
The combo PHY calibrates at probe time, and the result is shared globally so PCIe-only PHYs can apply the same termination settings. Signed-off-by: Eric Chung <[email protected]> --- drivers/phy/Kconfig | 1 + drivers/phy/Makefile | 1 + drivers/phy/spacemit/Kconfig | 11 + drivers/phy/spacemit/Makefile | 1 + drivers/phy/spacemit/phy-k1-pcie.c | 492 +++++++++++++++++++++++++++++++++++++ 5 files changed, 506 insertions(+) diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 89d84df96ae..fe3443d1489 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -349,6 +349,7 @@ source "drivers/phy/ti/Kconfig" source "drivers/phy/qcom/Kconfig" source "drivers/phy/renesas/Kconfig" source "drivers/phy/starfive/Kconfig" +source "drivers/phy/spacemit/Kconfig" config PHY_COMMON_PROPS bool "Common PHY properties support" diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index e46c362878d..9933a406d54 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -49,3 +49,4 @@ obj-y += ti/ obj-y += qcom/ obj-y += renesas/ obj-y += starfive/ +obj-y += spacemit/ diff --git a/drivers/phy/spacemit/Kconfig b/drivers/phy/spacemit/Kconfig new file mode 100644 index 00000000000..3c47601c1fd --- /dev/null +++ b/drivers/phy/spacemit/Kconfig @@ -0,0 +1,11 @@ +config PHY_SPACEMIT_K1_PCIE + bool "Spacemit K1 PCIe PHY driver" + depends on PHY + help + Support for the Spacemit K1 SoC PCIe and Combo (PCIe/USB3) PHYs. + + This driver handles both the combo PHY used for port A (which + calibrates RX/TX termination values) and the PCIe-only PHYs for + ports B and C. The combo PHY must be enabled even if port A + is not used for PCIe, because it provides calibration values + required by the other ports. diff --git a/drivers/phy/spacemit/Makefile b/drivers/phy/spacemit/Makefile new file mode 100644 index 00000000000..3496c099cdf --- /dev/null +++ b/drivers/phy/spacemit/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_PHY_SPACEMIT_K1_PCIE) += phy-k1-pcie.o diff --git a/drivers/phy/spacemit/phy-k1-pcie.c b/drivers/phy/spacemit/phy-k1-pcie.c new file mode 100644 index 00000000000..e54742a2a9c --- /dev/null +++ b/drivers/phy/spacemit/phy-k1-pcie.c @@ -0,0 +1,492 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Spacemit K1 PCIe and PCIe/USB 3 combo PHY driver + * + * Copyright (C) 2026 RISCstar Ltd. All rights reserved. + */ + +#include <asm/io.h> +#include <clk.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <dm/ofnode.h> +#include <errno.h> +#include <generic-phy.h> +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/iopoll.h> +#include <reset.h> + +#include <dt-bindings/phy/phy.h> + +/* + * Three PCIe ports are supported in the SpacemiT K1 SoC. + * + * Port A (combo PHY): 1 lane, can switch between PCIe and USB 3. + * The only PHY that can auto-calibrate RX/TX termination. + * Port B (pcie-phy): 2 lanes, PCIe only. + * Port C (pcie-phy): 2 lanes, PCIe only. + * + * Calibration values from port A are used to configure ports B and C. + * Port B/C must not probe until port A has completed calibration. + */ + +/* Offset between lane 0 and lane 1 registers */ +#define PHY_LANE_OFFSET 0x0400 + +/* PHY PLL configuration */ +#define PCIE_PU_ADDR_CLK_CFG 0x0008 +#define PLL_READY BIT(0) +#define CFG_INTERNAL_TIMER_ADJ GENMASK(10, 7) +#define TIMER_ADJ_PCIE 0x6 +#define CFG_SW_PHY_INIT_DONE BIT(11) + +#define PCIE_RC_DONE_STATUS 0x0018 +#define CFG_FORCE_RCV_RETRY BIT(10) + +/* PCIe PHY lane calibration */ +#define PCIE_RC_CAL_REG2 0x0020 +#define RC_CAL_TOGGLE BIT(22) +#define CLKSEL GENMASK(31, 29) +#define CLKSEL_24M 0x3 + +/* Additional PHY PLL configuration */ +#define PCIE_PU_PLL_1 0x0048 +#define REF_100_WSSC BIT(12) +#define FREF_SEL GENMASK(15, 13) +#define FREF_24M 0x1 +#define SSC_DEP_SEL GENMASK(19, 16) +#define SSC_DEP_NONE 0x0 + +/* PCIe PHY configuration */ +#define PCIE_PU_PLL_2 0x004c +#define GEN_REF100 BIT(4) + +#define PCIE_RX_REG1 0x0050 +#define EN_RTERM BIT(3) +#define AFE_RTERM_REG GENMASK(11, 8) + +#define PCIE_RX_REG2 0x0054 +#define RX_RTERM_SEL BIT(5) + +#define PCIE_LTSSM_DIS_ENTRY 0x005c +#define CFG_REFCLK_MODE GENMASK(9, 8) +#define RFCLK_MODE_DRIVER 0x1 +#define RFCLK_MODE_RECEIVER 0x2 +#define OVRD_REFCLK_MODE BIT(10) + +#define PCIE_TX_REG1 0x0064 +#define TX_RTERM_REG GENMASK(15, 12) +#define TX_RTERM_SEL BIT(25) + +/* PHY calibration values */ +#define PCIE_RCAL_RESULT 0x0084 +#define RTERM_VALUE_RX GENMASK(3, 0) +#define RTERM_VALUE_TX GENMASK(7, 4) +#define R_TUNE_DONE BIT(10) + +/* PMU register offsets (shared APMU space) */ +#define PMUA_USB_PHY_CTRL0 0x0110 +#define COMBO_PHY_SEL BIT(3) /* 0: PCIe; 1: USB 3 */ +#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 + +#define CALIBRATION_TIMEOUT_US 500000 +#define PLL_TIMEOUT_US 500000 + +/* Global calibration value shared across PHY ports */ +static u32 k1_phy_rterm = ~0; + +struct k1_pcie_phy { + void __iomem *regs; + void __iomem *apmu_base; /* only for combo PHY */ + u32 pcie_lanes; + bool is_combo; + struct clk_bulk clks; + struct reset_ctl_bulk rst; +}; + +static bool k1_phy_rterm_valid(void) +{ + return !(k1_phy_rterm & ~(RTERM_VALUE_RX | RTERM_VALUE_TX)); +} + +u32 k1_phy_rterm_rx(void) +{ + return FIELD_GET(RTERM_VALUE_RX, k1_phy_rterm); +} + +u32 k1_phy_rterm_tx(void) +{ + return FIELD_GET(RTERM_VALUE_TX, k1_phy_rterm); +} + +static void k1_phy_rterm_set(u32 val) +{ + k1_phy_rterm = val & (RTERM_VALUE_RX | RTERM_VALUE_TX); +} + +static void k1_combo_phy_sel(struct k1_pcie_phy *k1_phy, bool usb) +{ + void __iomem *pmu = k1_phy->apmu_base; + u32 val; + + val = readl(pmu + PMUA_USB_PHY_CTRL0); + if (usb) + val |= COMBO_PHY_SEL; + else + val &= ~COMBO_PHY_SEL; + writel(val, pmu + PMUA_USB_PHY_CTRL0); +} + +int k1_pcie_combo_phy_calibrate(struct udevice *dev, struct k1_pcie_phy *k1_phy) +{ + void __iomem *regs = k1_phy->regs; + int ret = 0; + u32 val; + + if (k1_phy_rterm_valid()) + return 0; + + /* + * 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. + */ + 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); + + /* 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, + 500, CALIBRATION_TIMEOUT_US); + if (ret) + dev_err(dev, "PHY calib timeout, RCAL=0x%08x\n", val); + + if (!ret) + k1_phy_rterm_set(val); + + return ret; +} + +static int k1_pcie_phy_init(struct phy *phy) +{ + struct k1_pcie_phy *k1_phy = dev_get_priv(phy->dev); + void __iomem *regs = k1_phy->regs; + int ret; + 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); + return 0; + } + + /* For combo PHY, ensure it's in PCIe mode */ + if (k1_phy->is_combo) + k1_combo_phy_sel(k1_phy, false); + + dev_dbg(phy->dev, "init: combo=%d lanes=%u rterm=0x%02x\n", + k1_phy->is_combo, k1_phy->pcie_lanes, k1_phy_rterm); + + lane = (k1_phy->pcie_lanes == 1) ? 1 : 2; + + /* Apply rterm calibration value: LSB to RX_REG1 */ + for (i = 0; i < lane; i++) { + val = readl(regs + PCIE_RX_REG1 + PHY_LANE_OFFSET * i); + val |= FIELD_PREP(AFE_RTERM_REG, k1_phy_rterm_rx()); + writel(val, regs + PCIE_RX_REG1 + PHY_LANE_OFFSET * i); + } + + /* Disable RX_RTERM_SEL for all lanes */ + for (i = 0; i < lane; i++) { + val = readl(regs + PCIE_RX_REG2 + PHY_LANE_OFFSET * i); + val &= ~RX_RTERM_SEL; + writel(val, regs + PCIE_RX_REG2 + PHY_LANE_OFFSET * i); + } + + /* Apply rterm MSB to TX_REG1 */ + for (i = 0; i < lane; i++) { + val = readl(regs + PCIE_TX_REG1 + PHY_LANE_OFFSET * i); + val |= FIELD_PREP(TX_RTERM_REG, k1_phy_rterm_tx()); + writel(val, regs + PCIE_TX_REG1 + PHY_LANE_OFFSET * i); + } + + /* Enable TX_RTERM_SEL for all lanes */ + for (i = 0; i < lane; i++) { + val = readl(regs + PCIE_TX_REG1 + PHY_LANE_OFFSET * i); + val |= TX_RTERM_SEL; + 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); + + /* Toggle RC_CAL_TOGGLE for all lanes */ + for (i = 0; i < lane; i++) { + val = readl(regs + PCIE_RC_CAL_REG2 + PHY_LANE_OFFSET * i); + val &= ~RC_CAL_TOGGLE; + writel(val, regs + PCIE_RC_CAL_REG2 + PHY_LANE_OFFSET * i); + } + for (i = 0; i < lane; i++) { + val = readl(regs + PCIE_RC_CAL_REG2 + PHY_LANE_OFFSET * i); + val |= RC_CAL_TOGGLE; + writel(val, regs + PCIE_RC_CAL_REG2 + PHY_LANE_OFFSET * i); + } + + /* Configure refclk as driver mode, override */ + 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 + PHY_LANE_OFFSET + PCIE_LTSSM_DIS_ENTRY); + val |= OVRD_REFCLK_MODE; + writel(val, regs + PHY_LANE_OFFSET + PCIE_LTSSM_DIS_ENTRY); + + val = readl(regs + PHY_LANE_OFFSET + PCIE_LTSSM_DIS_ENTRY); + val &= ~CFG_REFCLK_MODE; + writel(val, regs + PHY_LANE_OFFSET + PCIE_LTSSM_DIS_ENTRY); + + /* Set driver mode for both lanes */ + val = readl(regs + PCIE_LTSSM_DIS_ENTRY); + val |= FIELD_PREP(CFG_REFCLK_MODE, RFCLK_MODE_DRIVER); + writel(val, regs + PCIE_LTSSM_DIS_ENTRY); + + val = readl(regs + PHY_LANE_OFFSET + PCIE_LTSSM_DIS_ENTRY); + val |= FIELD_PREP(CFG_REFCLK_MODE, RFCLK_MODE_DRIVER); + writel(val, regs + PHY_LANE_OFFSET + PCIE_LTSSM_DIS_ENTRY); + + /* Configure PLL: select 24MHz reference */ + val = readl(regs + PCIE_PU_PLL_1); + val &= ~FREF_SEL; + 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); + + /* Enable 100MHz reference output */ + val = readl(regs + PCIE_PU_PLL_2); + val |= GEN_REF100; + writel(val, regs + PCIE_PU_PLL_2); + + /* Disable SSC */ + val = readl(regs + PCIE_PU_PLL_1); + 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); + + /* Force receiver done */ + val = CFG_FORCE_RCV_RETRY; + writel(val, regs + PCIE_RC_DONE_STATUS); + + /* Wait for PLL lock */ + ret = readl_poll_timeout(regs + PCIE_PU_ADDR_CLK_CFG, val, + val & PLL_READY, PLL_TIMEOUT_US); + if (ret) { + dev_err(phy->dev, "PLL lock timeout! reg[0x08]=0x%08x\n", val); + return ret; + } + + dev_dbg(phy->dev, "PLL locked (combo=%d, lanes=%d)\n", + k1_phy->is_combo, k1_phy->pcie_lanes); + + return 0; +} + +static int k1_pcie_phy_exit(struct phy *phy) +{ + return 0; +} + +static int k1_pcie_phy_of_xlate(struct phy *phy, + struct ofnode_phandle_args *args) +{ + struct k1_pcie_phy *k1_phy = dev_get_priv(phy->dev); + + if (k1_phy->is_combo) { + if (args->args_count != 1) + return -EINVAL; + if (args->args[0] != PHY_TYPE_PCIE && + args->args[0] != PHY_TYPE_USB3) + return -EINVAL; + } + + if (args->args_count > 0) + phy->id = args->args[0]; + else + phy->id = 0; + + return 0; +} + +static const struct phy_ops k1_pcie_phy_ops = { + .of_xlate = k1_pcie_phy_of_xlate, + .init = k1_pcie_phy_init, + .exit = k1_pcie_phy_exit, +}; + +static int k1_pcie_phy_probe(struct udevice *dev) +{ + struct k1_pcie_phy *k1_phy = dev_get_priv(dev); + struct ofnode_phandle_args apmu_args; + bool is_combo; + int ret; + u32 lanes; + + is_combo = (bool)dev_get_driver_data(dev); + k1_phy->is_combo = is_combo; + + k1_phy->regs = dev_read_addr_ptr(dev); + if (!k1_phy->regs) + return -EINVAL; + + /* Determine number of lanes */ + if (is_combo) { + lanes = 1; + } else { + u32 count = 0; + + ret = dev_read_u32(dev, "num-lanes", &count); + if (ret == 0 && count == 1) + lanes = 1; + else + lanes = 2; + } + k1_phy->pcie_lanes = lanes; + + /* Get APMU base for combo PHY (for mode select + calibration) */ + if (is_combo) { + ret = dev_read_phandle_with_args(dev, "spacemit,apmu", + NULL, 0, 0, + &apmu_args); + if (ret) + return ret; + + k1_phy->apmu_base = (void __iomem *)ofnode_get_addr(apmu_args.node); + 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); + if (ret) + dev_warn(dev, "failed to get resets: %d\n", ret); + + ret = k1_pcie_combo_phy_calibrate(dev, k1_phy); + if (ret) + dev_warn(dev, "calibration failed: %d\n", ret); + } else { + /* + * PCIe-only PHYs need the calibration value from the + * combo PHY. In device-tree order the combo PHY probes + * first, but handle the case where it hasn't yet by + * explicitly looking it up. + */ + if (!k1_phy_rterm_valid()) { + struct udevice *combo_dev; + + ret = uclass_first_device_drvdata(UCLASS_PHY, 1UL, + &combo_dev); + if (ret) + dev_warn(dev, + "failed to probe combo PHY: %d\n", + ret); + } + + if (!k1_phy_rterm_valid()) { + dev_err(dev, + "calibration value not available; " + "is the combo PHY enabled?\n"); + return -ENOENT; + } + + 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); + if (ret) + dev_warn(dev, "failed to get resets: %d\n", ret); + } + + dev_dbg(dev, "probed (combo=%d lanes=%u rterm=0x%02x)\n", + is_combo, lanes, k1_phy_rterm); + + return 0; +} + +static const struct udevice_id k1_pcie_phy_ids[] = { + { .compatible = "spacemit,k1-combo-phy", .data = 1UL }, + { .compatible = "spacemit,k1-pcie-phy", .data = 0UL }, + { } +}; + +U_BOOT_DRIVER(k1_pcie_phy) = { + .name = "k1_pcie_phy", + .id = UCLASS_PHY, + .of_match = k1_pcie_phy_ids, + .ops = &k1_pcie_phy_ops, + .probe = k1_pcie_phy_probe, + .priv_auto = sizeof(struct k1_pcie_phy), +}; -- 2.51.0

