[PATCH 2/3] pci: spacemit: add DesignWare PCIe host controller driver
Eric Chung
eric.chung at riscstar.com
Sun Jul 5 15:06:28 CEST 2026
Add PCIe host controller driver for Spacemit K1 SoC using the
Synopsys DesignWare PCIe core. Supports up to 3 ports.
Signed-off-by: Eric Chung <eric.chung at riscstar.com>
---
drivers/pci/Kconfig | 7 +
drivers/pci/Makefile | 1 +
drivers/pci/pcie_dw_spacemit.c | 383 +++++++++++++++++++++++++++++++++++++++++
drivers/pci/pcie_dw_spacemit.h | 78 +++++++++
4 files changed, 469 insertions(+)
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index 9ffccc3a80b..688f5eee571 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -478,4 +478,11 @@ config PCIE_DW_IMX
Say Y here if you want to enable DW PCIe controller support on
iMX SoCs.
+config PCIE_DW_SPACEMIT
+ bool "Enable Spacemit K1 PCIe"
+ select PCIE_DW_COMMON
+ help
+ Say Y here if you want to enable PCIe controller support on
+ K1 SoC.
+
endif
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index e6d71fd172b..80b1ac04195 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -58,3 +58,4 @@ obj-$(CONFIG_PCIE_PLDA_COMMON) += pcie_plda_common.o
obj-$(CONFIG_PCIE_STARFIVE_JH7110) += pcie_starfive_jh7110.o
obj-$(CONFIG_PCIE_DW_AMD) += pcie_dw_amd.o
obj-$(CONFIG_PCIE_DW_IMX) += pcie_dw_imx.o
+obj-$(CONFIG_PCIE_DW_SPACEMIT) += pcie_dw_spacemit.o
diff --git a/drivers/pci/pcie_dw_spacemit.c b/drivers/pci/pcie_dw_spacemit.c
new file mode 100644
index 00000000000..037b08cfe86
--- /dev/null
+++ b/drivers/pci/pcie_dw_spacemit.c
@@ -0,0 +1,383 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Spacemit K1 PCIe host controller driver
+ *
+ * Copyright (c) 2023, Spacemit Corporation.
+ * Copyright (c) 2026, RISCstar Ltd.
+ *
+ */
+#include <asm/global_data.h>
+#include <asm/io.h>
+#include <asm-generic/gpio.h>
+#include <clk.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <dm/ofnode.h>
+#include <dm/read.h>
+#include <generic-phy.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <log.h>
+#include <pci.h>
+#include <power-domain.h>
+#include <reset.h>
+
+#include "pcie_dw_common.h"
+#include "pcie_dw_spacemit.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct pcie_dw_spacemit {
+ /* Must be first member of the struct */
+ struct pcie_dw dw;
+ void __iomem *apmu_base;
+ u32 apmu_offset;
+ void __iomem *phy_ahb; /* DT "link" */
+ int port_id;
+
+ /* reset, clock resources */
+ struct clk_bulk clks;
+ struct reset_ctl_bulk rsts;
+ struct gpio_desc pwr_on_gpio;
+ int power_on_status;
+};
+
+static inline u32 spacemit_pcie_readl(struct pcie_dw_spacemit *pcie, u32 offset)
+{
+ return readl(pcie->apmu_base + pcie->apmu_offset + offset);
+}
+
+static inline void spacemit_pcie_writel(struct pcie_dw_spacemit *pcie,
+ u32 offset, u32 value)
+{
+ writel(value, pcie->apmu_base + pcie->apmu_offset + offset);
+}
+
+static inline u32 spacemit_pcie_phy_ahb_readl(struct pcie_dw_spacemit *pcie, u32 offset)
+{
+ return readl(pcie->phy_ahb + offset);
+}
+
+static void pcie_dw_configure(struct pcie_dw_spacemit *pci, u32 cap_speed)
+{
+ u32 val;
+
+ dw_pcie_dbi_write_enable(&pci->dw, true);
+
+ val = readl(pci->dw.dbi_base + PCIE_LINK_CAPABILITY);
+ val &= ~TARGET_LINK_SPEED_MASK;
+ val |= cap_speed;
+ writel(val, pci->dw.dbi_base + PCIE_LINK_CAPABILITY);
+
+ val = readl(pci->dw.dbi_base + PCIE_LINK_CTL_2);
+ val &= ~TARGET_LINK_SPEED_MASK;
+ val |= cap_speed;
+ writel(val, pci->dw.dbi_base + PCIE_LINK_CTL_2);
+
+ dw_pcie_dbi_write_enable(&pci->dw, false);
+}
+
+static int is_link_up(struct pcie_dw_spacemit *pci)
+{
+ u32 val;
+
+ val = readl(pci->dw.dbi_base + PCIE_PORT_DEBUG0);
+ val &= PORT_LOGIC_LTSSM_STATE_MASK;
+
+ return (val == PORT_LOGIC_LTSSM_STATE_L0);
+}
+
+static int wait_link_up(struct pcie_dw_spacemit *pci)
+{
+ unsigned long timeout;
+
+ timeout = get_timer(0) + PCIE_LINK_UP_TIMEOUT_MS;
+ while (!is_link_up(pci)) {
+ if (get_timer(0) > timeout) {
+ dev_dbg(pci->dw.dev, "LTSSM link training timeout\n");
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int pcie_dw_spacemit_pcie_link_up(struct pcie_dw_spacemit *pci, u32 cap_speed)
+{
+ u32 reg;
+
+ if (is_link_up(pci)) {
+ printf("PCI Link already up before configuration!\n");
+ return 1;
+ }
+
+ /* DW pre link configurations */
+ pcie_dw_configure(pci, cap_speed);
+
+ /* Initiate link training */
+ reg = spacemit_pcie_readl(pci, PCIECTRL_K1X_CONF_DEVICE_CMD);
+ reg |= LTSSM_EN;
+ reg &= ~APP_HOLD_PHY_RST;
+ spacemit_pcie_writel(pci, PCIECTRL_K1X_CONF_DEVICE_CMD, reg);
+
+ /* Check that link was established */
+ if (!wait_link_up(pci))
+ return 0;
+
+ /*
+ * Link can be established in Gen 1, still need to wait
+ * until MAC negotiation is completed
+ */
+ udelay(100);
+
+ return 1;
+}
+
+static int pcie_set_mode(struct pcie_dw_spacemit *pci,
+ enum dw_pcie_device_mode mode)
+{
+ u32 reg;
+
+ switch (mode) {
+ case DW_PCIE_RC_TYPE:
+ reg = spacemit_pcie_readl(pci, PCIECTRL_K1X_CONF_DEVICE_CMD);
+ reg |= DEVICE_TYPE_RC | PCIE_AUX_PWR_DET;
+ spacemit_pcie_writel(pci, PCIECTRL_K1X_CONF_DEVICE_CMD, reg);
+
+ reg = spacemit_pcie_readl(pci, PCIE_CTRL_LOGIC);
+ reg |= PCIE_IGNORE_PERSTN;
+ spacemit_pcie_writel(pci, PCIE_CTRL_LOGIC, reg);
+ break;
+ case DW_PCIE_EP_TYPE:
+ reg = spacemit_pcie_readl(pci, PCIECTRL_K1X_CONF_DEVICE_CMD);
+ reg &= ~DEVICE_TYPE_RC;
+ spacemit_pcie_writel(pci, PCIECTRL_K1X_CONF_DEVICE_CMD, reg);
+ break;
+ default:
+ dev_err(pci->dw.dev, "INVALID device type %d\n", mode);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int spacemit_pcie_host_init(struct pcie_dw_spacemit *pci)
+{
+ u32 reg;
+
+ mdelay(100);
+ /* set Perst# gpio high state*/
+ reg = spacemit_pcie_readl(pci, PCIECTRL_K1X_CONF_DEVICE_CMD);
+ reg &= ~PCIE_RC_PERST;
+ spacemit_pcie_writel(pci, PCIECTRL_K1X_CONF_DEVICE_CMD, reg);
+
+ return 0;
+}
+
+static int pcie_dw_init_id(struct pcie_dw_spacemit *pci)
+{
+ dw_pcie_dbi_write_enable(&pci->dw, true);
+ writew(SPACEMIT_PCIE_VENDOR_ID, pci->dw.dbi_base + PCI_VENDOR_ID);
+ writew(SPACEMIT_PCIE_DEVICE_ID, pci->dw.dbi_base + PCI_DEVICE_ID);
+ dw_pcie_dbi_write_enable(&pci->dw, false);
+
+ return 0;
+}
+
+static int k1x_power_on(struct pcie_dw_spacemit *pci, int on)
+{
+ struct pcie_dw *dwp = &pci->dw;
+ int gpio_val = 0;
+
+ if (on) {
+ if (pci->power_on_status)
+ gpio_val = 1;
+ else
+ gpio_val = 0;
+ } else {
+ if (pci->power_on_status)
+ gpio_val = 0;
+ else
+ gpio_val = 1;
+ }
+
+ if (dm_gpio_is_valid(&pci->pwr_on_gpio)) {
+ dev_info(dwp->dev, "PCIe interface power %s, set gpio %d to %d\n",
+ on ? "on" : "off", pci->pwr_on_gpio.offset, gpio_val);
+ dm_gpio_set_value(&pci->pwr_on_gpio, gpio_val);
+ }
+
+ return 0;
+}
+
+static int pcie_dw_spacemit_probe(struct udevice *dev)
+{
+ struct pcie_dw_spacemit *pci = dev_get_priv(dev);
+ struct udevice *ctlr = pci_get_controller(dev);
+ struct pci_controller *hose = dev_get_uclass_priv(ctlr);
+ struct phy phy0 = {0};
+ int ret;
+ u32 reg;
+
+ /* enable pcie clk and deassert resets */
+ clk_enable_bulk(&pci->clks);
+ reset_deassert_bulk(&pci->rsts);
+
+ reg = spacemit_pcie_readl(pci, PCIECTRL_K1X_CONF_DEVICE_CMD);
+ reg &= ~LTSSM_EN;
+ spacemit_pcie_writel(pci, PCIECTRL_K1X_CONF_DEVICE_CMD, reg);
+
+ /* set Perst# (fundamental reset) gpio low state*/
+ reg = spacemit_pcie_readl(pci, PCIECTRL_K1X_CONF_DEVICE_CMD);
+ reg |= PCIE_RC_PERST;
+ spacemit_pcie_writel(pci, PCIECTRL_K1X_CONF_DEVICE_CMD, reg);
+
+ ret = generic_phy_get_by_name(dev, "pcie-phy", &phy0);
+ if (!ret) {
+ reg = spacemit_pcie_readl(pci, PCIECTRL_K1X_CONF_DEVICE_CMD);
+ reg |= DEVICE_TYPE_RC | APP_HOLD_PHY_RST | PCIE_PHY_LANE_CTRL_MASK;
+ reg &= ~GLOBAL_PHY_RST;
+ spacemit_pcie_writel(pci, PCIECTRL_K1X_CONF_DEVICE_CMD, reg);
+ reg &= ~(u32)APP_HOLD_PHY_RST;
+ spacemit_pcie_writel(pci, PCIECTRL_K1X_CONF_DEVICE_CMD, reg);
+
+ ret = generic_phy_init(&phy0);
+ if (ret) {
+ dev_err(dev, "failed to init PHY: %d\n", ret);
+ return ret;
+ }
+
+ ret = generic_phy_power_on(&phy0);
+ if (ret) {
+ dev_err(dev, "failed to power on PHY: %d\n", ret);
+ return ret;
+ }
+ } else {
+ dev_err(dev, "failed to get pcie-phy: %d\n", ret);
+ return ret;
+ }
+
+ pci->dw.first_busno = dev_seq(dev);
+ pci->dw.dev = dev;
+
+ pcie_set_mode(pci, DW_PCIE_RC_TYPE);
+
+ /* power on the interface */
+ k1x_power_on(pci, 1);
+
+ spacemit_pcie_host_init(pci);
+ pcie_dw_setup_host(&pci->dw);
+ pcie_dw_init_id(pci);
+
+ if (!pcie_dw_spacemit_pcie_link_up(pci, LINK_SPEED_GEN_1)) {
+ printf("PCIE-%d: Link down\n", dev_seq(dev));
+ return -ENODEV;
+ }
+
+ printf("PCIE-%d: Link up (Gen%d-x%d, Bus%d)\n", dev_seq(dev),
+ pcie_dw_get_link_speed(&pci->dw),
+ pcie_dw_get_link_width(&pci->dw),
+ hose->first_busno);
+
+ ret = pcie_dw_prog_outbound_atu_unroll(&pci->dw, PCIE_ATU_REGION_INDEX0,
+ PCIE_ATU_TYPE_MEM,
+ pci->dw.mem.phys_start,
+ pci->dw.mem.bus_start,
+ pci->dw.mem.size);
+
+ return 0;
+}
+
+static int pcie_dw_spacemit_of_to_plat(struct udevice *dev)
+{
+ int ret = 0;
+ struct pcie_dw_spacemit *pcie = dev_get_priv(dev);
+ struct ofnode_phandle_args args;
+ fdt_addr_t dbi_addr;
+
+ /* Get the controller base address */
+ pcie->dw.dbi_base = (void *)dev_read_addr_name(dev, "dbi");
+ if ((fdt_addr_t)pcie->dw.dbi_base == FDT_ADDR_T_NONE)
+ return -EINVAL;
+
+ dbi_addr = (fdt_addr_t)pcie->dw.dbi_base;
+
+ /* Get the config space base address and size */
+ pcie->dw.cfg_base = (void *)dev_read_addr_size_name(dev, "config",
+ &pcie->dw.cfg_size);
+ if ((fdt_addr_t)pcie->dw.cfg_base == FDT_ADDR_T_NONE)
+ return -EINVAL;
+
+ /* Get the iATU base address and size */
+ pcie->dw.atu_base = (void *)dev_read_addr_name(dev, "atu");
+ if ((fdt_addr_t)pcie->dw.atu_base == FDT_ADDR_T_NONE)
+ return -EINVAL;
+
+ /* Get the PHY AHB base address (called "link" in Linux DTS) */
+ pcie->phy_ahb = (void *)dev_read_addr_name(dev, "link");
+ if ((fdt_addr_t)pcie->phy_ahb == FDT_ADDR_T_NONE) {
+ pcie->phy_ahb = (void *)dev_read_addr_name(dev, "phy_ahb");
+ if ((fdt_addr_t)pcie->phy_ahb == FDT_ADDR_T_NONE)
+ return -EINVAL;
+ }
+
+ /* Get APMU regmap from spacemit,apmu property */
+ ret = dev_read_phandle_with_args(dev, "spacemit,apmu", NULL, 1, 0,
+ &args);
+ if (ret)
+ return ret;
+
+ pcie->apmu_base = (void __iomem *)ofnode_get_addr(args.node);
+ if (!pcie->apmu_base)
+ return -EINVAL;
+ pcie->apmu_offset = args.args[0];
+
+ /* Derive port ID from DBI base address */
+ pcie->port_id = (dbi_addr - SPACEMIT_PCIE_DBI_BASE) / SPACEMIT_PCIE_DBI_STRIDE;
+
+ ret = clk_get_bulk(dev, &pcie->clks);
+ if (ret) {
+ dev_warn(dev, "failed to get clocks: %d\n", ret);
+ return ret;
+ }
+
+ ret = reset_get_bulk(dev, &pcie->rsts);
+ if (ret) {
+ dev_warn(dev, "failed to get resets: %d\n", ret);
+ return ret;
+ }
+
+ gpio_request_by_name(dev, "k1x,pwr_on", 0, &pcie->pwr_on_gpio,
+ GPIOD_IS_OUT);
+ if (!dm_gpio_is_valid(&pcie->pwr_on_gpio))
+ dev_info(dev, "has no power on gpio.\n");
+
+ ret = dev_read_u32(dev, "power-on-status", &pcie->power_on_status);
+ if (ret) {
+ dev_info(dev, "has no power-on-status flag, use default.\n");
+ pcie->power_on_status = 1;
+ }
+
+ return 0;
+}
+
+static const struct dm_pci_ops pcie_dw_spacemit_ops = {
+ .read_config = pcie_dw_read_config,
+ .write_config = pcie_dw_write_config,
+};
+
+static const struct udevice_id pcie_dw_spacemit_ids[] = {
+ { .compatible = "spacemit,k1-pcie" },
+ { }
+};
+
+U_BOOT_DRIVER(pcie_dw_spacemit) = {
+ .name = "pcie_dw_spacemit",
+ .id = UCLASS_PCI,
+ .of_match = pcie_dw_spacemit_ids,
+ .ops = &pcie_dw_spacemit_ops,
+ .of_to_plat = pcie_dw_spacemit_of_to_plat,
+ .probe = pcie_dw_spacemit_probe,
+ .priv_auto = sizeof(struct pcie_dw_spacemit),
+};
diff --git a/drivers/pci/pcie_dw_spacemit.h b/drivers/pci/pcie_dw_spacemit.h
new file mode 100644
index 00000000000..2c17f113b75
--- /dev/null
+++ b/drivers/pci/pcie_dw_spacemit.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Spacemit K1 DesignWare PCIe host controller driver
+ *
+ * Copyright (c) 2023, Spacemit Corporation.
+ * Copyright (c) 2026, RISCstar Ltd.
+ */
+
+#ifndef _PCIE_DW_SPACEMIT_H_
+#define _PCIE_DW_SPACEMIT_H_
+
+#include <linux/bitops.h>
+
+/* PCIe DW common registers */
+#define PCIE_LINK_CAPABILITY 0x7c
+#define PCIE_LINK_CTL_2 0xa0
+#define TARGET_LINK_SPEED_MASK 0xf
+#define LINK_SPEED_GEN_1 0x1
+#define LINK_SPEED_GEN_2 0x2
+#define LINK_SPEED_GEN_3 0x3
+
+#define PCIE_MISC_CONTROL_1_OFF 0x8bc
+#define PCIE_DBI_RO_WR_EN BIT(0)
+
+#define PLR_OFFSET 0x700
+#define PCIE_PORT_DEBUG0 (PLR_OFFSET + 0x28)
+#define PORT_LOGIC_LTSSM_STATE_MASK 0x1f
+#define PORT_LOGIC_LTSSM_STATE_L0 0x11
+
+#define PCIE_LINK_UP_TIMEOUT_MS 1000
+
+/* Vendor and device IDs */
+#define PCIE_VENDORID_MASK GENMASK(15, 0)
+#define PCIE_DEVICEID_SHIFT 16
+#define SPACEMIT_PCIE_VENDOR_ID 0x201F
+#define SPACEMIT_PCIE_DEVICE_ID 0x0001
+
+/* Application register offsets */
+#define PCIE_CMD_STATUS 0x04
+#define LTSSM_EN_VAL BIT(0)
+
+/* K1X_CONF_DEVICE_CMD register */
+#define PCIECTRL_K1X_CONF_DEVICE_CMD 0x0000
+#define LTSSM_EN BIT(6)
+#define PCIE_PERST_IN BIT(7)
+#define GLOBAL_PHY_RST BIT(8)
+#define PCIE_AUX_PWR_DET BIT(9)
+#define PCIE_CLKREQ_IN BIT(10) /* read-only: CLKREQ# IO input value */
+#define PCIE_REFCLK_EN BIT(11)
+#define PCIE_RC_PERST BIT(12)
+#define PCIE_EP_WAKE BIT(13)
+#define APP_HOLD_PHY_RST BIT(30)
+#define DEVICE_TYPE_RC BIT(31)
+
+/* PCIE_CTRL_LOGIC register */
+#define PCIE_CTRL_LOGIC 0x0004
+#define PCIE_SOFT_RESET BIT(0)
+#define PCIE_IGNORE_PERSTN BIT(2)
+
+/* PHY AHB link status */
+#define K1X_PHY_AHB_LINK_STS 0x0004
+#define SMLH_LINK_UP BIT(1)
+#define RDLH_LINK_UP BIT(12)
+
+/* PHY lane control mask (bits 0-5 in DEVICE_CMD) */
+#define PCIE_PHY_LANE_CTRL_MASK 0x3f
+
+/* PCIe DBI address calculation */
+#define SPACEMIT_PCIE_DBI_BASE 0xca000000
+#define SPACEMIT_PCIE_DBI_STRIDE 0x400000
+
+enum dw_pcie_device_mode {
+ DW_PCIE_UNKNOWN_TYPE,
+ DW_PCIE_EP_TYPE,
+ DW_PCIE_RC_TYPE,
+};
+
+#endif /* _PCIE_DW_SPACEMIT_H_ */
--
2.51.0
More information about the U-Boot
mailing list