[PATCH 2/5] pci: Add support for AMD QDMA PL PCIe root port
Pranav Sanwal <[email protected]>
| Newsgroups | org.u-boot-project.lists.u-boot |
|---|---|
| Message-ID | <24e0671496322ea483c359d586107c00d7006ce6.1787554549.git.pranav.sanwal@amd.com> |
Add PCIe host bridge driver for AMD QDMA PL (Programmable Logic) soft IP PCIe5 controller. This driver enables PCIe root port functionality for QDMA IP instantiated in the PL fabric. 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 | 10 ++ drivers/pci/Makefile | 1 + drivers/pci/pcie_amd_qdma_pl.c | 205 +++++++++++++++++++++++++++++++++ 4 files changed, 221 insertions(+) create mode 100644 drivers/pci/pcie_amd_qdma_pl.c diff --git a/MAINTAINERS b/MAINTAINERS index eb48eea55c5..4f184a39a9d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -71,6 +71,11 @@ F: include/alist.h F: lib/alist.c F: test/lib/alist.c +AMD QDMA PL PCIE DRIVER +M: Pranav Sanwal <[email protected]> +S: Maintained +F: drivers/pci/pcie_amd_qdma_pl.c + AMD VERSAL2 PCIE DRIVER M: Pranav Sanwal <[email protected]> S: Maintained diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 2b2ccdf4745..da2bebafe9a 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -444,6 +444,16 @@ config PCIE_XILINX_NWL Say 'Y' here if you want support for Xilinx / AMD NWL PCIe controller as Root Port. +config PCIE_AMD_QDMA_PL + bool "AMD QDMA PL PCIe host bridge support" + depends on ARCH_VERSAL + select SYS_PCI_64BIT + help + Say 'Y' here if you want support for the Xilinx/AMD QDMA + PL PCIe host bridge. The controller is a Soft IP instantiated + in the PL fabric which can act as a PCIe Root Port. It provides + ECAM-based configuration space access for device enumeration. + config PCIE_PLDA_COMMON bool diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index e6d71fd172b..a6891154e9e 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_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 obj-$(CONFIG_PCIE_DW_AMD) += pcie_dw_amd.o diff --git a/drivers/pci/pcie_amd_qdma_pl.c b/drivers/pci/pcie_amd_qdma_pl.c new file mode 100644 index 00000000000..2143b0a1a27 --- /dev/null +++ b/drivers/pci/pcie_amd_qdma_pl.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PCIe host bridge driver for AMD QDMA PL PCIe bridge (no XDMA support) + * + * Copyright (C) 2026 Advanced Micro Devices, Inc. + */ + +#define LOG_CATEGORY UCLASS_PCI + +#include <dm.h> +#include <pci.h> + +#include <dm/device_compat.h> +#include <linux/delay.h> +#include <linux/io.h> + +/* Register definitions */ +#define AMD_QDMA_REG_IDR 0x138 +#define AMD_QDMA_REG_IMR 0x13c +#define AMD_QDMA_REG_PSCR 0x144 +#define AMD_QDMA_REG_RPSC 0x148 + +#define AMD_QDMA_IMR_ALL_MASK 0x0ff30fe9 +#define AMD_QDMA_IDR_ALL_MASK 0xffffffff + +/* Root Port Status/control Register definitions */ +#define AMD_QDMA_REG_RPSC_BEN BIT(0) + +/* Phy Status/Control Register definitions */ +#define AMD_QDMA_REG_PSCR_LNKUP BIT(11) +#define QDMA_BRIDGE_BASE_OFF 0xcd8 + +/* Parameters for the waiting for link up routine */ +#define LINK_WAIT_MAX_RETRIES 10 +#define LINK_WAIT_DELAY_US 90000 + +/** + * struct amd_qdma_pcie - AMD QDMA PL PCIe bridge private data + * @dev: PCIe host bridge device + * @breg_base: Bridge register base address + * @ecam_base: ECAM configuration space base address + */ +struct amd_qdma_pcie { + struct udevice *dev; + void __iomem *breg_base; + void __iomem *ecam_base; +}; + +static inline u32 amd_qdma_pcie_read(struct amd_qdma_pcie *pcie, u32 reg) +{ + u32 val = readl(pcie->breg_base + reg + QDMA_BRIDGE_BASE_OFF); + + dev_dbg(pcie->dev, "rd reg 0x%03x = 0x%08x\n", reg, val); + + return val; +} + +static inline void amd_qdma_pcie_write(struct amd_qdma_pcie *pcie, u32 val, + u32 reg) +{ + dev_dbg(pcie->dev, "wr reg 0x%03x = 0x%08x\n", reg, val); + writel(val, pcie->breg_base + reg + QDMA_BRIDGE_BASE_OFF); +} + +static bool amd_qdma_pcie_link_up(struct amd_qdma_pcie *pcie) +{ + return !!(amd_qdma_pcie_read(pcie, AMD_QDMA_REG_PSCR) & + AMD_QDMA_REG_PSCR_LNKUP); +} + +static int amd_qdma_pcie_config_address(const struct udevice *bus, + pci_dev_t bdf, uint offset, + void **paddress) +{ + struct amd_qdma_pcie *priv = dev_get_priv(bus); + u32 busnum = PCI_BUS(bdf) - dev_seq(bus); + void *addr; + + if (busnum == 0) { + if (PCI_DEV(bdf) > 0 || PCI_FUNC(bdf) > 0) + return -ENODEV; + } else { + if (!amd_qdma_pcie_link_up(priv)) + return -ENODEV; + } + + addr = priv->ecam_base; + addr += PCIE_ECAM_OFFSET(busnum, PCI_DEV(bdf), PCI_FUNC(bdf), offset); + *paddress = addr; + + return 0; +} + +static int amd_qdma_pcie_read_config(const struct udevice *bus, + pci_dev_t bdf, uint offset, + ulong *valuep, enum pci_size_t size) +{ + return pci_generic_mmap_read_config(bus, amd_qdma_pcie_config_address, + bdf, offset, valuep, size); +} + +static int amd_qdma_pcie_write_config(struct udevice *bus, pci_dev_t bdf, + uint offset, ulong value, + enum pci_size_t size) +{ + /* + * The QDMA PL bridge's config space only decodes 32-bit accesses; + * narrower writes must be promoted to a 32-bit read-modify-write. + */ + return pci_generic_mmap_write_config32(bus, amd_qdma_pcie_config_address, + bdf, offset, value, size); +} + +static const struct dm_pci_ops amd_qdma_pcie_ops = { + .read_config = amd_qdma_pcie_read_config, + .write_config = amd_qdma_pcie_write_config, +}; + +static int amd_qdma_pcie_bridge_init(struct amd_qdma_pcie *pcie) +{ + int retries; + u32 val; + + /* Check if the link is up or not */ + for (retries = 0; retries < LINK_WAIT_MAX_RETRIES; retries++) { + if (amd_qdma_pcie_link_up(pcie)) + break; + udelay(LINK_WAIT_DELAY_US); + } + + if (retries >= LINK_WAIT_MAX_RETRIES) { + dev_warn(pcie->dev, "PHY link never came up\n"); + return -ETIMEDOUT; + } + + dev_dbg(pcie->dev, "PCIe Link is UP\n"); + + /* Disable all interrupts */ + amd_qdma_pcie_write(pcie, ~AMD_QDMA_IDR_ALL_MASK, AMD_QDMA_REG_IMR); + + /* Clear pending interrupts */ + val = amd_qdma_pcie_read(pcie, AMD_QDMA_REG_IDR) & AMD_QDMA_IMR_ALL_MASK; + amd_qdma_pcie_write(pcie, val, AMD_QDMA_REG_IDR); + + /* Set the Bridge enable bit */ + val = amd_qdma_pcie_read(pcie, AMD_QDMA_REG_RPSC); + amd_qdma_pcie_write(pcie, val | AMD_QDMA_REG_RPSC_BEN, AMD_QDMA_REG_RPSC); + + return 0; +} + +static int amd_qdma_pcie_parse_dt(struct amd_qdma_pcie *priv) +{ + struct udevice *dev = priv->dev; + + priv->breg_base = dev_read_addr_name_ptr(dev, "breg"); + if (!priv->breg_base) { + dev_err(dev, "failed to get breg address\n"); + return -EINVAL; + } + + priv->ecam_base = dev_read_addr_name_ptr(dev, "cfg"); + if (!priv->ecam_base) { + dev_err(dev, "failed to get cfg address\n"); + return -EINVAL; + } + + return 0; +} + +static int amd_qdma_pcie_probe(struct udevice *dev) +{ + struct amd_qdma_pcie *priv = dev_get_priv(dev); + int err; + + priv->dev = dev; + + err = amd_qdma_pcie_parse_dt(priv); + if (err) { + dev_err(dev, "Parsing DT failed\n"); + return err; + } + + err = amd_qdma_pcie_bridge_init(priv); + if (err) { + dev_err(dev, "HW Initialization failed\n"); + return err; + } + + return 0; +} + +static const struct udevice_id amd_qdma_pcie_of_match[] = { + { .compatible = "xlnx,qdma-host-3.00", }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(amd_qdma_pcie) = { + .name = "amd_qdma_pcie", + .id = UCLASS_PCI, + .of_match = amd_qdma_pcie_of_match, + .probe = amd_qdma_pcie_probe, + .priv_auto = sizeof(struct amd_qdma_pcie), + .ops = &amd_qdma_pcie_ops, +}; -- 2.43.7