[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