[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