[PATCH 3/5] pci: Add support for CPM4 and CPM5 PCIe controllers

Pranav Sanwal <[email protected]>
Newsgroups org.u-boot-project.lists.u-boot
Message-ID <529f1b16b05ee843cb89045798631cae6361db6b.1787554549.git.pranav.sanwal@amd.com>
Add a host controller driver for the AMD Versal CPM4 and CPM5 PCIe
bridges, selected via the DT compatible string and enabled with
CONFIG_PCIE_AMD_CPM. The driver maps the bridge/ECAM regions,
services ECAM-based config space accesses, masks off all local
interrupts, and brings up the root port bridge. CPM5 variants also
support an optional PERST#-driven IP reset sequence ahead of link
bring-up.

Co-developed-by: Uday Hegde <[email protected]>
Signed-off-by: Uday Hegde <[email protected]>
Signed-off-by: Pranav Sanwal <[email protected]>
---
 MAINTAINERS                |   5 +
 drivers/pci/Kconfig        |   9 +
 drivers/pci/Makefile       |   1 +
 drivers/pci/pcie_amd_cpm.c | 363 +++++++++++++++++++++++++++++++++++++
 4 files changed, 378 insertions(+)
 create mode 100644 drivers/pci/pcie_amd_cpm.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 4f184a39a9d..0cc8670cfe4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -71,6 +71,11 @@ F:	include/alist.h
 F:	lib/alist.c
 F:	test/lib/alist.c
 
+AMD CPM PCIE DRIVER
+M:	Pranav Sanwal <[email protected]>
+S:	Maintained
+F:	drivers/pci/pcie_amd_cpm.c
+
 AMD QDMA PL PCIE DRIVER
 M:	Pranav Sanwal <[email protected]>
 S:	Maintained
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index da2bebafe9a..64bab2b9dd6 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -444,6 +444,15 @@ config PCIE_XILINX_NWL
 	  Say 'Y' here if you want support for Xilinx / AMD NWL PCIe
 	  controller as Root Port.
 
+config PCIE_AMD_CPM
+	bool "AMD Versal CPM PCIe host bridge support"
+	depends on ARCH_VERSAL
+	depends on DM_GPIO
+	select SYS_PCI_64BIT
+	help
+	  Say 'Y' here if you want support for the AMD Versal
+	  CPM PCIe host bridge.
+
 config PCIE_AMD_QDMA_PL
 	bool "AMD QDMA PL PCIe host bridge support"
 	depends on ARCH_VERSAL
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index a6891154e9e..3873f357a45 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -54,6 +54,7 @@ obj-$(CONFIG_PCIE_OCTEON) += pcie_octeon.o
 obj-$(CONFIG_PCIE_DW_SIFIVE) += pcie_dw_sifive.o
 obj-$(CONFIG_PCIE_UNIPHIER) += pcie_uniphier.o
 obj-$(CONFIG_PCIE_XILINX_NWL) += pcie-xilinx-nwl.o
+obj-$(CONFIG_PCIE_AMD_CPM) += pcie_amd_cpm.o
 obj-$(CONFIG_PCIE_AMD_QDMA_PL) += pcie_amd_qdma_pl.o
 obj-$(CONFIG_PCIE_PLDA_COMMON) += pcie_plda_common.o
 obj-$(CONFIG_PCIE_STARFIVE_JH7110) += pcie_starfive_jh7110.o
