[PATCH] pci: add support for Broadcom BCM2712 SoC PCIe controller
Peter Robinson
pbrobinson at gmail.com
Tue Apr 28 22:42:54 CEST 2026
On Tue, 28 Apr 2026 at 19:39, Beniamin Sandu <beniaminsandu at gmail.com> wrote:
>
> Tested on a RPI5 with an M2 hat:
That's not an appropriate commit message.
> U-Boot> nvme scan
> U-Boot> nvme info
> Device 0: Vendor: 0x144d Rev: 2B2QKXG7 Prod: S7U5NU0YB20649N
> Type: Hard Disk
> Capacity: 953869.7 MB = 931.5 GB (1953525168 x 512)
> U-Boot> nvme part
>
> Partition Map for nvme device 0 -- Partition Type: DOS
>
> Part Start Sector Num Sectors UUID Type
> 1 8192 524288 a4192dbd-01 0c Boot
> U-Boot> ls nvme 0:1 /
> 31064576 Image
> 78231 bcm2712-rpi-5-b.dtb
> 78945 bcm2712-rpi-cm5-cm4io.dtb
> 79011 bcm2712-rpi-cm5-cm5io.dtb
> 78986 bcm2712-rpi-cm5l-cm4io.dtb
> 79052 bcm2712-rpi-cm5l-cm5io.dtb
> 52476 bootcode.bin
> 90 cmdline.txt
> 2592 config.txt
> 7325 fixup.dat
> 5456 fixup4.dat
> 3230 fixup4cd.dat
> 8449 fixup4db.dat
> 8449 fixup4x.dat
> 3230 fixup_cd.dat
> 10294 fixup_db.dat
> 10290 fixup_x.dat
> 713432 kernel_2712.img
> 2988128 start.elf
> 2263968 start4.elf
> 814140 start4cd.elf
> 3762408 start4db.elf
> 3011592 start4x.elf
> 814140 start_cd.elf
> 4834408 start_db.elf
> 3735336 start_x.elf
> overlays/
This is irrelevant for a commit message.
> 26 file(s), 1 dir(s)
>
> Signed-off-by: Beniamin Sandu <beniaminsandu at gmail.com>
Have you looked at or tested Thorsten's series, have you reached out
to him on it?
> ---
> .../mach-bcm283x/include/mach/acpi/bcm2711.h | 2 +-
> .../mach-bcm283x/include/mach/acpi/bcm2712.h | 44 +++
> arch/arm/mach-bcm283x/init.c | 13 +-
> drivers/nvme/nvme.c | 62 +++-
> drivers/pci/Kconfig | 3 +-
> drivers/pci/pcie_brcmstb.c | 334 +++++++++++++++---
> 6 files changed, 390 insertions(+), 68 deletions(-)
> create mode 100644 arch/arm/mach-bcm283x/include/mach/acpi/bcm2712.h
>
> diff --git a/arch/arm/mach-bcm283x/include/mach/acpi/bcm2711.h b/arch/arm/mach-bcm283x/include/mach/acpi/bcm2711.h
> index a86875b1833..d9d3c2e3c9c 100644
> --- a/arch/arm/mach-bcm283x/include/mach/acpi/bcm2711.h
> +++ b/arch/arm/mach-bcm283x/include/mach/acpi/bcm2711.h
> @@ -93,7 +93,7 @@
> #define PCIE_MEM_WIN0_LIMIT_HI(win) \
> PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LIMIT_HI + ((win) * 8)
>
> -#define PCIE_MISC_HARD_PCIE_HARD_DEBUG 0x4204
> +#define PCIE_MISC_HARD_PCIE_HARD_DEBUG_BCM2711 0x4204
> #define PCIE_HARD_DEBUG_SERDES_IDDQ_MASK 0x08000000
>
> #define PCIE_INTR2_CPU_STATUS 0x4300
> diff --git a/arch/arm/mach-bcm283x/include/mach/acpi/bcm2712.h b/arch/arm/mach-bcm283x/include/mach/acpi/bcm2712.h
> new file mode 100644
> index 00000000000..ff675de1a07
> --- /dev/null
> +++ b/arch/arm/mach-bcm283x/include/mach/acpi/bcm2712.h
> @@ -0,0 +1,44 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* Copyright (C) 2026, Beniamin Sandu <beniaminsandu at gmail.com */
> +
> +#ifndef BCM2712_H__
> +#define BCM2712_H__
> +
> +/* PCIe registers */
> +#define PCIE_MISC_HARD_PCIE_HARD_DEBUG_BCM2712 0x4304
> +
> +#define PCIE_MISC_PCIE_CTRL 0x4064
> +#define PCIE_MISC_PCIE_CTRL_PERST_MASK 0x4
> +
> +#define PCIE_RC_PL_PHY_CTL_15 0x184c
> +#define PCIE_RC_PL_PHY_CTL_15_PM_CLK_PERIOD_MASK 0xff
> +
> +#define MISC_CTRL_PCIE_RCB_MPS_MODE_MASK 0x400
> +#define MISC_CTRL_PCIE_RCB_64B_MODE_MASK 0x80
> +#define MISC_CTRL_MAX_BURST_SIZE_512 (BURST_SIZE_512 << 20)
> +#define PCIE_MISC_RC_BAR_CONFIG_LO(idx) \
> + (PCIE_MISC_RC_BAR1_CONFIG_LO + 8 * ((idx) - 1))
> +#define PCIE_MISC_UBUS_BAR_CONFIG_REMAP(idx) (0x40ac + 8 * ((idx) - 1))
> +#define UBUS_BAR_CONFIG_REMAP_ACCESS_EN BIT(0)
> +
> +#define PCIE_MISC_RC_CONFIG_RETRY_TIMEOUT 0x405c
> +
> +#define PCIE_MISC_CTRL_1 0x40a0
> +#define PCIE_MISC_CTRL_1_EN_VDM_QOS_CONTROL_MASK BIT(5)
> +
> +#define PCIE_MISC_UBUS_CTRL 0x40a4
> +#define UBUS_CTRL_PCIE_REPLY_ERR_DIS_MASK BIT(13)
> +#define UBUS_CTRL_PCIE_REPLY_DECERR_DIS_MASK BIT(19)
> +
> +#define PCIE_MISC_UBUS_TIMEOUT 0x40a8
> +
> +#define PCIE_MISC_AXI_INTF_CTRL 0x416c
> +#define AXI_REQFIFO_EN_QOS_PROPAGATION BIT(7)
> +#define AXI_DIS_QOS_GATING_IN_MASTER BIT(11)
> +#define AXI_EN_QOS_UPDATE_TIMING_FIX BIT(12)
> +#define AXI_EN_RCLK_QOS_ARRAY_FIX BIT(13)
> +#define AXI_MASTER_MAX_OUTSTANDING_REQUESTS_MASK 0x3f
> +
> +#define PCIE_MISC_AXI_READ_ERROR_DATA 0x4170
> +
> +#endif /* BCM2712_H__ */
> diff --git a/arch/arm/mach-bcm283x/init.c b/arch/arm/mach-bcm283x/init.c
> index 7a1de22e0ae..726031793ae 100644
> --- a/arch/arm/mach-bcm283x/init.c
> +++ b/arch/arm/mach-bcm283x/init.c
> @@ -18,7 +18,7 @@
> #ifdef CONFIG_ARM64
> #include <asm/armv8/mmu.h>
>
> -#define MEM_MAP_MAX_ENTRIES (4)
> +#define MEM_MAP_MAX_ENTRIES (5)
>
> static struct mm_region bcm283x_mem_map[MEM_MAP_MAX_ENTRIES] = {
> {
> @@ -91,6 +91,17 @@ static struct mm_region bcm2712_mem_map[MEM_MAP_MAX_ENTRIES] = {
> .attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
> PTE_BLOCK_NON_SHARE |
> PTE_BLOCK_PXN | PTE_BLOCK_UXN
> + }, {
> + /*
> + * PCIe1 + PCIe2 outbound windows (covers both 32-bit and
> + * 64-bit ranges of both controllers from the bcm2712 dts).
> + */
> + .virt = 0x1800000000UL,
> + .phys = 0x1800000000UL,
> + .size = 0x0800000000UL,
> + .attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
> + PTE_BLOCK_NON_SHARE |
> + PTE_BLOCK_PXN | PTE_BLOCK_UXN
> }, {
> /* List terminator */
> 0,
> diff --git a/drivers/nvme/nvme.c b/drivers/nvme/nvme.c
> index 2b14437f69c..e64ccb9bad6 100644
> --- a/drivers/nvme/nvme.c
> +++ b/drivers/nvme/nvme.c
> @@ -12,6 +12,7 @@
> #include <log.h>
> #include <malloc.h>
> #include <memalign.h>
> +#include <pci.h>
> #include <time.h>
> #include <dm/device-internal.h>
> #include <linux/compat.h>
> @@ -27,6 +28,31 @@
> #define IO_TIMEOUT 30
> #define MAX_PRP_POOL 512
>
> +/*
> + * Translate a host address into the PCI bus address. On systems where the
> + * host bridge defines `dma-ranges` with a non-zero offset (e.g. the BCM2712 PCIe
> + * controller), the bus and CPU views of system memory differ. Passing raw CPU
> + * addresses to the controller results in master-aborts.
> + *
> + * For NVMe controllers attached to a non-PCI bus, `pci_get_controller()`
> + * returns the device itself, no dma-ranges are found, and we fall back
> + * to the identity translation.
> + */
> +static u64 nvme_phys_to_bus(struct udevice *udev, u64 phys)
> +{
> + struct udevice *ctlr = pci_get_controller(udev);
> + struct pci_region region;
> + int i;
> +
> + for (i = 0; pci_get_dma_regions(ctlr, ®ion, i) == 0; i++) {
> + if (phys >= region.phys_start &&
> + phys < region.phys_start + region.size)
> + return region.bus_start + (phys - region.phys_start);
> + }
> +
> + return phys;
> +}
> +
> static int nvme_wait_csts(struct nvme_dev *dev, u32 mask, u32 val)
> {
> int timeout;
> @@ -66,7 +92,7 @@ static int nvme_setup_prps(struct nvme_dev *dev, u64 *prp2,
> dma_addr += (page_size - offset);
>
> if (length <= page_size) {
> - *prp2 = dma_addr;
> + *prp2 = nvme_phys_to_bus(dev->udev, dma_addr);
> return 0;
> }
>
> @@ -91,16 +117,17 @@ static int nvme_setup_prps(struct nvme_dev *dev, u64 *prp2,
> i = 0;
> while (nprps) {
> if ((i == (prps_per_page - 1)) && nprps > 1) {
> - *(prp_pool + i) = cpu_to_le64((ulong)prp_pool +
> - page_size);
> + *(prp_pool + i) = cpu_to_le64(nvme_phys_to_bus(dev->udev,
> + (ulong)prp_pool + page_size));
> i = 0;
> prp_pool += page_size;
> }
> - *(prp_pool + i++) = cpu_to_le64(dma_addr);
> + *(prp_pool + i++) = cpu_to_le64(nvme_phys_to_bus(dev->udev,
> + dma_addr));
> dma_addr += page_size;
> nprps--;
> }
> - *prp2 = (ulong)dev->prp_pool;
> + *prp2 = nvme_phys_to_bus(dev->udev, (ulong)dev->prp_pool);
>
> flush_dcache_range((ulong)dev->prp_pool, (ulong)dev->prp_pool +
> num_pages * page_size);
> @@ -393,8 +420,10 @@ static int nvme_configure_admin_queue(struct nvme_dev *dev)
> dev->ctrl_config |= NVME_CC_IOSQES | NVME_CC_IOCQES;
>
> writel(aqa, &dev->bar->aqa);
> - nvme_writeq((ulong)nvmeq->sq_cmds, &dev->bar->asq);
> - nvme_writeq((ulong)nvmeq->cqes, &dev->bar->acq);
> + nvme_writeq(nvme_phys_to_bus(dev->udev, (ulong)nvmeq->sq_cmds),
> + &dev->bar->asq);
> + nvme_writeq(nvme_phys_to_bus(dev->udev, (ulong)nvmeq->cqes),
> + &dev->bar->acq);
>
> result = nvme_enable_ctrl(dev);
> if (result)
> @@ -420,7 +449,8 @@ static int nvme_alloc_cq(struct nvme_dev *dev, u16 qid,
>
> memset(&c, 0, sizeof(c));
> c.create_cq.opcode = nvme_admin_create_cq;
> - c.create_cq.prp1 = cpu_to_le64((ulong)nvmeq->cqes);
> + c.create_cq.prp1 = cpu_to_le64(nvme_phys_to_bus(dev->udev,
> + (ulong)nvmeq->cqes));
> c.create_cq.cqid = cpu_to_le16(qid);
> c.create_cq.qsize = cpu_to_le16(nvmeq->q_depth - 1);
> c.create_cq.cq_flags = cpu_to_le16(flags);
> @@ -437,7 +467,8 @@ static int nvme_alloc_sq(struct nvme_dev *dev, u16 qid,
>
> memset(&c, 0, sizeof(c));
> c.create_sq.opcode = nvme_admin_create_sq;
> - c.create_sq.prp1 = cpu_to_le64((ulong)nvmeq->sq_cmds);
> + c.create_sq.prp1 = cpu_to_le64(nvme_phys_to_bus(dev->udev,
> + (ulong)nvmeq->sq_cmds));
> c.create_sq.sqid = cpu_to_le16(qid);
> c.create_sq.qsize = cpu_to_le16(nvmeq->q_depth - 1);
> c.create_sq.sq_flags = cpu_to_le16(flags);
> @@ -453,19 +484,21 @@ int nvme_identify(struct nvme_dev *dev, unsigned nsid,
> u32 page_size = dev->page_size;
> int offset = dma_addr & (page_size - 1);
> int length = sizeof(struct nvme_id_ctrl);
> + u64 bus_addr = nvme_phys_to_bus(dev->udev, dma_addr);
> int ret;
>
> memset(&c, 0, sizeof(c));
> c.identify.opcode = nvme_admin_identify;
> c.identify.nsid = cpu_to_le32(nsid);
> - c.identify.prp1 = cpu_to_le64(dma_addr);
> + c.identify.prp1 = cpu_to_le64(bus_addr);
>
> length -= (page_size - offset);
> if (length <= 0) {
> c.identify.prp2 = 0;
> } else {
> dma_addr += (page_size - offset);
> - c.identify.prp2 = cpu_to_le64(dma_addr);
> + c.identify.prp2 =
> + cpu_to_le64(nvme_phys_to_bus(dev->udev, dma_addr));
> }
>
> c.identify.cns = cpu_to_le32(cns);
> @@ -490,7 +523,7 @@ int nvme_get_features(struct nvme_dev *dev, unsigned fid, unsigned nsid,
> memset(&c, 0, sizeof(c));
> c.features.opcode = nvme_admin_get_features;
> c.features.nsid = cpu_to_le32(nsid);
> - c.features.prp1 = cpu_to_le64(dma_addr);
> + c.features.prp1 = cpu_to_le64(nvme_phys_to_bus(dev->udev, dma_addr));
> c.features.fid = cpu_to_le32(fid);
>
> ret = nvme_submit_admin_cmd(dev, &c, result);
> @@ -516,7 +549,7 @@ int nvme_set_features(struct nvme_dev *dev, unsigned fid, unsigned dword11,
>
> memset(&c, 0, sizeof(c));
> c.features.opcode = nvme_admin_set_features;
> - c.features.prp1 = cpu_to_le64(dma_addr);
> + c.features.prp1 = cpu_to_le64(nvme_phys_to_bus(dev->udev, dma_addr));
> c.features.fid = cpu_to_le32(fid);
> c.features.dword11 = cpu_to_le32(dword11);
>
> @@ -785,7 +818,8 @@ static ulong nvme_blk_rw(struct udevice *udev, lbaint_t blknr,
> c.rw.slba = cpu_to_le64(slba);
> slba += lbas;
> c.rw.length = cpu_to_le16(lbas - 1);
> - c.rw.prp1 = cpu_to_le64(temp_buffer);
> + c.rw.prp1 = cpu_to_le64(nvme_phys_to_bus(dev->udev,
> + temp_buffer));
> c.rw.prp2 = cpu_to_le64(prp2);
> status = nvme_submit_sync_cmd(dev->queues[NVME_IO_Q],
> &c, NULL, IO_TIMEOUT);
> diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
> index 39df0e776df..b77b4302357 100644
> --- a/drivers/pci/Kconfig
> +++ b/drivers/pci/Kconfig
> @@ -425,8 +425,7 @@ config PCI_BRCMSTB
> help
> Say Y here if you want to enable support for PCIe controller
> on Broadcom set-top-box (STB) SoCs.
> - This driver currently supports only BCM2711 SoC and RC mode
> - of the controller.
> + This driver supports the BCM2711 and BCM2712 SoCs in RC mode.
>
> config PCIE_UNIPHIER
> bool "Socionext UniPhier PCIe driver"
> diff --git a/drivers/pci/pcie_brcmstb.c b/drivers/pci/pcie_brcmstb.c
> index f089c48f028..a243ff8f161 100644
> --- a/drivers/pci/pcie_brcmstb.c
> +++ b/drivers/pci/pcie_brcmstb.c
> @@ -13,17 +13,20 @@
> */
>
> #include <asm/arch/acpi/bcm2711.h>
> +#include <asm/arch/acpi/bcm2712.h>
> #include <errno.h>
> #include <dm.h>
> #include <dm/ofnode.h>
> #include <pci.h>
> #include <asm/io.h>
> #include <linux/bitfield.h>
> +#include <linux/bitops.h>
> #include <linux/log2.h>
> #include <linux/iopoll.h>
>
> /* PCIe parameters */
> #define BRCM_NUM_PCIE_OUT_WINS 4
> +#define BRCM_NUM_PCIE_IN_WINS 3
>
> /* MDIO registers */
> #define MDIO_PORT0 0x0
> @@ -49,18 +52,34 @@
> #define SSC_STATUS_PLL_LOCK_MASK 0x800
> #define SSC_STATUS_PLL_LOCK_SHIFT 11
>
> +enum brcm_pcie_type {
> + BRCM_PCIE_BCM2711,
> + BRCM_PCIE_BCM2712,
> +};
> +
> +/**
> + * struct brcm_pcie_cfg - per-SoC configuration data
> + * @type: SoC type, used to branch on quirks at runtime
> + * @hard_debug: Offset of the PCIE_MISC_HARD_PCIE_HARD_DEBUG register
> + */
> +struct brcm_pcie_cfg {
> + enum brcm_pcie_type type;
> + unsigned int hard_debug;
> +};
> +
> /**
> * struct brcm_pcie - the PCIe controller state
> * @base: Base address of memory mapped IO registers of the controller
> + * @cfg: Pointer to per-SoC configuration
> * @gen: Non-zero value indicates limitation of the PCIe controller operation
> * to a specific generation (1, 2 or 3)
> * @ssc: true indicates active Spread Spectrum Clocking operation
> */
> struct brcm_pcie {
> - void __iomem *base;
> -
> - int gen;
> - bool ssc;
> + void __iomem *base;
> + const struct brcm_pcie_cfg *cfg;
> + int gen;
> + bool ssc;
> };
>
> /**
> @@ -345,6 +364,156 @@ static void brcm_pcie_set_outbound_win(struct brcm_pcie *pcie,
> writel(tmp, base + PCIE_MEM_WIN0_LIMIT_HI(win));
> }
>
> +/*
> + * Assert (or de-assert) the PCIe fundamental reset (PERST#).
> + *
> + * On BCM2711 PERST# is bit 0 of PCIE_RGR1_SW_INIT_1; setting the bit asserts
> + * reset. On BCM2712 PERST# moved to PCIE_MISC_PCIE_CTRL with inverted
> + * polarity (the bit is high when reset is de-asserted).
> + */
> +static void brcm_pcie_perst_set(struct brcm_pcie *pcie, bool assert)
> +{
> + void __iomem *base = pcie->base;
> + u32 tmp;
> +
> + if (pcie->cfg->type == BRCM_PCIE_BCM2712) {
> + tmp = readl(base + PCIE_MISC_PCIE_CTRL);
> + if (assert)
> + tmp &= ~PCIE_MISC_PCIE_CTRL_PERST_MASK;
> + else
> + tmp |= PCIE_MISC_PCIE_CTRL_PERST_MASK;
> + writel(tmp, base + PCIE_MISC_PCIE_CTRL);
> + } else if (assert) {
> + setbits_le32(base + PCIE_RGR1_SW_INIT_1,
> + PCIE_RGR1_SW_INIT_1_PERST_MASK);
> + } else {
> + clrbits_le32(base + PCIE_RGR1_SW_INIT_1,
> + PCIE_RGR1_SW_INIT_1_PERST_MASK);
> + }
> +}
> +
> +/*
> + * Set up one inbound DMA window on BCM2712.
> + *
> + * The PCIe-side address goes into PCIE_MISC_RC_BAR{idx}_CONFIG_LO/HI; the
> + * matching CPU-side address goes into PCIE_MISC_UBUS_BAR{idx}_CONFIG_REMAP
> + * (low 12 bits are reserved for flags, hence the 4 KiB alignment).
> + */
> +static void brcm_pcie_set_inbound_win(struct brcm_pcie *pcie, int idx,
> + u64 cpu_addr, u64 pcie_addr, u64 size)
> +{
> + void __iomem *base = pcie->base;
> + u32 tmp;
> +
> + tmp = lower_32_bits(pcie_addr);
> + u32p_replace_bits(&tmp, brcm_pcie_encode_ibar_size(size),
> + RC_BAR2_CONFIG_LO_SIZE_MASK);
> + writel(tmp, base + PCIE_MISC_RC_BAR_CONFIG_LO(idx));
> + writel(upper_32_bits(pcie_addr),
> + base + PCIE_MISC_RC_BAR_CONFIG_LO(idx) + 4);
> +
> + tmp = lower_32_bits(cpu_addr) & ~0xfff;
> + tmp |= UBUS_BAR_CONFIG_REMAP_ACCESS_EN;
> + writel(tmp, base + PCIE_MISC_UBUS_BAR_CONFIG_REMAP(idx));
> + writel(upper_32_bits(cpu_addr),
> + base + PCIE_MISC_UBUS_BAR_CONFIG_REMAP(idx) + 4);
> +}
> +
> +/*
> + * BCM2712 post-setup: feed the PHY a 54 MHz refclk and program the
> + * L1SS PM clock period for that source.
> + *
> + * Must be called after the bridge is out of reset but before PERST# is
> + * de-asserted, otherwise the link will not train.
> + */
> +static int brcm_pcie_post_setup_bcm2712(struct brcm_pcie *pcie)
> +{
> + static const u16 mdio_data[] = { 0x50b9, 0xbda1, 0x0094, 0x97b4,
> + 0x5030, 0x5030, 0x0007 };
> + static const u8 mdio_regs[] = { 0x16, 0x17, 0x18, 0x19,
> + 0x1b, 0x1c, 0x1e };
> + void __iomem *base = pcie->base;
> + u32 tmp;
> + int i, ret;
> +
> + ret = brcm_pcie_mdio_write(base, MDIO_PORT0, SET_ADDR_OFFSET, 0x1600);
> + if (ret < 0)
> + return ret;
> +
> + for (i = 0; i < ARRAY_SIZE(mdio_regs); i++) {
> + ret = brcm_pcie_mdio_write(base, MDIO_PORT0,
> + mdio_regs[i], mdio_data[i]);
> + if (ret < 0)
> + return ret;
> + }
> +
> + udelay(200);
> +
> + /*
> + * Set L1SS sub-state timers to avoid lengthy state transitions,
> + * PM clock period is 18.52ns (1/54MHz, round down).
> + */
> + tmp = readl(base + PCIE_RC_PL_PHY_CTL_15);
> + tmp &= ~PCIE_RC_PL_PHY_CTL_15_PM_CLK_PERIOD_MASK;
> + tmp |= 0x12;
> + writel(tmp, base + PCIE_RC_PL_PHY_CTL_15);
> +
> + /*
> + * The UBUS-AXI bridge raises bus errors on PCIe accesses that
> + * miss (e.g. probing during enumeration). Suppress those and
> + * have failed reads return all-ones, so the PCI uclass sees a
> + * regular missing-device pattern instead of an aborted access.
> + */
> + tmp = readl(base + PCIE_MISC_UBUS_CTRL);
> + tmp |= UBUS_CTRL_PCIE_REPLY_ERR_DIS_MASK |
> + UBUS_CTRL_PCIE_REPLY_DECERR_DIS_MASK;
> + writel(tmp, base + PCIE_MISC_UBUS_CTRL);
> + writel(0xffffffff, base + PCIE_MISC_AXI_READ_ERROR_DATA);
> +
> + /*
> + * Many NVMe controllers reply with CRS for hundreds of ms after
> + * PERST# de-assertion while their firmware initialises. The
> + * default UBUS / RC retry timeouts terminate the request before
> + * CRS clears, so config reads come back as 0xffffffff. Bump
> + * both to ~250ms (units are 750MHz cycles).
> + */
> + writel(0xb2d0000, base + PCIE_MISC_UBUS_TIMEOUT);
> + writel(0xaba0000, base + PCIE_MISC_RC_CONFIG_RETRY_TIMEOUT);
> +
> + /*
> + * Production 2712D0 silicon needs these chicken bits set in the
> + * AXI interface control register; the QoS-propagation bit must
> + * also be cleared because the matching forwarding logic is
> + * broken in HW.
> + */
> + tmp = readl(base + PCIE_MISC_AXI_INTF_CTRL);
> + tmp &= ~AXI_REQFIFO_EN_QOS_PROPAGATION;
> + tmp |= AXI_EN_RCLK_QOS_ARRAY_FIX |
> + AXI_EN_QOS_UPDATE_TIMING_FIX |
> + AXI_DIS_QOS_GATING_IN_MASTER;
> + writel(tmp, base + PCIE_MISC_AXI_INTF_CTRL);
> +
> + /*
> + * Older 2712C1 silicon (and single-lane RC variants) leave
> + * AXI_EN_QOS_UPDATE_TIMING_FIX as reserved-0, in which case
> + * the chicken-bit fix above doesn't take. Fall back to
> + * throttling the number of in-flight AXI requests to SDRAM.
> + */
> + tmp = readl(base + PCIE_MISC_AXI_INTF_CTRL);
> + if (!(tmp & AXI_EN_QOS_UPDATE_TIMING_FIX)) {
> + tmp &= ~AXI_MASTER_MAX_OUTSTANDING_REQUESTS_MASK;
> + tmp |= 15;
> + writel(tmp, base + PCIE_MISC_AXI_INTF_CTRL);
> + }
> +
> + /* No traffic-class shaping in U-Boot; keep VDM reception off. */
> + tmp = readl(base + PCIE_MISC_CTRL_1);
> + tmp &= ~PCIE_MISC_CTRL_1_EN_VDM_QOS_CONTROL_MASK;
> + writel(tmp, base + PCIE_MISC_CTRL_1);
> +
> + return 0;
> +}
> +
> static int brcm_pcie_probe(struct udevice *dev)
> {
> struct udevice *ctlr = pci_get_controller(dev);
> @@ -354,19 +523,19 @@ static int brcm_pcie_probe(struct udevice *dev)
> struct pci_region region;
> bool ssc_good = false;
> int num_out_wins = 0;
> - u64 rc_bar2_offset, rc_bar2_size;
> - unsigned int scb_size_val;
> int i, ret;
> u16 nlw, cls, lnksta;
> u32 tmp;
>
> /*
> - * Reset the bridge, assert the fundamental reset. Note for some SoCs,
> - * e.g. BCM7278, the fundamental reset should not be asserted here.
> - * This will need to be changed when support for other SoCs is added.
> + * Reset the bridge and assert PERST#. For BCM2711 PERST# lives in
> + * RGR1_SW_INIT_1 alongside the bridge reset; for BCM2712 it lives
> + * in PCIE_MISC_PCIE_CTRL, so the two are written separately.
> */
> setbits_le32(base + PCIE_RGR1_SW_INIT_1,
> - PCIE_RGR1_SW_INIT_1_INIT_MASK | PCIE_RGR1_SW_INIT_1_PERST_MASK);
> + PCIE_RGR1_SW_INIT_1_INIT_MASK);
> + brcm_pcie_perst_set(pcie, true);
> +
> /*
> * The delay is a safety precaution to preclude the reset signal
> * from looking like a glitch.
> @@ -376,45 +545,80 @@ static int brcm_pcie_probe(struct udevice *dev)
> /* Take the bridge out of reset */
> clrbits_le32(base + PCIE_RGR1_SW_INIT_1, PCIE_RGR1_SW_INIT_1_INIT_MASK);
>
> - clrbits_le32(base + PCIE_MISC_HARD_PCIE_HARD_DEBUG,
> + clrbits_le32(base + pcie->cfg->hard_debug,
> PCIE_HARD_DEBUG_SERDES_IDDQ_MASK);
>
> /* Wait for SerDes to be stable */
> udelay(100);
>
> - /* Set SCB_MAX_BURST_SIZE, CFG_READ_UR_MODE, SCB_ACCESS_EN */
> - clrsetbits_le32(base + PCIE_MISC_MISC_CTRL,
> - MISC_CTRL_MAX_BURST_SIZE_MASK,
> - MISC_CTRL_SCB_ACCESS_EN_MASK |
> - MISC_CTRL_CFG_READ_UR_MODE_MASK |
> - MISC_CTRL_MAX_BURST_SIZE_128);
> -
> - pci_get_dma_regions(dev, ®ion, 0);
> - rc_bar2_offset = region.bus_start - region.phys_start;
> - rc_bar2_size = 1ULL << fls64(region.size - 1);
> -
> - tmp = lower_32_bits(rc_bar2_offset);
> - u32p_replace_bits(&tmp, brcm_pcie_encode_ibar_size(rc_bar2_size),
> - RC_BAR2_CONFIG_LO_SIZE_MASK);
> - writel(tmp, base + PCIE_MISC_RC_BAR2_CONFIG_LO);
> - writel(upper_32_bits(rc_bar2_offset),
> - base + PCIE_MISC_RC_BAR2_CONFIG_HI);
> -
> - scb_size_val = rc_bar2_size ?
> - ilog2(rc_bar2_size) - 15 : 0xf; /* 0xf is 1GB */
> -
> - tmp = readl(base + PCIE_MISC_MISC_CTRL);
> - u32p_replace_bits(&tmp, scb_size_val,
> - MISC_CTRL_SCB0_SIZE_MASK);
> - writel(tmp, base + PCIE_MISC_MISC_CTRL);
> -
> - /* Disable the PCIe->GISB memory window (RC_BAR1) */
> - clrbits_le32(base + PCIE_MISC_RC_BAR1_CONFIG_LO,
> - RC_BAR1_CONFIG_LO_SIZE_MASK);
> -
> - /* Disable the PCIe->SCB memory window (RC_BAR3) */
> - clrbits_le32(base + PCIE_MISC_RC_BAR3_CONFIG_LO,
> - RC_BAR3_CONFIG_LO_SIZE_MASK);
> + /*
> + * Set SCB_MAX_BURST_SIZE, CFG_READ_UR_MODE, SCB_ACCESS_EN. BCM2712
> + * needs a larger burst size (512 B) and the RCB MPS / 64-byte mode
> + * bits enabled to operate correctly.
> + */
> + if (pcie->cfg->type == BRCM_PCIE_BCM2712)
> + clrsetbits_le32(base + PCIE_MISC_MISC_CTRL,
> + MISC_CTRL_MAX_BURST_SIZE_MASK,
> + MISC_CTRL_SCB_ACCESS_EN_MASK |
> + MISC_CTRL_CFG_READ_UR_MODE_MASK |
> + MISC_CTRL_MAX_BURST_SIZE_512 |
> + MISC_CTRL_PCIE_RCB_MPS_MODE_MASK |
> + MISC_CTRL_PCIE_RCB_64B_MODE_MASK);
> + else
> + clrsetbits_le32(base + PCIE_MISC_MISC_CTRL,
> + MISC_CTRL_MAX_BURST_SIZE_MASK,
> + MISC_CTRL_SCB_ACCESS_EN_MASK |
> + MISC_CTRL_CFG_READ_UR_MODE_MASK |
> + MISC_CTRL_MAX_BURST_SIZE_128);
> +
> + if (pcie->cfg->type == BRCM_PCIE_BCM2712) {
> + /*
> + * BCM2712 maps each dma-range to its own RC inbound BAR
> + * starting at BAR1; iterate the device tree dma-ranges
> + * and program one BAR + UBUS remap per entry. The dts
> + * deliberately places inbound windows at PCIe addresses
> + * disjoint from the outbound windows, so an EP DMA never
> + * gets mis-claimed as a downstream access.
> + */
> + for (i = 0; i < BRCM_NUM_PCIE_IN_WINS; i++) {
> + if (pci_get_dma_regions(dev, ®ion, i))
> + break;
> + brcm_pcie_set_inbound_win(pcie, i + 1,
> + region.phys_start,
> + region.bus_start,
> + region.size);
> + }
> + } else {
> + u64 rc_bar2_offset, rc_bar2_size;
> + unsigned int scb_size_val;
> +
> + pci_get_dma_regions(dev, ®ion, 0);
> + rc_bar2_offset = region.bus_start - region.phys_start;
> + rc_bar2_size = 1ULL << fls64(region.size - 1);
> +
> + tmp = lower_32_bits(rc_bar2_offset);
> + u32p_replace_bits(&tmp, brcm_pcie_encode_ibar_size(rc_bar2_size),
> + RC_BAR2_CONFIG_LO_SIZE_MASK);
> + writel(tmp, base + PCIE_MISC_RC_BAR2_CONFIG_LO);
> + writel(upper_32_bits(rc_bar2_offset),
> + base + PCIE_MISC_RC_BAR2_CONFIG_HI);
> +
> + scb_size_val = rc_bar2_size ?
> + ilog2(rc_bar2_size) - 15 : 0xf; /* 0xf is 1GB */
> +
> + tmp = readl(base + PCIE_MISC_MISC_CTRL);
> + u32p_replace_bits(&tmp, scb_size_val,
> + MISC_CTRL_SCB0_SIZE_MASK);
> + writel(tmp, base + PCIE_MISC_MISC_CTRL);
> +
> + /* Disable the PCIe->GISB memory window (RC_BAR1) */
> + clrbits_le32(base + PCIE_MISC_RC_BAR1_CONFIG_LO,
> + RC_BAR1_CONFIG_LO_SIZE_MASK);
> +
> + /* Disable the PCIe->SCB memory window (RC_BAR3) */
> + clrbits_le32(base + PCIE_MISC_RC_BAR3_CONFIG_LO,
> + RC_BAR3_CONFIG_LO_SIZE_MASK);
> + }
>
> /* Mask all interrupts since we are not handling any yet */
> writel(0xffffffff, base + PCIE_MSI_INTR2_MASK_SET);
> @@ -425,9 +629,17 @@ static int brcm_pcie_probe(struct udevice *dev)
> if (pcie->gen)
> brcm_pcie_set_gen(pcie, pcie->gen);
>
> + /* BCM2712 needs PHY tuning before the link is brought up. */
> + if (pcie->cfg->type == BRCM_PCIE_BCM2712) {
> + ret = brcm_pcie_post_setup_bcm2712(pcie);
> + if (ret) {
> + printf("PCIe BRCM: PHY tuning failed (%d)\n", ret);
> + return ret;
> + }
> + }
> +
> /* Unassert the fundamental reset */
> - clrbits_le32(pcie->base + PCIE_RGR1_SW_INIT_1,
> - PCIE_RGR1_SW_INIT_1_PERST_MASK);
> + brcm_pcie_perst_set(pcie, false);
>
> /*
> * Wait for 100ms after PERST# deassertion; see PCIe CEM specification
> @@ -514,10 +726,10 @@ static int brcm_pcie_remove(struct udevice *dev)
> void __iomem *base = pcie->base;
>
> /* Assert fundamental reset */
> - setbits_le32(base + PCIE_RGR1_SW_INIT_1, PCIE_RGR1_SW_INIT_1_PERST_MASK);
> + brcm_pcie_perst_set(pcie, true);
>
> /* Turn off SerDes */
> - setbits_le32(base + PCIE_MISC_HARD_PCIE_HARD_DEBUG,
> + setbits_le32(base + pcie->cfg->hard_debug,
> PCIE_HARD_DEBUG_SERDES_IDDQ_MASK);
>
> /* Shutdown bridge */
> @@ -533,12 +745,23 @@ static int brcm_pcie_of_to_plat(struct udevice *dev)
> u32 max_link_speed;
> int ret;
>
> + pcie->cfg = (const struct brcm_pcie_cfg *)dev_get_driver_data(dev);
> + if (!pcie->cfg)
> + return -ENODEV;
> +
> /* Get the controller base address */
> pcie->base = dev_read_addr_ptr(dev);
> if (!pcie->base)
> return -EINVAL;
>
> - pcie->ssc = ofnode_read_bool(dn, "brcm,enable-ssc");
> + /*
> + * BCM2712's MDIO register map differs enough from BCM2711 that the
> + * SSC programming sequence in brcm_pcie_set_ssc() is invalid; the
> + * downstream Linux driver tags this with CFG_QUIRK_NO_SSC. Force SSC
> + * off here regardless of what the device tree requests.
> + */
> + pcie->ssc = pcie->cfg->type != BRCM_PCIE_BCM2712 &&
> + ofnode_read_bool(dn, "brcm,enable-ssc");
>
> ret = ofnode_read_u32(dn, "max-link-speed", &max_link_speed);
> if (ret < 0 || max_link_speed > 4)
> @@ -554,8 +777,19 @@ static const struct dm_pci_ops brcm_pcie_ops = {
> .write_config = brcm_pcie_write_config,
> };
>
> +static const struct brcm_pcie_cfg bcm2711_cfg = {
> + .type = BRCM_PCIE_BCM2711,
> + .hard_debug = PCIE_MISC_HARD_PCIE_HARD_DEBUG_BCM2711,
> +};
> +
> +static const struct brcm_pcie_cfg bcm2712_cfg = {
> + .type = BRCM_PCIE_BCM2712,
> + .hard_debug = PCIE_MISC_HARD_PCIE_HARD_DEBUG_BCM2712,
> +};
> +
> static const struct udevice_id brcm_pcie_ids[] = {
> - { .compatible = "brcm,bcm2711-pcie" },
> + { .compatible = "brcm,bcm2711-pcie", .data = (ulong)&bcm2711_cfg },
> + { .compatible = "brcm,bcm2712-pcie", .data = (ulong)&bcm2712_cfg },
> { }
> };
>
> --
> 2.48.1
>
More information about the U-Boot
mailing list