[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, &region, 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, &region, 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, &region, 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, &region, 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