diff --git a/drivers/pci/pcie_amd_cpm.c b/drivers/pci/pcie_amd_cpm.c
new file mode 100644
index 00000000000..8377e772792
--- /dev/null
+++ b/drivers/pci/pcie_amd_cpm.c
@@ -0,0 +1,363 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * PCIe host controller driver for AMD CPM
+ *
+ * Copyright (C) 2026 Advanced Micro Devices, Inc.
+ */
+
+#define LOG_CATEGORY UCLASS_PCI
+
+#include <dm.h>
+#include <pci.h>
+
+#include <asm/gpio.h>
+#include <dm/device_compat.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+
+/*
+ * End of conventional reset (PERST# de-asserted) to first configuration
+ * request (device able to respond with a "Request Retry Status" completion),
+ * from PCIe r6.0, sec 6.6.1.
+ */
+#define PCIE_T_RRS_READY_MS	100
+
+/*
+ * "PERST# active time", as per Table 2-10: Power Sequencing and Reset
+ * Signal Timings of the PCIe Electromechanical Specification,
+ * Revision 6.0, symbol "T_PERST".
+ */
+#define PCIE_T_PERST_US		100
+
+/* Register definitions */
+#define AMD_CPM_PCIE0_RST		0x00000308
+#define AMD_CPM5_PCIE0_RST		0x00000318
+#define AMD_CPM5_PCIE1_RST		0x0000031C
+
+#define AMD_CPM_PCIE_REG_IDR		0x00000E10
+#define AMD_CPM_PCIE_REG_IMR		0x00000E14
+#define AMD_CPM_PCIE_REG_PSCR		0x00000E1C
+#define AMD_CPM_PCIE_REG_RPSC		0x00000E20
+
+/* Interrupt registers definitions */
+#define AMD_CPM_INTR_LINK_DOWN		0
+#define AMD_CPM_INTR_HOT_RESET		3
+#define AMD_CPM_INTR_CFG_PCIE_TIMEOUT	4
+#define AMD_CPM_INTR_CFG_TIMEOUT	8
+#define AMD_CPM_INTR_CORRECTABLE	9
+#define AMD_CPM_INTR_NONFATAL		10
+#define AMD_CPM_INTR_FATAL		11
+#define AMD_CPM_INTR_CFG_ERR_POISON	12
+#define AMD_CPM_INTR_PME_TO_ACK_RCVD	15
+#define AMD_CPM_INTR_INTX		16
+#define AMD_CPM_INTR_PM_PME_RCVD	17
+#define AMD_CPM_INTR_MSI		17
+#define AMD_CPM_INTR_SLV_UNSUPP		20
+#define AMD_CPM_INTR_SLV_UNEXP		21
+#define AMD_CPM_INTR_SLV_COMPL		22
+#define AMD_CPM_INTR_SLV_ERRP		23
+#define AMD_CPM_INTR_SLV_CMPABT		24
+#define AMD_CPM_INTR_SLV_ILLBUR		25
+#define AMD_CPM_INTR_MST_DECERR		26
+#define AMD_CPM_INTR_MST_SLVERR		27
+#define AMD_CPM_INTR_SLV_PCIE_TIMEOUT	28
+
+#define IMR(x) BIT(AMD_CPM_INTR_ ##x)
+
+#define AMD_CPM_PCIE_IMR_ALL_MASK		\
+	(IMR(LINK_DOWN)		| \
+	 IMR(HOT_RESET)		| \
+	 IMR(CFG_PCIE_TIMEOUT)	| \
+	 IMR(CFG_TIMEOUT)	| \
+	 IMR(CORRECTABLE)	| \
+	 IMR(NONFATAL)		| \
+	 IMR(FATAL)		| \
+	 IMR(CFG_ERR_POISON)	| \
+	 IMR(PME_TO_ACK_RCVD)	| \
+	 IMR(INTX)		| \
+	 IMR(PM_PME_RCVD)	| \
+	 IMR(SLV_UNSUPP)	| \
+	 IMR(SLV_UNEXP)		| \
+	 IMR(SLV_COMPL)		| \
+	 IMR(SLV_ERRP)		| \
+	 IMR(SLV_CMPABT)	| \
+	 IMR(SLV_ILLBUR)	| \
+	 IMR(MST_DECERR)	| \
+	 IMR(MST_SLVERR)	| \
+	 IMR(SLV_PCIE_TIMEOUT))
+
+#define AMD_CPM_PCIE_IDR_ALL_MASK		0xFFFFFFFF
+
+#define AMD_CPM_PCIE_REG_RPSC_BEN		BIT(0)
+
+#define AMD_CPM_PCIE_REG_PSCR_LNKUP		BIT(11)
+
+enum amd_cpm_version {
+	AMD_CPM_VER_1,
+	AMD_CPM_VER_5_HOST0,
+	AMD_CPM_VER_5_HOST1,
+};
+
+/**
+ * struct amd_cpm_variant - CPM variant information
+ * @version: CPM version
+ * @cpm_pcie_rst: Offset for the PCIe IP reset
+ */
+struct amd_cpm_variant {
+	enum amd_cpm_version version;
+	u32 cpm_pcie_rst;
+};
+
+/**
+ * struct amd_cpm_pcie - AMD CPM PCIe bridge private data
+ * @dev: PCIe host bridge device
+ * @reg_base: Bridge register base address
+ * @cfg_base: ECAM configuration space base address
+ * @crx_base: CPM5 reset control base address (optional)
+ * @variant: CPM version check pointer
+ */
+struct amd_cpm_pcie {
+	struct udevice *dev;
+	void __iomem *reg_base;
+	void __iomem *cfg_base;
+	void __iomem *crx_base;
+	const struct amd_cpm_variant *variant;
+};
+
+static inline u32 amd_cpm_pcie_read(struct amd_cpm_pcie *pcie, u32 reg)
+{
+	u32 val = readl(pcie->reg_base + reg);
+
+	dev_dbg(pcie->dev, "rd reg 0x%03x = 0x%08x\n", reg, val);
+
+	return val;
+}
+
+static inline void amd_cpm_pcie_write(struct amd_cpm_pcie *pcie,
+				      u32 val, u32 reg)
+{
+	dev_dbg(pcie->dev, "wr reg 0x%03x = 0x%08x\n", reg, val);
+	writel(val, pcie->reg_base + reg);
+}
+
+static bool amd_cpm_pcie_link_up(struct amd_cpm_pcie *pcie)
+{
+	return !!(amd_cpm_pcie_read(pcie, AMD_CPM_PCIE_REG_PSCR) &
+		  AMD_CPM_PCIE_REG_PSCR_LNKUP);
+}
+
+static int amd_cpm_pcie_config_address(const struct udevice *bus,
+				       pci_dev_t bdf, uint offset,
+				       void **paddress)
+{
+	struct amd_cpm_pcie *priv = dev_get_priv(bus);
+	u32 busnum = PCI_BUS(bdf) - dev_seq(bus);
+	void *addr;
+
+	addr = priv->cfg_base;
+	addr += PCIE_ECAM_OFFSET(busnum, PCI_DEV(bdf), PCI_FUNC(bdf), offset);
+	dev_dbg(priv->dev, "cfg bdf=%02x:%02x.%x offset=0x%x addr=%p\n",
+		PCI_BUS(bdf), PCI_DEV(bdf), PCI_FUNC(bdf), offset, addr);
+	*paddress = addr;
+
+	return 0;
+}
+
+/*
+ * The root port's own function (bus 0, device 0, function 0) reports a
+ * bogus 64-bit BAR0 that autoconfig can never place. Mask it off so it
+ * always reads back as not implemented.
+ */
+static bool amd_cpm_pcie_is_own_bar(const struct udevice *bus, pci_dev_t bdf,
+				    uint offset)
+{
+	u32 busnum = PCI_BUS(bdf) - dev_seq(bus);
+
+	return busnum == 0 && PCI_DEV(bdf) == 0 && PCI_FUNC(bdf) == 0 &&
+	       offset >= PCI_BASE_ADDRESS_0 && offset < PCI_BASE_ADDRESS_2;
+}
+
+static int amd_cpm_pcie_read_config(const struct udevice *bus,
+				    pci_dev_t bdf, uint offset,
+				    ulong *valuep, enum pci_size_t size)
+{
+	if (amd_cpm_pcie_is_own_bar(bus, bdf, offset)) {
+		*valuep = 0;
+		return 0;
+	}
+
+	return pci_generic_mmap_read_config(bus, amd_cpm_pcie_config_address,
+					    bdf, offset, valuep, size);
+}
+
+static int amd_cpm_pcie_write_config(struct udevice *bus, pci_dev_t bdf,
+				     uint offset, ulong value,
+				     enum pci_size_t size)
+{
+	if (amd_cpm_pcie_is_own_bar(bus, bdf, offset))
+		return 0;
+
+	return pci_generic_mmap_write_config(bus, amd_cpm_pcie_config_address,
+					     bdf, offset, value, size);
+}
+
+static const struct dm_pci_ops amd_cpm_pcie_ops = {
+	.read_config = amd_cpm_pcie_read_config,
+	.write_config = amd_cpm_pcie_write_config,
+};
+
+static int amd_cpm_pcie_host_init(struct amd_cpm_pcie *pcie)
+{
+	const struct amd_cpm_variant *variant = pcie->variant;
+	struct udevice *dev = pcie->dev;
+	struct gpio_desc *reset_gpio;
+	u32 val;
+
+	if (pcie->crx_base) {
+		reset_gpio = devm_gpiod_get_optional(dev, "reset",
+						     GPIOD_IS_OUT |
+						     GPIOD_IS_OUT_ACTIVE);
+		if (reset_gpio) {
+			/* Assert the PCIe IP reset */
+			writel(BIT(0), pcie->crx_base + variant->cpm_pcie_rst);
+
+			udelay(PCIE_T_PERST_US);
+
+			/* Deassert the PCIe IP reset */
+			writel(0, pcie->crx_base + variant->cpm_pcie_rst);
+
+			/* Deassert the reset signal */
+			dm_gpio_set_value(reset_gpio, 0);
+			mdelay(PCIE_T_RRS_READY_MS);
+		}
+	}
+
+	dev_dbg(dev, "PCIe link %s\n",
+		amd_cpm_pcie_link_up(pcie) ? "up" : "down");
+
+	/* Disable all interrupts */
+	amd_cpm_pcie_write(pcie, ~AMD_CPM_PCIE_IDR_ALL_MASK,
+			   AMD_CPM_PCIE_REG_IMR);
+
+	/* Clear pending interrupts */
+	val = amd_cpm_pcie_read(pcie, AMD_CPM_PCIE_REG_IDR);
+	amd_cpm_pcie_write(pcie, val & AMD_CPM_PCIE_IMR_ALL_MASK,
+			   AMD_CPM_PCIE_REG_IDR);
+
+	/* Enable root port bridge */
+	val = amd_cpm_pcie_read(pcie, AMD_CPM_PCIE_REG_RPSC);
+	amd_cpm_pcie_write(pcie, val | AMD_CPM_PCIE_REG_RPSC_BEN,
+			   AMD_CPM_PCIE_REG_RPSC);
+
+	return 0;
+}
+
+static int amd_cpm_pcie_map_res(struct udevice *dev, const char *name,
+				void __iomem **map)
+{
+	struct resource res;
+	int ret;
+
+	ret = dev_read_resource_byname(dev, name, &res);
+	if (ret)
+		return ret;
+
+	*map = devm_ioremap(dev, res.start, resource_size(&res));
+	if (!*map)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static int amd_cpm_pcie_parse_dt(struct amd_cpm_pcie *pcie)
+{
+	struct udevice *dev = pcie->dev;
+	int ret;
+
+	ret = amd_cpm_pcie_map_res(dev, "cfg", &pcie->cfg_base);
+	if (ret)
+		return ret;
+
+	if (pcie->variant->version == AMD_CPM_VER_1) {
+		/*
+		 * On original CPM the ECAM and bridge registers share the same
+		 * region; cfg_base and reg_base both point into it.
+		 */
+		pcie->reg_base = pcie->cfg_base;
+	} else {
+		ret = amd_cpm_pcie_map_res(dev, "cpm_csr", &pcie->reg_base);
+		if (ret)
+			return ret;
+	}
+
+	/* cpm_crx is optional; absence means no PCIe IP reset sequence */
+	amd_cpm_pcie_map_res(dev, "cpm_crx", &pcie->crx_base);
+
+	return 0;
+}
+
+static int amd_cpm_pcie_probe(struct udevice *dev)
+{
+	struct amd_cpm_pcie *priv = dev_get_priv(dev);
+	int err;
+
+	priv->dev = dev;
+	priv->variant = (const struct amd_cpm_variant *)dev_get_driver_data(dev);
+	if (!priv->variant)
+		return -EINVAL;
+
+	err = amd_cpm_pcie_parse_dt(priv);
+	if (err) {
+		dev_err(dev, "failed to parse DT: %d\n", err);
+		return err;
+	}
+
+	err = amd_cpm_pcie_host_init(priv);
+	if (err) {
+		dev_err(dev, "failed to initialize host: %d\n", err);
+		return err;
+	}
+
+	return 0;
+}
+
+static const struct amd_cpm_variant cpm_host = {
+	.version = AMD_CPM_VER_1,
+	.cpm_pcie_rst = AMD_CPM_PCIE0_RST,
+};
+
+static const struct amd_cpm_variant cpm5_host = {
+	.version = AMD_CPM_VER_5_HOST0,
+	.cpm_pcie_rst = AMD_CPM5_PCIE0_RST,
+};
+
+static const struct amd_cpm_variant cpm5_host1 = {
+	.version = AMD_CPM_VER_5_HOST1,
+	.cpm_pcie_rst = AMD_CPM5_PCIE1_RST,
+};
+
+static const struct udevice_id amd_cpm_pcie_of_match[] = {
+	{
+		.compatible = "xlnx,versal-cpm-host-1.00",
+		.data = (ulong)&cpm_host,
+	},
+	{
+		.compatible = "xlnx,versal-cpm5-host",
+		.data = (ulong)&cpm5_host,
+	},
+	{
+		.compatible = "xlnx,versal-cpm5-host1",
+		.data = (ulong)&cpm5_host1,
+	},
+	{ /* sentinel */ }
+};
+
+U_BOOT_DRIVER(amd_cpm_pcie) = {
+	.name = "amd_cpm_pcie",
+	.id = UCLASS_PCI,
+	.of_match = amd_cpm_pcie_of_match,
+	.probe = amd_cpm_pcie_probe,
+	.priv_auto = sizeof(struct amd_cpm_pcie),
+	.ops = &amd_cpm_pcie_ops,
+};
-- 
2.43.7
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.