Message ID | 20200504124523.23484-10-s.nawrocki@samsung.com |
---|---|
State | New |
Headers | show |
Series | USB host support for Raspberry Pi 4 board | expand |
On Mon, 2020-05-04 at 14:45 +0200, Sylwester Nawrocki wrote: > This patch adds basic driver for the Broadcom STB PCIe host controller. > The code is based on Linux upstream driver (pcie-brcmstb.c) with MSI > handling removed. The inbound access memory region is not currently > parsed from dma-ranges DT property and a fixed 4GB region is used. > > The patch has been tested on RPI4 board, i.e. on BCM2711 SoC with VL805 > USB Host Controller. > > Signed-off-by: Nicolas Saenz Julienne <nsaenzjulienne at suse.de> > Signed-off-by: Sylwester Nawrocki <s.nawrocki at samsung.com> I don't know if it's a little redundant already having a Signed-off-by tag, but if relevant you can add my: Reviewed-by: Nicolas Saenz Julienne <nsaenzjulienne at suse.de> Regards, Nicolas > --- > Changes since v1: > - fixed argument in brcm_pcie_set_ssc() function call > - changed rc_bar2_size assignment to value 0xC0000000, as in upstream > devicetre > Changes since RFC: > - reworked to align with current Linux mainline version and u-boot > driver by Nicolas Saenz Julienne > > brcmstb pcie > --- > drivers/pci/Kconfig | 6 + > drivers/pci/Makefile | 1 + > drivers/pci/pcie_brcmstb.c | 594 > +++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 601 insertions(+) > create mode 100644 drivers/pci/pcie_brcmstb.c > > diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig > index 437cd9a..056a021 100644 > --- a/drivers/pci/Kconfig > +++ b/drivers/pci/Kconfig > @@ -197,4 +197,10 @@ config PCIE_MEDIATEK > Say Y here if you want to enable Gen2 PCIe controller, > which could be found on MT7623 SoC family. > > +config PCI_BRCMSTB > + bool "Broadcom STB PCIe controller" > + depends on DM_PCI > + depends on ARCH_BCM283X > + help > + Say Y here if you want to enable Broadcom STB PCIe controller support. > endif > diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile > index c051ecc..3e53b1f 100644 > --- a/drivers/pci/Makefile > +++ b/drivers/pci/Makefile > @@ -43,3 +43,4 @@ obj-$(CONFIG_PCI_PHYTIUM) += pcie_phytium.o > obj-$(CONFIG_PCIE_INTEL_FPGA) += pcie_intel_fpga.o > obj-$(CONFIG_PCI_KEYSTONE) += pcie_dw_ti.o > obj-$(CONFIG_PCIE_MEDIATEK) += pcie_mediatek.o > +obj-$(CONFIG_PCI_BRCMSTB) += pcie_brcmstb.o > diff --git a/drivers/pci/pcie_brcmstb.c b/drivers/pci/pcie_brcmstb.c > new file mode 100644 > index 0000000..c6ddf92 > --- /dev/null > +++ b/drivers/pci/pcie_brcmstb.c > @@ -0,0 +1,594 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Broadcom STB PCIe controller driver > + * > + * Copyright (C) 2020 Samsung Electronics Co., Ltd. > + * > + * Based on upstream Linux kernel driver: > + * drivers/pci/controller/pcie-brcmstb.c > + * Copyright (C) 2009 - 2017 Broadcom > + * > + * Based driver by Nicolas Saenz Julienne > + * Copyright (C) 2020 Nicolas Saenz Julienne <nsaenzjulienne at suse.de> > + */ > + > +#include <asm/io.h> > +#include <common.h> > +#include <dm.h> > +#include <dm/ofnode.h> > +#include <errno.h> > +#include <linux/bitfield.h> > +#include <linux/log2.h> > +#include <pci.h> > + > +/* BRCM_PCIE_CAP_REGS - Offset for the mandatory capability config regs */ > +#define BRCM_PCIE_CAP_REGS 0x00ac > + > +/* Broadcom STB PCIe Register Offsets */ > +#define PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1 > 0x0188 > +#define PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1_ENDIAN_MODE_BAR2_MASK > 0xc > +#define PCIE_RC_CFG_VENDOR_SPCIFIC_REG1_LITTLE_ENDIAN > 0x0 > + > +#define PCIE_RC_CFG_PRIV1_ID_VAL3 0x043c > +#define PCIE_RC_CFG_PRIV1_ID_VAL3_CLASS_CODE_MASK 0xffffff > + > +#define PCIE_RC_DL_MDIO_ADDR 0x1100 > +#define PCIE_RC_DL_MDIO_WR_DATA 0x1104 > +#define PCIE_RC_DL_MDIO_RD_DATA 0x1108 > + > +#define PCIE_MISC_MISC_CTRL 0x4008 > +#define PCIE_MISC_MISC_CTRL_SCB_ACCESS_EN_MASK 0x1000 > +#define PCIE_MISC_MISC_CTRL_CFG_READ_UR_MODE_MASK 0x2000 > +#define PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_MASK 0x300000 > +#define PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_128 0x0 > +#define PCIE_MISC_MISC_CTRL_SCB0_SIZE_MASK 0xf8000000 > + > +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LO 0x400c > +#define PCIE_MEM_WIN0_LO(win) \ > + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LO + ((win) * 4) > + > +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_HI 0x4010 > +#define PCIE_MEM_WIN0_HI(win) \ > + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_HI + ((win) * 4) > + > +#define PCIE_MISC_RC_BAR1_CONFIG_LO 0x402c > +#define PCIE_MISC_RC_BAR1_CONFIG_LO_SIZE_MASK 0x1f > + > +#define PCIE_MISC_RC_BAR2_CONFIG_LO 0x4034 > +#define PCIE_MISC_RC_BAR2_CONFIG_LO_SIZE_MASK 0x1f > +#define PCIE_MISC_RC_BAR2_CONFIG_HI 0x4038 > + > +#define PCIE_MISC_RC_BAR3_CONFIG_LO 0x403c > +#define PCIE_MISC_RC_BAR3_CONFIG_LO_SIZE_MASK 0x1f > + > +#define PCIE_MISC_PCIE_STATUS 0x4068 > +#define PCIE_MISC_PCIE_STATUS_PCIE_PORT_MASK 0x80 > +#define PCIE_MISC_PCIE_STATUS_PCIE_DL_ACTIVE_MASK 0x20 > +#define PCIE_MISC_PCIE_STATUS_PCIE_PHYLINKUP_MASK 0x10 > +#define PCIE_MISC_PCIE_STATUS_PCIE_LINK_IN_L23_MASK 0x40 > + > +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT 0x4070 > +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT_LIMIT_MASK 0xfff00000 > +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT_BASE_MASK 0xfff0 > +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT_BASE_HI_SHIFT 12 > +#define PCIE_MEM_WIN0_BASE_LIMIT(win) \ > + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT + ((win) * 4) > + > +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_HI 0x4080 > +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_HI_BASE_MASK 0xff > +#define PCIE_MEM_WIN0_BASE_HI(win) \ > + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_HI + ((win) * 8) > + > +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LIMIT_HI 0x4084 > +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LIMIT_HI_LIMIT_MASK 0xff > +#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_CLKREQ_DEBUG_ENABLE_MASK 0x2 > +#define PCIE_MISC_HARD_PCIE_HARD_DEBUG_SERDES_IDDQ_MASK 0x08000000 > + > +#define PCIE_MSI_INTR2_CLR 0x4508 > +#define PCIE_MSI_INTR2_MASK_SET 0x4510 > + > +#define PCIE_EXT_CFG_DATA 0x8000 > + > +#define PCIE_EXT_CFG_INDEX 0x9000 > +#define PCIE_EXT_BUSNUM_SHIFT 20 > +#define PCIE_EXT_SLOT_SHIFT 15 > +#define PCIE_EXT_FUNC_SHIFT 12 > + > +#define PCIE_RGR1_SW_INIT_1 0x9210 > +#define PCIE_RGR1_SW_INIT_1_PERST_MASK 0x1 > +#define PCIE_RGR1_SW_INIT_1_INIT_MASK 0x2 > + > +/* PCIe parameters */ > +#define BRCM_NUM_PCIE_OUT_WINS 0x4 > + > +/* MDIO registers */ > +#define MDIO_PORT0 0x0 > +#define MDIO_DATA_MASK 0x7fffffff > +#define MDIO_PORT_MASK 0xf0000 > +#define MDIO_REGAD_MASK 0xffff > +#define MDIO_CMD_MASK 0xfff00000 > +#define MDIO_CMD_READ 0x1 > +#define MDIO_CMD_WRITE 0x0 > +#define MDIO_DATA_DONE_MASK 0x80000000 > +#define MDIO_RD_DONE(x) (((x) & MDIO_DATA_DONE_MASK) ? 1 > : 0) > +#define MDIO_WT_DONE(x) (((x) & MDIO_DATA_DONE_MASK) ? 0 > : 1) > +#define SSC_REGS_ADDR 0x1100 > +#define SET_ADDR_OFFSET 0x1f > +#define SSC_CNTL_OFFSET 0x2 > +#define SSC_CNTL_OVRD_EN_MASK 0x8000 > +#define SSC_CNTL_OVRD_VAL_MASK 0x4000 > +#define SSC_STATUS_OFFSET 0x1 > +#define SSC_STATUS_SSC_MASK 0x400 > +#define SSC_STATUS_PLL_LOCK_MASK 0x800 > + > +struct brcm_pcie { > + void __iomem *base; > + > + int gen; > + bool ssc; > +}; > + > +#define msleep(a) udelay((a) * 1000) > + > +/* > + * This is to convert the size of the inbound "BAR" region to the > + * non-linear values of PCIE_X_MISC_RC_BAR[123]_CONFIG_LO.SIZE > + */ > +static int brcm_pcie_encode_ibar_size(u64 size) > +{ > + int log2_in = ilog2(size); > + > + if (log2_in >= 12 && log2_in <= 15) > + /* Covers 4KB to 32KB (inclusive) */ > + return (log2_in - 12) + 0x1c; > + else if (log2_in >= 16 && log2_in <= 37) > + /* Covers 64KB to 32GB, (inclusive) */ > + return log2_in - 15; > + /* Something is awry so disable */ > + return 0; > +} > + > +/* Configuration space read/write support */ > +static inline int brcm_pcie_cfg_index(pci_dev_t bdf, int reg) > +{ > + return (PCI_DEV(bdf) << PCIE_EXT_SLOT_SHIFT) > + | (PCI_FUNC(bdf) << PCIE_EXT_FUNC_SHIFT) > + | (PCI_BUS(bdf) << PCIE_EXT_BUSNUM_SHIFT) > + | (reg & ~3); > +} > + > +/* The controller is capable of serving in both RC and EP roles */ > +static bool brcm_pcie_rc_mode(struct brcm_pcie *pcie) > +{ > + u32 val = readl(pcie->base + PCIE_MISC_PCIE_STATUS); > + > + return !!FIELD_GET(PCIE_MISC_PCIE_STATUS_PCIE_PORT_MASK, val); > +} > + > +static bool brcm_pcie_link_up(struct brcm_pcie *pcie) > +{ > + u32 val = readl(pcie->base + PCIE_MISC_PCIE_STATUS); > + u32 dla = FIELD_GET(PCIE_MISC_PCIE_STATUS_PCIE_DL_ACTIVE_MASK, val); > + u32 plu = FIELD_GET(PCIE_MISC_PCIE_STATUS_PCIE_PHYLINKUP_MASK, val); > + > + return dla && plu; > +} > + > +static int brcm_pcie_config_address(const struct udevice *udev, pci_dev_t > bdf, > + uint offset, void **paddress) > +{ > + struct brcm_pcie *pcie = dev_get_priv(udev); > + unsigned int bus = PCI_BUS(bdf); > + unsigned int dev = PCI_DEV(bdf); > + int idx; > + > + /* > + * Busses 0 (host PCIe bridge) and 1 (its immediate child) > + * are limited to a single device each > + */ > + if ((bus == (udev->seq + 1)) && dev > 0) > + return -ENODEV; > + > + /* Accesses to the RC go right to the RC registers if PCI device == 0 */ > + if (bus == udev->seq) { > + if (PCI_DEV(bdf)) > + return -ENODEV; > + > + *paddress = pcie->base + offset; > + return 0; > + } > + > + /* For devices, write to the config space index register */ > + idx = brcm_pcie_cfg_index(bdf, 0); > + > + writel(idx, pcie->base + PCIE_EXT_CFG_INDEX); > + *paddress = pcie->base + PCIE_EXT_CFG_DATA + offset; > + > + return 0; > +} > + > +static int brcm_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, brcm_pcie_config_address, > + bdf, offset, valuep, size); > +} > + > +static int brcm_pcie_write_config(struct udevice *bus, pci_dev_t bdf, > + uint offset, ulong value, > + enum pci_size_t size) > +{ > + return pci_generic_mmap_write_config(bus, brcm_pcie_config_address, > + bdf, offset, value, size); > +} > + > +static const char *link_speed_to_str(unsigned int s) > +{ > + static const char * const speed_str[] = { "??", "2.5", "5.0", "8.0" }; > + > + if (s >= ARRAY_SIZE(speed_str)) > + s = 0; > + > + return speed_str[s]; > +} > + > +static inline void brcm_pcie_bridge_sw_init_set(struct brcm_pcie *pcie, u32 > val) > +{ > + u32 tmp; > + > + tmp = readl(pcie->base + PCIE_RGR1_SW_INIT_1); > + u32p_replace_bits(&tmp, val, PCIE_RGR1_SW_INIT_1_INIT_MASK); > + writel(tmp, pcie->base + PCIE_RGR1_SW_INIT_1); > +} > + > +static inline void brcm_pcie_perst_set(struct brcm_pcie *pcie, u32 val) > +{ > + u32 tmp; > + > + tmp = readl(pcie->base + PCIE_RGR1_SW_INIT_1); > + u32p_replace_bits(&tmp, val, PCIE_RGR1_SW_INIT_1_PERST_MASK); > + writel(tmp, pcie->base + PCIE_RGR1_SW_INIT_1); > +} > + > +static u32 brcm_pcie_mdio_form_pkt(int port, int regad, int cmd) > +{ > + u32 pkt = 0; > + > + pkt |= FIELD_PREP(MDIO_PORT_MASK, port); > + pkt |= FIELD_PREP(MDIO_REGAD_MASK, regad); > + pkt |= FIELD_PREP(MDIO_CMD_MASK, cmd); > + > + return pkt; > +} > + > +/* Negative return value indicates error */ > +static int brcm_pcie_mdio_read(void __iomem *base, u8 port, u8 regad, u32 > *val) > +{ > + int tries; > + u32 data; > + > + writel(brcm_pcie_mdio_form_pkt(port, regad, MDIO_CMD_READ), > + base + PCIE_RC_DL_MDIO_ADDR); > + readl(base + PCIE_RC_DL_MDIO_ADDR); > + > + data = readl(base + PCIE_RC_DL_MDIO_RD_DATA); > + for (tries = 0; !MDIO_RD_DONE(data) && tries < 10; tries++) { > + udelay(10); > + data = readl(base + PCIE_RC_DL_MDIO_RD_DATA); > + } > + > + *val = FIELD_GET(MDIO_DATA_MASK, data); > + return MDIO_RD_DONE(data) ? 0 : -EIO; > +} > + > +/* Negative return value indicates error */ > +static int brcm_pcie_mdio_write(void __iomem *base, u8 port, > + u8 regad, u16 wrdata) > +{ > + int tries; > + u32 data; > + > + writel(brcm_pcie_mdio_form_pkt(port, regad, MDIO_CMD_WRITE), > + base + PCIE_RC_DL_MDIO_ADDR); > + readl(base + PCIE_RC_DL_MDIO_ADDR); > + writel(MDIO_DATA_DONE_MASK | wrdata, base + PCIE_RC_DL_MDIO_WR_DATA); > + > + data = readl(base + PCIE_RC_DL_MDIO_WR_DATA); > + for (tries = 0; !MDIO_WT_DONE(data) && tries < 10; tries++) { > + udelay(10); > + data = readl(base + PCIE_RC_DL_MDIO_WR_DATA); > + } > + > + return MDIO_WT_DONE(data) ? 0 : -EIO; > +} > + > +/* > + * Configures device for Spread Spectrum Clocking (SSC) mode; negative > + * return value indicates error. > + */ > +static int brcm_pcie_set_ssc(struct brcm_pcie *pcie) > +{ > + void __iomem *base = pcie->base; > + int pll, ssc; > + int ret; > + u32 tmp; > + > + ret = brcm_pcie_mdio_write(base, MDIO_PORT0, SET_ADDR_OFFSET, > + SSC_REGS_ADDR); > + if (ret < 0) > + return ret; > + > + ret = brcm_pcie_mdio_read(base, MDIO_PORT0, SSC_CNTL_OFFSET, &tmp); > + if (ret < 0) > + return ret; > + > + u32p_replace_bits(&tmp, 1, SSC_CNTL_OVRD_EN_MASK); > + u32p_replace_bits(&tmp, 1, SSC_CNTL_OVRD_VAL_MASK); > + ret = brcm_pcie_mdio_write(base, MDIO_PORT0, SSC_CNTL_OFFSET, tmp); > + if (ret < 0) > + return ret; > + > + udelay(1000); > + ret = brcm_pcie_mdio_read(base, MDIO_PORT0, SSC_STATUS_OFFSET, &tmp); > + if (ret < 0) > + return ret; > + > + ssc = FIELD_GET(SSC_STATUS_SSC_MASK, tmp); > + pll = FIELD_GET(SSC_STATUS_PLL_LOCK_MASK, tmp); > + > + return ssc && pll ? 0 : -EIO; > +} > + > +/* Limits operation to a specific generation (1, 2, or 3) */ > +static void brcm_pcie_set_gen(struct brcm_pcie *pcie, int gen) > +{ > + void __iomem *base = pcie->base; > + > + u16 lnkctl2 = readw(base + BRCM_PCIE_CAP_REGS + PCI_EXP_LNKCTL2); > + u32 lnkcap = readl(base + BRCM_PCIE_CAP_REGS + PCI_EXP_LNKCAP); > + > + lnkcap = (lnkcap & ~PCI_EXP_LNKCAP_SLS) | gen; > + writel(lnkcap, base + BRCM_PCIE_CAP_REGS + PCI_EXP_LNKCAP); > + > + lnkctl2 = (lnkctl2 & ~0xf) | gen; > + writew(lnkctl2, base + BRCM_PCIE_CAP_REGS + PCI_EXP_LNKCTL2); > +} > + > +static void brcm_pcie_set_outbound_win(struct brcm_pcie *pcie, > + unsigned int win, u64 phys_addr, > + u64 pcie_addr, u64 size) > +{ > + void __iomem *base = pcie->base; > + u32 phys_addr_mb_high, limit_addr_mb_high; > + phys_addr_t phys_addr_mb, limit_addr_mb; > + int high_addr_shift; > + u32 tmp; > + > + /* Set the base of the pcie_addr window */ > + writel(lower_32_bits(pcie_addr), base + PCIE_MEM_WIN0_LO(win)); > + writel(upper_32_bits(pcie_addr), base + PCIE_MEM_WIN0_HI(win)); > + > + /* Write the addr base & limit lower bits (in MBs) */ > + phys_addr_mb = phys_addr / SZ_1M; > + limit_addr_mb = (phys_addr + size - 1) / SZ_1M; > + > + tmp = readl(base + PCIE_MEM_WIN0_BASE_LIMIT(win)); > + u32p_replace_bits(&tmp, phys_addr_mb, > + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT_BASE_MASK); > + u32p_replace_bits(&tmp, limit_addr_mb, > + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT_LIMIT_MASK); > + writel(tmp, base + PCIE_MEM_WIN0_BASE_LIMIT(win)); > + > + /* Write the cpu & limit addr upper bits */ > + high_addr_shift = > PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT_BASE_HI_SHIFT; > + phys_addr_mb_high = phys_addr_mb >> high_addr_shift; > + tmp = readl(base + PCIE_MEM_WIN0_BASE_HI(win)); > + u32p_replace_bits(&tmp, phys_addr_mb_high, > + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_HI_BASE_MASK); > + writel(tmp, base + PCIE_MEM_WIN0_BASE_HI(win)); > + > + limit_addr_mb_high = limit_addr_mb >> high_addr_shift; > + tmp = readl(base + PCIE_MEM_WIN0_LIMIT_HI(win)); > + u32p_replace_bits(&tmp, limit_addr_mb_high, > + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LIMIT_HI_LIMIT_MASK); > + writel(tmp, base + PCIE_MEM_WIN0_LIMIT_HI(win)); > +} > + > +static int brcm_pcie_probe(struct udevice *dev) > +{ > + struct udevice *ctlr = pci_get_controller(dev); > + struct pci_controller *hose = dev_get_uclass_priv(ctlr); > + struct brcm_pcie *pcie = dev_get_priv(dev); > + void __iomem *base = pcie->base; > + 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 */ > + brcm_pcie_bridge_sw_init_set(pcie, 1); > + > + udelay(150); > + > + /* Take the bridge out of reset */ > + brcm_pcie_bridge_sw_init_set(pcie, 0); > + > + tmp = readl(base + PCIE_MISC_HARD_PCIE_HARD_DEBUG); > + tmp &= ~PCIE_MISC_HARD_PCIE_HARD_DEBUG_SERDES_IDDQ_MASK; > + writel(tmp, base + PCIE_MISC_HARD_PCIE_HARD_DEBUG); > + /* Wait for SerDes to be stable */ > + udelay(150); > + > + /* Set SCB_MAX_BURST_SIZE, CFG_READ_UR_MODE, SCB_ACCESS_EN */ > + u32p_replace_bits(&tmp, 1, PCIE_MISC_MISC_CTRL_SCB_ACCESS_EN_MASK); > + u32p_replace_bits(&tmp, 1, PCIE_MISC_MISC_CTRL_CFG_READ_UR_MODE_MASK); > + u32p_replace_bits(&tmp, PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_128, > + PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_MASK); > + writel(tmp, base + PCIE_MISC_MISC_CTRL); > + > + /* > + * TODO: When support for other SoCs than BCM2711 is added we may > + * need to use the base address and size(s) provided in the dma-ranges > + * property. > + */ > + rc_bar2_offset = 0; > + rc_bar2_size = 0xc0000000; > + > + tmp = lower_32_bits(rc_bar2_offset); > + u32p_replace_bits(&tmp, brcm_pcie_encode_ibar_size(rc_bar2_size), > + PCIE_MISC_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, > + PCIE_MISC_MISC_CTRL_SCB0_SIZE_MASK); > + writel(tmp, base + PCIE_MISC_MISC_CTRL); > + > + /* Disable the PCIe->GISB memory window (RC_BAR1) */ > + tmp = readl(base + PCIE_MISC_RC_BAR1_CONFIG_LO); > + tmp &= ~PCIE_MISC_RC_BAR1_CONFIG_LO_SIZE_MASK; > + writel(tmp, base + PCIE_MISC_RC_BAR1_CONFIG_LO); > + > + /* Disable the PCIe->SCB memory window (RC_BAR3) */ > + tmp = readl(base + PCIE_MISC_RC_BAR3_CONFIG_LO); > + tmp &= ~PCIE_MISC_RC_BAR3_CONFIG_LO_SIZE_MASK; > + writel(tmp, base + PCIE_MISC_RC_BAR3_CONFIG_LO); > + > + /* Mask all interrupts since we are not handling any yet */ > + writel(0xffffffff, base + PCIE_MSI_INTR2_MASK_SET); > + > + /* Clear any interrupts we find on boot */ > + writel(0xffffffff, base + PCIE_MSI_INTR2_CLR); > + > + if (pcie->gen) > + brcm_pcie_set_gen(pcie, pcie->gen); > + > + /* Unassert the fundamental reset */ > + brcm_pcie_perst_set(pcie, 0); > + > + /* Give the RC/EP time to wake up, before trying to configure RC. > + * Intermittently check status for link-up, up to a total of 100ms. > + */ > + for (i = 0; i < 100 && !brcm_pcie_link_up(pcie); i += 5) > + msleep(5); > + > + if (!brcm_pcie_link_up(pcie)) { > + printf("PCIe BRCM: link down\n"); > + return -ENODEV; > + } > + > + if (!brcm_pcie_rc_mode(pcie)) { > + printf("PCIe misconfigured; is in EP mode\n"); > + return -EINVAL; > + } > + > + for (i = 0; i < hose->region_count; i++) { > + struct pci_region *reg = &hose->regions[i]; > + > + if (reg->flags != PCI_REGION_MEM) > + continue; > + > + if (num_out_wins >= BRCM_NUM_PCIE_OUT_WINS) > + return -EINVAL; > + > + brcm_pcie_set_outbound_win(pcie, num_out_wins, reg->phys_start, > + reg->bus_start, reg->size); > + > + num_out_wins++; > + } > + > + /* > + * For config space accesses on the RC, show the right class for > + * a PCIe-PCIe bridge (the default setting is to be EP mode). > + */ > + tmp = readl(base + PCIE_RC_CFG_PRIV1_ID_VAL3); > + u32p_replace_bits(&tmp, 0x060400, > + PCIE_RC_CFG_PRIV1_ID_VAL3_CLASS_CODE_MASK); > + writel(tmp, base + PCIE_RC_CFG_PRIV1_ID_VAL3); > + > + if (pcie->ssc) { > + ret = brcm_pcie_set_ssc(pcie); > + if (ret == 0) > + ssc_good = true; > + else > + printf("PCIe BRCM: failed attempt to enter SSC mode\n"); > + } > + > + lnksta = readw(base + BRCM_PCIE_CAP_REGS + PCI_EXP_LNKSTA); > + cls = FIELD_GET(PCI_EXP_LNKSTA_CLS, lnksta); > + nlw = FIELD_GET(PCI_EXP_LNKSTA_NLW, lnksta); > + > + printf("PCIe BRCM: link up, %s Gbps x%u %s\n", link_speed_to_str(cls), > + nlw, ssc_good ? "(SSC)" : "(!SSC)"); > + > + /* PCIe->SCB endian mode for BAR */ > + tmp = readl(base + PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1); > + u32p_replace_bits(&tmp, PCIE_RC_CFG_VENDOR_SPCIFIC_REG1_LITTLE_ENDIAN, > + PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1_ENDIAN_MODE_BAR2_MASK); > + writel(tmp, base + PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1); > + > + /* > + * Refclk from RC should be gated with CLKREQ# input when ASPM L0s,L1 > + * is enabled => setting the CLKREQ_DEBUG_ENABLE field to 1. > + */ > + tmp = readl(base + PCIE_MISC_HARD_PCIE_HARD_DEBUG); > + tmp |= PCIE_MISC_HARD_PCIE_HARD_DEBUG_CLKREQ_DEBUG_ENABLE_MASK; > + writel(tmp, base + PCIE_MISC_HARD_PCIE_HARD_DEBUG); > + > + return 0; > +} > + > +static int brcm_pcie_ofdata_to_platdata(struct udevice *dev) > +{ > + struct brcm_pcie *pcie = dev_get_priv(dev); > + ofnode dn = dev_ofnode(dev); > + u32 max_link_speed; > + int ret; > + > + /* 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"); > + > + ret = ofnode_read_u32(dn, "max-link-speed", &max_link_speed); > + if (ret < 0 || max_link_speed > 4) > + pcie->gen = 0; > + else > + pcie->gen = max_link_speed; > + > + return 0; > +} > + > +static const struct dm_pci_ops brcm_pcie_ops = { > + .read_config = brcm_pcie_read_config, > + .write_config = brcm_pcie_write_config, > +}; > + > +static const struct udevice_id brcm_pcie_ids[] = { > + { .compatible = "brcm,bcm2711-pcie" }, > + { } > +}; > + > +U_BOOT_DRIVER(pcie_brcm_base) = { > + .name = "pcie_brcm", > + .id = UCLASS_PCI, > + .ops = &brcm_pcie_ops, > + .of_match = brcm_pcie_ids, > + .probe = brcm_pcie_probe, > + .ofdata_to_platdata = brcm_pcie_ofdata_to_platdata, > + .priv_auto_alloc_size = sizeof(struct brcm_pcie), > +}; -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 488 bytes Desc: This is a digitally signed message part URL: <https://lists.denx.de/pipermail/u-boot/attachments/20200505/8f6ea8f3/attachment.sig>
Hi All. On Mon, 2020-05-04 at 14:45 +0200, Sylwester Nawrocki wrote: > This patch adds basic driver for the Broadcom STB PCIe host controller. > The code is based on Linux upstream driver (pcie-brcmstb.c) with MSI > handling removed. The inbound access memory region is not currently > parsed from dma-ranges DT property and a fixed 4GB region is used. > > The patch has been tested on RPI4 board, i.e. on BCM2711 SoC with VL805 > USB Host Controller. > > Signed-off-by: Nicolas Saenz Julienne <nsaenzjulienne at suse.de> > Signed-off-by: Sylwester Nawrocki <s.nawrocki at samsung.com> > --- If it's OK with you, as I'm already familiar with the driver from maintaining it on the Linux kernel, I wouldn't mind doing it here too. Sylwester, if you're also interested I can send a patch adding both of us. Regards, Nicolas -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 488 bytes Desc: This is a digitally signed message part URL: <https://lists.denx.de/pipermail/u-boot/attachments/20200505/a7ccd2ce/attachment.sig>
On 05.05.2020 16:13, Nicolas Saenz Julienne wrote: > On Mon, 2020-05-04 at 14:45 +0200, Sylwester Nawrocki wrote: >> This patch adds basic driver for the Broadcom STB PCIe host controller. >> The code is based on Linux upstream driver (pcie-brcmstb.c) with MSI >> handling removed. The inbound access memory region is not currently >> parsed from dma-ranges DT property and a fixed 4GB region is used. ^^^^^^ (I will change that to 3GB in next version) >> The patch has been tested on RPI4 board, i.e. on BCM2711 SoC with VL805 >> USB Host Controller. >> >> Signed-off-by: Nicolas Saenz Julienne <nsaenzjulienne at suse.de> >> Signed-off-by: Sylwester Nawrocki <s.nawrocki at samsung.com> > I don't know if it's a little redundant already having a Signed-off-by tag, > but if relevant you can add my: > > Reviewed-by: Nicolas Saenz Julienne <nsaenzjulienne at suse.de> Thanks for your review, I think it doesn't hurt to add the tag, it would indicate your review of final version.
Hi Nicolas, On 05.05.2020 17:45, Nicolas Saenz Julienne wrote: > If it's OK with you, as I'm already familiar with the driver from maintaining > it on the Linux kernel, I wouldn't mind doing it here too. > > Sylwester, if you're also interested I can send a patch adding both of us. Of course, I'm fine with having you as the maintainer of the driver. You can add me as well, I'm just not sure if a formal separate MAINTAINERS entry is needed. It looks like it would be first one with two persons for one driver.
Hi Sylwester, On Mon, 4 May 2020 at 06:45, Sylwester Nawrocki <s.nawrocki at samsung.com> wrote: > > This patch adds basic driver for the Broadcom STB PCIe host controller. > The code is based on Linux upstream driver (pcie-brcmstb.c) with MSI > handling removed. The inbound access memory region is not currently > parsed from dma-ranges DT property and a fixed 4GB region is used. > > The patch has been tested on RPI4 board, i.e. on BCM2711 SoC with VL805 > USB Host Controller. > > Signed-off-by: Nicolas Saenz Julienne <nsaenzjulienne at suse.de> > Signed-off-by: Sylwester Nawrocki <s.nawrocki at samsung.com> > --- > Changes since v1: > - fixed argument in brcm_pcie_set_ssc() function call > - changed rc_bar2_size assignment to value 0xC0000000, as in upstream > devicetre > Changes since RFC: > - reworked to align with current Linux mainline version and u-boot > driver by Nicolas Saenz Julienne > > brcmstb pcie > --- > drivers/pci/Kconfig | 6 + > drivers/pci/Makefile | 1 + > drivers/pci/pcie_brcmstb.c | 594 +++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 601 insertions(+) > create mode 100644 drivers/pci/pcie_brcmstb.c A few small comments. > > diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig > index 437cd9a..056a021 100644 > --- a/drivers/pci/Kconfig > +++ b/drivers/pci/Kconfig > @@ -197,4 +197,10 @@ config PCIE_MEDIATEK > Say Y here if you want to enable Gen2 PCIe controller, > which could be found on MT7623 SoC family. > > +config PCI_BRCMSTB > + bool "Broadcom STB PCIe controller" > + depends on DM_PCI > + depends on ARCH_BCM283X > + help > + Say Y here if you want to enable Broadcom STB PCIe controller support. What is STB? What features does this support? You should get a warning here to write at least three lines. > endif > diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile > index c051ecc..3e53b1f 100644 > --- a/drivers/pci/Makefile > +++ b/drivers/pci/Makefile > @@ -43,3 +43,4 @@ obj-$(CONFIG_PCI_PHYTIUM) += pcie_phytium.o > obj-$(CONFIG_PCIE_INTEL_FPGA) += pcie_intel_fpga.o > obj-$(CONFIG_PCI_KEYSTONE) += pcie_dw_ti.o > obj-$(CONFIG_PCIE_MEDIATEK) += pcie_mediatek.o > +obj-$(CONFIG_PCI_BRCMSTB) += pcie_brcmstb.o > diff --git a/drivers/pci/pcie_brcmstb.c b/drivers/pci/pcie_brcmstb.c > new file mode 100644 > index 0000000..c6ddf92 > --- /dev/null > +++ b/drivers/pci/pcie_brcmstb.c > @@ -0,0 +1,594 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Broadcom STB PCIe controller driver > + * > + * Copyright (C) 2020 Samsung Electronics Co., Ltd. > + * > + * Based on upstream Linux kernel driver: > + * drivers/pci/controller/pcie-brcmstb.c > + * Copyright (C) 2009 - 2017 Broadcom > + * > + * Based driver by Nicolas Saenz Julienne > + * Copyright (C) 2020 Nicolas Saenz Julienne <nsaenzjulienne at suse.de> > + */ > + > +#include <asm/io.h> > +#include <common.h> > +#include <dm.h> > +#include <dm/ofnode.h> > +#include <errno.h> > +#include <linux/bitfield.h> > +#include <linux/log2.h> > +#include <pci.h> Check ordering of include files: https://www.denx.de/wiki/U-Boot/CodingStyle > + > +/* BRCM_PCIE_CAP_REGS - Offset for the mandatory capability config regs */ > +#define BRCM_PCIE_CAP_REGS 0x00ac > + > +/* Broadcom STB PCIe Register Offsets */ > +#define PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1 0x0188 > +#define PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1_ENDIAN_MODE_BAR2_MASK 0xc > +#define PCIE_RC_CFG_VENDOR_SPCIFIC_REG1_LITTLE_ENDIAN 0x0 > + > +#define PCIE_RC_CFG_PRIV1_ID_VAL3 0x043c > +#define PCIE_RC_CFG_PRIV1_ID_VAL3_CLASS_CODE_MASK 0xffffff > + > +#define PCIE_RC_DL_MDIO_ADDR 0x1100 > +#define PCIE_RC_DL_MDIO_WR_DATA 0x1104 > +#define PCIE_RC_DL_MDIO_RD_DATA 0x1108 > + > +#define PCIE_MISC_MISC_CTRL 0x4008 > +#define PCIE_MISC_MISC_CTRL_SCB_ACCESS_EN_MASK 0x1000 > +#define PCIE_MISC_MISC_CTRL_CFG_READ_UR_MODE_MASK 0x2000 > +#define PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_MASK 0x300000 > +#define PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_128 0x0 > +#define PCIE_MISC_MISC_CTRL_SCB0_SIZE_MASK 0xf8000000 If you have a _MASK, don't you need a _SHIFT to allow you to read from the field? Can you drop the PCIE_MISC prefix? These are very long and local to this file. > + > +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LO 0x400c > +#define PCIE_MEM_WIN0_LO(win) \ > + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LO + ((win) * 4) > + > +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_HI 0x4010 > +#define PCIE_MEM_WIN0_HI(win) \ > + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_HI + ((win) * 4) > + > +#define PCIE_MISC_RC_BAR1_CONFIG_LO 0x402c > +#define PCIE_MISC_RC_BAR1_CONFIG_LO_SIZE_MASK 0x1f > + > +#define PCIE_MISC_RC_BAR2_CONFIG_LO 0x4034 > +#define PCIE_MISC_RC_BAR2_CONFIG_LO_SIZE_MASK 0x1f > +#define PCIE_MISC_RC_BAR2_CONFIG_HI 0x4038 > + > +#define PCIE_MISC_RC_BAR3_CONFIG_LO 0x403c > +#define PCIE_MISC_RC_BAR3_CONFIG_LO_SIZE_MASK 0x1f > + > +#define PCIE_MISC_PCIE_STATUS 0x4068 > +#define PCIE_MISC_PCIE_STATUS_PCIE_PORT_MASK 0x80 > +#define PCIE_MISC_PCIE_STATUS_PCIE_DL_ACTIVE_MASK 0x20 > +#define PCIE_MISC_PCIE_STATUS_PCIE_PHYLINKUP_MASK 0x10 > +#define PCIE_MISC_PCIE_STATUS_PCIE_LINK_IN_L23_MASK 0x40 > + > +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT 0x4070 > +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT_LIMIT_MASK 0xfff00000 > +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT_BASE_MASK 0xfff0 > +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT_BASE_HI_SHIFT 12 > +#define PCIE_MEM_WIN0_BASE_LIMIT(win) \ > + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT + ((win) * 4) > + > +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_HI 0x4080 > +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_HI_BASE_MASK 0xff > +#define PCIE_MEM_WIN0_BASE_HI(win) \ > + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_HI + ((win) * 8) > + > +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LIMIT_HI 0x4084 > +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LIMIT_HI_LIMIT_MASK 0xff > +#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_CLKREQ_DEBUG_ENABLE_MASK 0x2 > +#define PCIE_MISC_HARD_PCIE_HARD_DEBUG_SERDES_IDDQ_MASK 0x08000000 > + > +#define PCIE_MSI_INTR2_CLR 0x4508 > +#define PCIE_MSI_INTR2_MASK_SET 0x4510 > + > +#define PCIE_EXT_CFG_DATA 0x8000 > + > +#define PCIE_EXT_CFG_INDEX 0x9000 > +#define PCIE_EXT_BUSNUM_SHIFT 20 > +#define PCIE_EXT_SLOT_SHIFT 15 > +#define PCIE_EXT_FUNC_SHIFT 12 > + > +#define PCIE_RGR1_SW_INIT_1 0x9210 > +#define PCIE_RGR1_SW_INIT_1_PERST_MASK 0x1 > +#define PCIE_RGR1_SW_INIT_1_INIT_MASK 0x2 > + > +/* PCIe parameters */ > +#define BRCM_NUM_PCIE_OUT_WINS 0x4 > + > +/* MDIO registers */ > +#define MDIO_PORT0 0x0 > +#define MDIO_DATA_MASK 0x7fffffff > +#define MDIO_PORT_MASK 0xf0000 > +#define MDIO_REGAD_MASK 0xffff > +#define MDIO_CMD_MASK 0xfff00000 > +#define MDIO_CMD_READ 0x1 > +#define MDIO_CMD_WRITE 0x0 > +#define MDIO_DATA_DONE_MASK 0x80000000 > +#define MDIO_RD_DONE(x) (((x) & MDIO_DATA_DONE_MASK) ? 1 : 0) > +#define MDIO_WT_DONE(x) (((x) & MDIO_DATA_DONE_MASK) ? 0 : 1) Are these two worth it? You can do this in your code: if (readl(xxx) & MDIO_DATA_DONE_MASK) If you must use these, then I think true/false are better than 1/0. > +#define SSC_REGS_ADDR 0x1100 > +#define SET_ADDR_OFFSET 0x1f > +#define SSC_CNTL_OFFSET 0x2 > +#define SSC_CNTL_OVRD_EN_MASK 0x8000 > +#define SSC_CNTL_OVRD_VAL_MASK 0x4000 > +#define SSC_STATUS_OFFSET 0x1 > +#define SSC_STATUS_SSC_MASK 0x400 > +#define SSC_STATUS_PLL_LOCK_MASK 0x800 > + > +struct brcm_pcie { struct comment > + void __iomem *base; > + > + int gen; > + bool ssc; > +}; > + > +#define msleep(a) udelay((a) * 1000) This is already defined in U-Boot. > + > +/* > + * This is to convert the size of the inbound "BAR" region to the > + * non-linear values of PCIE_X_MISC_RC_BAR[123]_CONFIG_LO.SIZE Please use a proper function comments with args and return value. > + */ > +static int brcm_pcie_encode_ibar_size(u64 size) > +{ > + int log2_in = ilog2(size); > + > + if (log2_in >= 12 && log2_in <= 15) > + /* Covers 4KB to 32KB (inclusive) */ > + return (log2_in - 12) + 0x1c; > + else if (log2_in >= 16 && log2_in <= 37) > + /* Covers 64KB to 32GB, (inclusive) */ > + return log2_in - 15; > + /* Something is awry so disable */ blank line always before return at end of function > + return 0; > +} > + > +/* Configuration space read/write support */ > +static inline int brcm_pcie_cfg_index(pci_dev_t bdf, int reg) > +{ > + return (PCI_DEV(bdf) << PCIE_EXT_SLOT_SHIFT) > + | (PCI_FUNC(bdf) << PCIE_EXT_FUNC_SHIFT) > + | (PCI_BUS(bdf) << PCIE_EXT_BUSNUM_SHIFT) > + | (reg & ~3); > +} > + > +/* The controller is capable of serving in both RC and EP roles */ > +static bool brcm_pcie_rc_mode(struct brcm_pcie *pcie) > +{ > + u32 val = readl(pcie->base + PCIE_MISC_PCIE_STATUS); Consider using a struct for your registers which would reduce the size of this code. > + > + return !!FIELD_GET(PCIE_MISC_PCIE_STATUS_PCIE_PORT_MASK, val); What is FIELD_GET? We have this sort of thing rejected in the past. Here, since you return a bool, the !! should not be necessary. > +} > + > +static bool brcm_pcie_link_up(struct brcm_pcie *pcie) > +{ > + u32 val = readl(pcie->base + PCIE_MISC_PCIE_STATUS); > + u32 dla = FIELD_GET(PCIE_MISC_PCIE_STATUS_PCIE_DL_ACTIVE_MASK, val); > + u32 plu = FIELD_GET(PCIE_MISC_PCIE_STATUS_PCIE_PHYLINKUP_MASK, val); PCIE_MISC_PCIE_STATUS_PCIE_PHYLINKUP_MASK is way too long. How about STATUS_PCIE_PHYLINKUP_MASK? > + > + return dla && plu; > +} > + > +static int brcm_pcie_config_address(const struct udevice *udev, pci_dev_t bdf, Please use 'dev' instead of udev for the device. > + uint offset, void **paddress) > +{ > + struct brcm_pcie *pcie = dev_get_priv(udev); > + unsigned int bus = PCI_BUS(bdf); > + unsigned int dev = PCI_DEV(bdf); > + int idx; > + > + /* > + * Busses 0 (host PCIe bridge) and 1 (its immediate child) > + * are limited to a single device each > + */ > + if ((bus == (udev->seq + 1)) && dev > 0) > + return -ENODEV; > + > + /* Accesses to the RC go right to the RC registers if PCI device == 0 */ > + if (bus == udev->seq) { > + if (PCI_DEV(bdf)) > + return -ENODEV; You can't return that as there is a device. Perhaps ENXIO? Does this indicate an internal error? If so you could add a comment here as to how to fix it. > + > + *paddress = pcie->base + offset; > + return 0; > + } > + > + /* For devices, write to the config space index register */ > + idx = brcm_pcie_cfg_index(bdf, 0); > + > + writel(idx, pcie->base + PCIE_EXT_CFG_INDEX); > + *paddress = pcie->base + PCIE_EXT_CFG_DATA + offset; > + > + return 0; > +} > + > +static int brcm_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, brcm_pcie_config_address, > + bdf, offset, valuep, size); > +} > + > +static int brcm_pcie_write_config(struct udevice *bus, pci_dev_t bdf, > + uint offset, ulong value, > + enum pci_size_t size) > +{ > + return pci_generic_mmap_write_config(bus, brcm_pcie_config_address, > + bdf, offset, value, size); > +} > + > +static const char *link_speed_to_str(unsigned int s) > +{ > + static const char * const speed_str[] = { "??", "2.5", "5.0", "8.0" }; > + > + if (s >= ARRAY_SIZE(speed_str)) > + s = 0; > + > + return speed_str[s]; > +} > + > +static inline void brcm_pcie_bridge_sw_init_set(struct brcm_pcie *pcie, u32 val) > +{ > + u32 tmp; > + > + tmp = readl(pcie->base + PCIE_RGR1_SW_INIT_1); > + u32p_replace_bits(&tmp, val, PCIE_RGR1_SW_INIT_1_INIT_MASK); > + writel(tmp, pcie->base + PCIE_RGR1_SW_INIT_1); See clrsetbits_le32(), etc. Below alos. > +} > + > +static inline void brcm_pcie_perst_set(struct brcm_pcie *pcie, u32 val) > +{ > + u32 tmp; > + > + tmp = readl(pcie->base + PCIE_RGR1_SW_INIT_1); > + u32p_replace_bits(&tmp, val, PCIE_RGR1_SW_INIT_1_PERST_MASK); > + writel(tmp, pcie->base + PCIE_RGR1_SW_INIT_1); > +} > + > +static u32 brcm_pcie_mdio_form_pkt(int port, int regad, int cmd) > +{ > + u32 pkt = 0; > + > + pkt |= FIELD_PREP(MDIO_PORT_MASK, port); > + pkt |= FIELD_PREP(MDIO_REGAD_MASK, regad); > + pkt |= FIELD_PREP(MDIO_CMD_MASK, cmd); > + > + return pkt; > +} > + > +/* Negative return value indicates error */ This would be incorporated in a full function comment > +static int brcm_pcie_mdio_read(void __iomem *base, u8 port, u8 regad, u32 *val) > +{ > + int tries; > + u32 data; > + > + writel(brcm_pcie_mdio_form_pkt(port, regad, MDIO_CMD_READ), > + base + PCIE_RC_DL_MDIO_ADDR); > + readl(base + PCIE_RC_DL_MDIO_ADDR); > + > + data = readl(base + PCIE_RC_DL_MDIO_RD_DATA); > + for (tries = 0; !MDIO_RD_DONE(data) && tries < 10; tries++) { > + udelay(10); > + data = readl(base + PCIE_RC_DL_MDIO_RD_DATA); > + } > + > + *val = FIELD_GET(MDIO_DATA_MASK, data); > + return MDIO_RD_DONE(data) ? 0 : -EIO; > +} > + > +/* Negative return value indicates error */ > +static int brcm_pcie_mdio_write(void __iomem *base, u8 port, > + u8 regad, u16 wrdata) > +{ > + int tries; > + u32 data; > + > + writel(brcm_pcie_mdio_form_pkt(port, regad, MDIO_CMD_WRITE), > + base + PCIE_RC_DL_MDIO_ADDR); > + readl(base + PCIE_RC_DL_MDIO_ADDR); > + writel(MDIO_DATA_DONE_MASK | wrdata, base + PCIE_RC_DL_MDIO_WR_DATA); > + > + data = readl(base + PCIE_RC_DL_MDIO_WR_DATA); > + for (tries = 0; !MDIO_WT_DONE(data) && tries < 10; tries++) { > + udelay(10); > + data = readl(base + PCIE_RC_DL_MDIO_WR_DATA); > + } > + > + return MDIO_WT_DONE(data) ? 0 : -EIO; > +} > + > +/* > + * Configures device for Spread Spectrum Clocking (SSC) mode; negative > + * return value indicates error. > + */ > +static int brcm_pcie_set_ssc(struct brcm_pcie *pcie) > +{ > + void __iomem *base = pcie->base; > + int pll, ssc; > + int ret; > + u32 tmp; > + > + ret = brcm_pcie_mdio_write(base, MDIO_PORT0, SET_ADDR_OFFSET, > + SSC_REGS_ADDR); > + if (ret < 0) > + return ret; > + > + ret = brcm_pcie_mdio_read(base, MDIO_PORT0, SSC_CNTL_OFFSET, &tmp); > + if (ret < 0) > + return ret; > + > + u32p_replace_bits(&tmp, 1, SSC_CNTL_OVRD_EN_MASK); > + u32p_replace_bits(&tmp, 1, SSC_CNTL_OVRD_VAL_MASK); > + ret = brcm_pcie_mdio_write(base, MDIO_PORT0, SSC_CNTL_OFFSET, tmp); > + if (ret < 0) > + return ret; > + > + udelay(1000); > + ret = brcm_pcie_mdio_read(base, MDIO_PORT0, SSC_STATUS_OFFSET, &tmp); > + if (ret < 0) > + return ret; > + > + ssc = FIELD_GET(SSC_STATUS_SSC_MASK, tmp); > + pll = FIELD_GET(SSC_STATUS_PLL_LOCK_MASK, tmp); > + > + return ssc && pll ? 0 : -EIO; > +} > + > +/* Limits operation to a specific generation (1, 2, or 3) */ > +static void brcm_pcie_set_gen(struct brcm_pcie *pcie, int gen) > +{ > + void __iomem *base = pcie->base; > + > + u16 lnkctl2 = readw(base + BRCM_PCIE_CAP_REGS + PCI_EXP_LNKCTL2); > + u32 lnkcap = readl(base + BRCM_PCIE_CAP_REGS + PCI_EXP_LNKCAP); > + > + lnkcap = (lnkcap & ~PCI_EXP_LNKCAP_SLS) | gen; > + writel(lnkcap, base + BRCM_PCIE_CAP_REGS + PCI_EXP_LNKCAP); > + > + lnkctl2 = (lnkctl2 & ~0xf) | gen; > + writew(lnkctl2, base + BRCM_PCIE_CAP_REGS + PCI_EXP_LNKCTL2); > +} > + > +static void brcm_pcie_set_outbound_win(struct brcm_pcie *pcie, > + unsigned int win, u64 phys_addr, > + u64 pcie_addr, u64 size) > +{ > + void __iomem *base = pcie->base; > + u32 phys_addr_mb_high, limit_addr_mb_high; > + phys_addr_t phys_addr_mb, limit_addr_mb; > + int high_addr_shift; > + u32 tmp; > + > + /* Set the base of the pcie_addr window */ > + writel(lower_32_bits(pcie_addr), base + PCIE_MEM_WIN0_LO(win)); > + writel(upper_32_bits(pcie_addr), base + PCIE_MEM_WIN0_HI(win)); > + > + /* Write the addr base & limit lower bits (in MBs) */ > + phys_addr_mb = phys_addr / SZ_1M; > + limit_addr_mb = (phys_addr + size - 1) / SZ_1M; > + > + tmp = readl(base + PCIE_MEM_WIN0_BASE_LIMIT(win)); > + u32p_replace_bits(&tmp, phys_addr_mb, > + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT_BASE_MASK); > + u32p_replace_bits(&tmp, limit_addr_mb, > + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT_LIMIT_MASK); > + writel(tmp, base + PCIE_MEM_WIN0_BASE_LIMIT(win)); > + > + /* Write the cpu & limit addr upper bits */ > + high_addr_shift = PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT_BASE_HI_SHIFT; > + phys_addr_mb_high = phys_addr_mb >> high_addr_shift; > + tmp = readl(base + PCIE_MEM_WIN0_BASE_HI(win)); > + u32p_replace_bits(&tmp, phys_addr_mb_high, > + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_HI_BASE_MASK); > + writel(tmp, base + PCIE_MEM_WIN0_BASE_HI(win)); > + > + limit_addr_mb_high = limit_addr_mb >> high_addr_shift; > + tmp = readl(base + PCIE_MEM_WIN0_LIMIT_HI(win)); > + u32p_replace_bits(&tmp, limit_addr_mb_high, > + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LIMIT_HI_LIMIT_MASK); > + writel(tmp, base + PCIE_MEM_WIN0_LIMIT_HI(win)); > +} > + > +static int brcm_pcie_probe(struct udevice *dev) > +{ > + struct udevice *ctlr = pci_get_controller(dev); > + struct pci_controller *hose = dev_get_uclass_priv(ctlr); > + struct brcm_pcie *pcie = dev_get_priv(dev); > + void __iomem *base = pcie->base; > + 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 */ > + brcm_pcie_bridge_sw_init_set(pcie, 1); > + > + udelay(150); Please add a comment as to how you chose the value, and below. > + > + /* Take the bridge out of reset */ > + brcm_pcie_bridge_sw_init_set(pcie, 0); > + > + tmp = readl(base + PCIE_MISC_HARD_PCIE_HARD_DEBUG); > + tmp &= ~PCIE_MISC_HARD_PCIE_HARD_DEBUG_SERDES_IDDQ_MASK; > + writel(tmp, base + PCIE_MISC_HARD_PCIE_HARD_DEBUG); > + /* Wait for SerDes to be stable */ > + udelay(150); > + > + /* Set SCB_MAX_BURST_SIZE, CFG_READ_UR_MODE, SCB_ACCESS_EN */ > + u32p_replace_bits(&tmp, 1, PCIE_MISC_MISC_CTRL_SCB_ACCESS_EN_MASK); > + u32p_replace_bits(&tmp, 1, PCIE_MISC_MISC_CTRL_CFG_READ_UR_MODE_MASK); > + u32p_replace_bits(&tmp, PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_128, > + PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_MASK); > + writel(tmp, base + PCIE_MISC_MISC_CTRL); > + > + /* > + * TODO: When support for other SoCs than BCM2711 is added we may > + * need to use the base address and size(s) provided in the dma-ranges > + * property. > + */ > + rc_bar2_offset = 0; > + rc_bar2_size = 0xc0000000; > + > + tmp = lower_32_bits(rc_bar2_offset); > + u32p_replace_bits(&tmp, brcm_pcie_encode_ibar_size(rc_bar2_size), > + PCIE_MISC_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, > + PCIE_MISC_MISC_CTRL_SCB0_SIZE_MASK); > + writel(tmp, base + PCIE_MISC_MISC_CTRL); > + > + /* Disable the PCIe->GISB memory window (RC_BAR1) */ > + tmp = readl(base + PCIE_MISC_RC_BAR1_CONFIG_LO); > + tmp &= ~PCIE_MISC_RC_BAR1_CONFIG_LO_SIZE_MASK; > + writel(tmp, base + PCIE_MISC_RC_BAR1_CONFIG_LO); > + > + /* Disable the PCIe->SCB memory window (RC_BAR3) */ > + tmp = readl(base + PCIE_MISC_RC_BAR3_CONFIG_LO); > + tmp &= ~PCIE_MISC_RC_BAR3_CONFIG_LO_SIZE_MASK; > + writel(tmp, base + PCIE_MISC_RC_BAR3_CONFIG_LO); > + > + /* Mask all interrupts since we are not handling any yet */ > + writel(0xffffffff, base + PCIE_MSI_INTR2_MASK_SET); > + > + /* Clear any interrupts we find on boot */ > + writel(0xffffffff, base + PCIE_MSI_INTR2_CLR); > + > + if (pcie->gen) > + brcm_pcie_set_gen(pcie, pcie->gen); > + > + /* Unassert the fundamental reset */ > + brcm_pcie_perst_set(pcie, 0); > + > + /* Give the RC/EP time to wake up, before trying to configure RC. > + * Intermittently check status for link-up, up to a total of 100ms. > + */ > + for (i = 0; i < 100 && !brcm_pcie_link_up(pcie); i += 5) > + msleep(5); > + > + if (!brcm_pcie_link_up(pcie)) { > + printf("PCIe BRCM: link down\n"); > + return -ENODEV; Again, cannot return that error > + } > + > + if (!brcm_pcie_rc_mode(pcie)) { > + printf("PCIe misconfigured; is in EP mode\n"); > + return -EINVAL; > + } > + > + for (i = 0; i < hose->region_count; i++) { > + struct pci_region *reg = &hose->regions[i]; > + > + if (reg->flags != PCI_REGION_MEM) > + continue; > + > + if (num_out_wins >= BRCM_NUM_PCIE_OUT_WINS) > + return -EINVAL; > + > + brcm_pcie_set_outbound_win(pcie, num_out_wins, reg->phys_start, > + reg->bus_start, reg->size); > + > + num_out_wins++; > + } > + > + /* > + * For config space accesses on the RC, show the right class for > + * a PCIe-PCIe bridge (the default setting is to be EP mode). > + */ > + tmp = readl(base + PCIE_RC_CFG_PRIV1_ID_VAL3); > + u32p_replace_bits(&tmp, 0x060400, > + PCIE_RC_CFG_PRIV1_ID_VAL3_CLASS_CODE_MASK); > + writel(tmp, base + PCIE_RC_CFG_PRIV1_ID_VAL3); > + > + if (pcie->ssc) { > + ret = brcm_pcie_set_ssc(pcie); > + if (ret == 0) > + ssc_good = true; > + else > + printf("PCIe BRCM: failed attempt to enter SSC mode\n"); Should this return an error? > + } > + > + lnksta = readw(base + BRCM_PCIE_CAP_REGS + PCI_EXP_LNKSTA); > + cls = FIELD_GET(PCI_EXP_LNKSTA_CLS, lnksta); > + nlw = FIELD_GET(PCI_EXP_LNKSTA_NLW, lnksta); > + > + printf("PCIe BRCM: link up, %s Gbps x%u %s\n", link_speed_to_str(cls), > + nlw, ssc_good ? "(SSC)" : "(!SSC)"); > + > + /* PCIe->SCB endian mode for BAR */ > + tmp = readl(base + PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1); > + u32p_replace_bits(&tmp, PCIE_RC_CFG_VENDOR_SPCIFIC_REG1_LITTLE_ENDIAN, > + PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1_ENDIAN_MODE_BAR2_MASK); > + writel(tmp, base + PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1); > + > + /* > + * Refclk from RC should be gated with CLKREQ# input when ASPM L0s,L1 > + * is enabled => setting the CLKREQ_DEBUG_ENABLE field to 1. > + */ > + tmp = readl(base + PCIE_MISC_HARD_PCIE_HARD_DEBUG); > + tmp |= PCIE_MISC_HARD_PCIE_HARD_DEBUG_CLKREQ_DEBUG_ENABLE_MASK; > + writel(tmp, base + PCIE_MISC_HARD_PCIE_HARD_DEBUG); > + > + return 0; > +} > + > +static int brcm_pcie_ofdata_to_platdata(struct udevice *dev) > +{ > + struct brcm_pcie *pcie = dev_get_priv(dev); > + ofnode dn = dev_ofnode(dev); > + u32 max_link_speed; > + int ret; > + > + /* 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"); > + > + ret = ofnode_read_u32(dn, "max-link-speed", &max_link_speed); > + if (ret < 0 || max_link_speed > 4) > + pcie->gen = 0; > + else > + pcie->gen = max_link_speed; > + > + return 0; > +} > + > +static const struct dm_pci_ops brcm_pcie_ops = { > + .read_config = brcm_pcie_read_config, > + .write_config = brcm_pcie_write_config, > +}; > + > +static const struct udevice_id brcm_pcie_ids[] = { > + { .compatible = "brcm,bcm2711-pcie" }, > + { } > +}; > + > +U_BOOT_DRIVER(pcie_brcm_base) = { > + .name = "pcie_brcm", > + .id = UCLASS_PCI, > + .ops = &brcm_pcie_ops, > + .of_match = brcm_pcie_ids, > + .probe = brcm_pcie_probe, > + .ofdata_to_platdata = brcm_pcie_ofdata_to_platdata, > + .priv_auto_alloc_size = sizeof(struct brcm_pcie), > +}; > -- > 2.7.4 > Regards, Simon
Hi, I'll try to add my two cents. On Wed, 2020-05-06 at 08:47 -0600, Simon Glass wrote: > > +config PCI_BRCMSTB > > + bool "Broadcom STB PCIe controller" > > + depends on DM_PCI > > + depends on ARCH_BCM283X > > + help > > + Say Y here if you want to enable Broadcom STB PCIe controller > > support. > > What is STB? What features does this support? You should get a warning > here to write at least three lines. STB is short for set top box, which is the wider family of SoCs bcm2711 is part of[1]. This implementation is, for now, tailor made for bcm2711. The bus not being exposed, only the relevant bits to enabling xHCI compatibility, which is hardwired into the RPi4 board, have been tested. For example there is so far no support for IO memory mappings. Note that some work is already being done on the Linux version of this driver to incorporate other devices, which will be easily ported here, as for now, both are pretty much the same (minus interrupt handling). [1] https://www.broadcom.com/products/broadband/set-top-box [...] > > +#define PCIE_MISC_MISC_CTRL 0x4008 > > +#define PCIE_MISC_MISC_CTRL_SCB_ACCESS_EN_MASK 0x1000 > > +#define PCIE_MISC_MISC_CTRL_CFG_READ_UR_MODE_MASK 0x2000 > > +#define PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_MASK 0x300000 > > +#define PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_128 0x0 > > +#define PCIE_MISC_MISC_CTRL_SCB0_SIZE_MASK 0xf8000000 > > If you have a _MASK, don't you need a _SHIFT to allow you to read from > the field? See patch #7, where we import some bitfield helpers from Linux. The shift calculation happens there. > Can you drop the PCIE_MISC prefix? These are very long and local to this file. This was also discussed when upstreaming this in Linux, Broadcom engineers asked to keep the naming scheme as is, convoluted as it is, to help with debugging in the future. I added Jim and Florian in to the conversation in case they want to clarify something. [...] > > + > > + return !!FIELD_GET(PCIE_MISC_PCIE_STATUS_PCIE_PORT_MASK, val); > > What is FIELD_GET? We have this sort of thing rejected in the past. This is also part of the bitfield helpers introduced in patch #7 [...] > > +static int brcm_pcie_probe(struct udevice *dev) > > +{ > > + struct udevice *ctlr = pci_get_controller(dev); > > + struct pci_controller *hose = dev_get_uclass_priv(ctlr); > > + struct brcm_pcie *pcie = dev_get_priv(dev); > > + void __iomem *base = pcie->base; > > + 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 */ > > + brcm_pcie_bridge_sw_init_set(pcie, 1); > > + > > + udelay(150); > > Please add a comment as to how you chose the value, and below. This was picked from Jim Quinlan's original code submission: https://lkml.org/lkml/2018/9/19/642 Sadly there isn't any comment there. Regards, Nicolas -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 488 bytes Desc: This is a digitally signed message part URL: <https://lists.denx.de/pipermail/u-boot/attachments/20200508/8e017400/attachment.sig>
Hi Simon, On 06.05.2020 16:47, Simon Glass wrote: > On Mon, 4 May 2020 at 06:45, Sylwester Nawrocki <s.nawrocki at samsung.com> wrote: >> --- >> drivers/pci/Kconfig | 6 + >> drivers/pci/Makefile | 1 + >> drivers/pci/pcie_brcmstb.c | 594 +++++++++++++++++++++++++++++++++++++++++++++ >> 3 files changed, 601 insertions(+) >> create mode 100644 drivers/pci/pcie_brcmstb.c > > A few small comments. Thank you for time and a valuable review. >> diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig >> +config PCI_BRCMSTB >> + bool "Broadcom STB PCIe controller" >> + depends on DM_PCI >> + depends on ARCH_BCM283X >> + help >> + Say Y here if you want to enable Broadcom STB PCIe controller support. > > What is STB? What features does this support? You should get a warning > here to write at least three lines. I'm going to change that help text to something along the lines of: 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. >> diff --git a/drivers/pci/pcie_brcmstb.c b/drivers/pci/pcie_brcmstb.c >> new file mode 100644 >> index 0000000..c6ddf92 >> --- /dev/null >> +++ b/drivers/pci/pcie_brcmstb.c >> @@ -0,0 +1,594 @@ >> +#include <asm/io.h> >> +#include <common.h> >> +#include <dm.h> >> +#include <dm/ofnode.h> >> +#include <errno.h> >> +#include <linux/bitfield.h> >> +#include <linux/log2.h> >> +#include <pci.h> > > Check ordering of include files: > > https://protect2.fireeye.com/url?k=c3a0292d-9e737093-c3a1a262-0cc47a31ba82-690f9f10b3970f9d&q=1&u=https%3A%2F%2Fwww.denx.de%2Fwiki%2FU-Boot%2FCodingStyle Thanks for the hint, it felt there was something wrong with the ordering. >> +#define PCIE_MISC_MISC_CTRL 0x4008 >> +#define PCIE_MISC_MISC_CTRL_SCB_ACCESS_EN_MASK 0x1000 >> +#define PCIE_MISC_MISC_CTRL_CFG_READ_UR_MODE_MASK 0x2000 >> +#define PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_MASK 0x300000 >> +#define PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_128 0x0 >> +#define PCIE_MISC_MISC_CTRL_SCB0_SIZE_MASK 0xf8000000 > > If you have a _MASK, don't you need a _SHIFT to allow you to read from > the field? I had shift definitions originally but these got removed when we started to use the FIELD_GET macro and similar. Shifts are retrieved there from a mask by ffs() call (find first bit set in a word). If that's not preferred in u-boot I will switch back to using explicit shift definitions. > Can you drop the PCIE_MISC prefix? These are very long and local to this file. Yes, will do. It seems many of these bit field macro definitions could have shorter names. I revisited them all again and shortened most of them. >> +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LO 0x400c >> +#define PCIE_MEM_WIN0_LO(win) \ >> + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LO + ((win) * 4) >> +/* MDIO registers */ >> +#define MDIO_RD_DONE(x) (((x) & MDIO_DATA_DONE_MASK) ? 1 : 0) >> +#define MDIO_WT_DONE(x) (((x) & MDIO_DATA_DONE_MASK) ? 0 : 1) > > Are these two worth it? You can do this in your code> if (readl(xxx) & MDIO_DATA_DONE_MASK) I will drop these macros, have copied them from the Linux driver without much thought. They are not really necessary, especially when we use readl_poll_timout(). > If you must use these, then I think true/false are better than 1/0. >> +struct brcm_pcie { > > struct comment Ok. >> + void __iomem *base; >> + >> + int gen; >> + bool ssc; >> +}; >> + >> +#define msleep(a) udelay((a) * 1000) > > This is already defined in U-Boot. Indeed, I should have grepped for mdelay rather than msleep. >> +/* >> + * This is to convert the size of the inbound "BAR" region to the >> + * non-linear values of PCIE_X_MISC_RC_BAR[123]_CONFIG_LO.SIZE > > Please use a proper function comments with args and return value. Will improve that. >> + */ >> +static int brcm_pcie_encode_ibar_size(u64 size) >> +{ >> + int log2_in = ilog2(size); >> + >> + if (log2_in >= 12 && log2_in <= 15) >> + /* Covers 4KB to 32KB (inclusive) */ >> + return (log2_in - 12) + 0x1c; >> + else if (log2_in >= 16 && log2_in <= 37) >> + /* Covers 64KB to 32GB, (inclusive) */ >> + return log2_in - 15; >> + /* Something is awry so disable */ > > blank line always before return at end of function >> + return 0; >> +} >> +/* The controller is capable of serving in both RC and EP roles */ >> +static bool brcm_pcie_rc_mode(struct brcm_pcie *pcie) >> +{ >> + u32 val = readl(pcie->base + PCIE_MISC_PCIE_STATUS); > > Consider using a struct for your registers which would reduce the size > of this code. I agree with all your other comments but here I have some doubts. The register offsets used are sparse and there will be many gaps in a struct. Anyway, I will give it a try and see how it looks. >> + return !!FIELD_GET(PCIE_MISC_PCIE_STATUS_PCIE_PORT_MASK, val); > > What is FIELD_GET? We have this sort of thing rejected in the past. It's a common macro from linux/bitfield.h for masking and shifting a bitfield. If its use is not preferred I will switch to using explicit _SHIFTs as I mentioned above. > Here, since you return a bool, the !! should not be necessary. > >> +} >> + >> +static bool brcm_pcie_link_up(struct brcm_pcie *pcie) >> +{ >> + u32 val = readl(pcie->base + PCIE_MISC_PCIE_STATUS); >> + u32 dla = FIELD_GET(PCIE_MISC_PCIE_STATUS_PCIE_DL_ACTIVE_MASK, val); >> + u32 plu = FIELD_GET(PCIE_MISC_PCIE_STATUS_PCIE_PHYLINKUP_MASK, val); > > PCIE_MISC_PCIE_STATUS_PCIE_PHYLINKUP_MASK is way too long. > > How about STATUS_PCIE_PHYLINKUP_MASK? Yes, that really looks much more reasonable. >> + return dla && plu; >> +} >> + >> +static int brcm_pcie_config_address(const struct udevice *udev, pci_dev_t bdf, > > Please use 'dev' instead of udev for the device. Fixed. >> + uint offset, void **paddress) >> +{ >> + struct brcm_pcie *pcie = dev_get_priv(udev); >> + unsigned int bus = PCI_BUS(bdf); >> + unsigned int dev = PCI_DEV(bdf); >> + int idx; >> + >> + /* >> + * Busses 0 (host PCIe bridge) and 1 (its immediate child) >> + * are limited to a single device each >> + */ >> + if ((bus == (udev->seq + 1)) && dev > 0) >> + return -ENODEV; >> + >> + /* Accesses to the RC go right to the RC registers if PCI device == 0 */ >> + if (bus == udev->seq) { >> + if (PCI_DEV(bdf)) >> + return -ENODEV; > > You can't return that as there is a device. Perhaps ENXIO? Does this > indicate an internal error? If so you could add a comment here as to > how to fix it. The function is supposed to return 0 when @bdf is valid and a non 0 value otherwise. The pci core code calls it with a range of PCI bus/device/func and we have to return 0 only when a PCI bus/device/function is valid. Exact error code from this function is not propagated further as can be seen in pci_generic_mmap_read_config(). This function only gets called by below two read/write function callbacks. I just followed what is done in other PCIe drivers and used ENODEV. I'm going to change the error code to EINVAL. I have also already simplified this function so a non-zero value is returned in one place at the beginning only. >> + *paddress = pcie->base + offset; >> + return 0; >> + } >> + >> + /* For devices, write to the config space index register */ >> + idx = brcm_pcie_cfg_index(bdf, 0); >> + >> + writel(idx, pcie->base + PCIE_EXT_CFG_INDEX); >> + *paddress = pcie->base + PCIE_EXT_CFG_DATA + offset; >> + >> + return 0; >> +} >> + >> +static int brcm_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, brcm_pcie_config_address, >> + bdf, offset, valuep, size); >> +} >> + >> +static int brcm_pcie_write_config(struct udevice *bus, pci_dev_t bdf, >> + uint offset, ulong value, >> + enum pci_size_t size) >> +{ >> + return pci_generic_mmap_write_config(bus, brcm_pcie_config_address, >> + bdf, offset, value, size); >> +} >> +static inline void brcm_pcie_bridge_sw_init_set(struct brcm_pcie *pcie, u32 val) >> +{ >> + u32 tmp; >> + >> + tmp = readl(pcie->base + PCIE_RGR1_SW_INIT_1); >> + u32p_replace_bits(&tmp, val, PCIE_RGR1_SW_INIT_1_INIT_MASK); >> + writel(tmp, pcie->base + PCIE_RGR1_SW_INIT_1); > > See clrsetbits_le32(), etc. Below alos. Yes, thanks, that's really what I was missing but somehow overlooked. >> +} >> +/* Negative return value indicates error */ > > This would be incorporated in a full function comment OK, will improve that. >> +static int brcm_pcie_mdio_read(void __iomem *base, u8 port, u8 regad, u32 *val) >> +{ >> + int tries; >> + u32 data; >> + >> + writel(brcm_pcie_mdio_form_pkt(port, regad, MDIO_CMD_READ), >> + base + PCIE_RC_DL_MDIO_ADDR); >> + readl(base + PCIE_RC_DL_MDIO_ADDR); >> + >> + data = readl(base + PCIE_RC_DL_MDIO_RD_DATA); >> + for (tries = 0; !MDIO_RD_DONE(data) && tries < 10; tries++) { >> + udelay(10); >> + data = readl(base + PCIE_RC_DL_MDIO_RD_DATA); >> + } >> + >> + *val = FIELD_GET(MDIO_DATA_MASK, data); >> + return MDIO_RD_DONE(data) ? 0 : -EIO; >> +} >> +static int brcm_pcie_probe(struct udevice *dev) >> +{ >> + struct udevice *ctlr = pci_get_controller(dev); >> + struct pci_controller *hose = dev_get_uclass_priv(ctlr); >> + struct brcm_pcie *pcie = dev_get_priv(dev); >> + void __iomem *base = pcie->base; >> + 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 */ >> + brcm_pcie_bridge_sw_init_set(pcie, 1); >> + >> + udelay(150); > > Please add a comment as to how you chose the value, and below. I'm not sure what are exact requirements for these delays, in downstream Linux code usleep_range(100, 200) is used, so I picked 150 us value. >> + /* Take the bridge out of reset */ >> + brcm_pcie_bridge_sw_init_set(pcie, 0); >> + >> + tmp = readl(base + PCIE_MISC_HARD_PCIE_HARD_DEBUG); >> + tmp &= ~PCIE_MISC_HARD_PCIE_HARD_DEBUG_SERDES_IDDQ_MASK; >> + writel(tmp, base + PCIE_MISC_HARD_PCIE_HARD_DEBUG); >> + /* Wait for SerDes to be stable */ >> + udelay(150); >> + if (!brcm_pcie_link_up(pcie)) { >> + printf("PCIe BRCM: link down\n"); >> + return -ENODEV; > > Again, cannot return that error Will make it EINVAL as is done below. >> + } >> + >> + if (!brcm_pcie_rc_mode(pcie)) { >> + printf("PCIe misconfigured; is in EP mode\n"); >> + return -EINVAL; >> + } >> + >> + if (pcie->ssc) { >> + ret = brcm_pcie_set_ssc(pcie); >> + if (ret == 0) >> + ssc_good = true; >> + else >> + printf("PCIe BRCM: failed attempt to enter SSC mode\n"); > > Should this return an error? In Linux driver any errors here are also ignored. AFAIU that Spread Spectrum Clocking is not critical for the PCIe controller's operation, it's just for EMI reduction. I don't have detailed documentation of the controller and I'm not sure what comment could be added here.
Hi Sylwester, On Fri, 8 May 2020 at 05:46, Sylwester Nawrocki <s.nawrocki at samsung.com> wrote: > > Hi Simon, > > On 06.05.2020 16:47, Simon Glass wrote: > > On Mon, 4 May 2020 at 06:45, Sylwester Nawrocki <s.nawrocki at samsung.com> wrote: > > >> --- > >> drivers/pci/Kconfig | 6 + > >> drivers/pci/Makefile | 1 + > >> drivers/pci/pcie_brcmstb.c | 594 +++++++++++++++++++++++++++++++++++++++++++++ > >> 3 files changed, 601 insertions(+) > >> create mode 100644 drivers/pci/pcie_brcmstb.c > > > > A few small comments. > > Thank you for time and a valuable review. > > >> diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig > > >> +config PCI_BRCMSTB > >> + bool "Broadcom STB PCIe controller" > >> + depends on DM_PCI > >> + depends on ARCH_BCM283X > >> + help > >> + Say Y here if you want to enable Broadcom STB PCIe controller support. > > > > What is STB? What features does this support? You should get a warning > > here to write at least three lines. > > I'm going to change that help text to something along the lines of: > > 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. > > >> diff --git a/drivers/pci/pcie_brcmstb.c b/drivers/pci/pcie_brcmstb.c > >> new file mode 100644 > >> index 0000000..c6ddf92 > >> --- /dev/null > >> +++ b/drivers/pci/pcie_brcmstb.c > >> @@ -0,0 +1,594 @@ > > >> +#include <asm/io.h> > >> +#include <common.h> > >> +#include <dm.h> > >> +#include <dm/ofnode.h> > >> +#include <errno.h> > >> +#include <linux/bitfield.h> > >> +#include <linux/log2.h> > >> +#include <pci.h> > > > > Check ordering of include files: > > > > https://protect2.fireeye.com/url?k=c3a0292d-9e737093-c3a1a262-0cc47a31ba82-690f9f10b3970f9d&q=1&u=https%3A%2F%2Fwww.denx.de%2Fwiki%2FU-Boot%2FCodingStyle > > Thanks for the hint, it felt there was something wrong with the ordering. > > >> +#define PCIE_MISC_MISC_CTRL 0x4008 > >> +#define PCIE_MISC_MISC_CTRL_SCB_ACCESS_EN_MASK 0x1000 > >> +#define PCIE_MISC_MISC_CTRL_CFG_READ_UR_MODE_MASK 0x2000 > >> +#define PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_MASK 0x300000 > >> +#define PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_128 0x0 > >> +#define PCIE_MISC_MISC_CTRL_SCB0_SIZE_MASK 0xf8000000 > > > > If you have a _MASK, don't you need a _SHIFT to allow you to read from > > the field? > > I had shift definitions originally but these got removed when we started to > use the FIELD_GET macro and similar. Shifts are retrieved there from a mask > by ffs() call (find first bit set in a word). If that's not preferred in > u-boot I will switch back to using explicit shift definitions. +Tom Rini We've had a policy of avoiding these for some time, but now that Linux has them, should we pull this in and start using it? It is not that widely used in Linux but has been there for a few years. Regards, Simon
On Fri, May 8, 2020 at 5:50 AM Nicolas Saenz Julienne <nsaenzjulienne at suse.de> wrote: > > Hi, I'll try to add my two cents. > > On Wed, 2020-05-06 at 08:47 -0600, Simon Glass wrote: > > > +config PCI_BRCMSTB > > > + bool "Broadcom STB PCIe controller" > > > + depends on DM_PCI > > > + depends on ARCH_BCM283X > > > + help > > > + Say Y here if you want to enable Broadcom STB PCIe controller > > > support. > > > > What is STB? What features does this support? You should get a warning > > here to write at least three lines. > > STB is short for set top box, which is the wider family of SoCs bcm2711 is part > of[1]. > > This implementation is, for now, tailor made for bcm2711. The bus not being > exposed, only the relevant bits to enabling xHCI compatibility, which is > hardwired into the RPi4 board, have been tested. For example there is so far no > support for IO memory mappings. Note that some work is already being done on > the Linux version of this driver to incorporate other devices, which will be > easily ported here, as for now, both are pretty much the same (minus interrupt > handling). > > [1] https://www.broadcom.com/products/broadband/set-top-box > > [...] > > > > +#define PCIE_MISC_MISC_CTRL 0x4008 > > > +#define PCIE_MISC_MISC_CTRL_SCB_ACCESS_EN_MASK 0x1000 > > > +#define PCIE_MISC_MISC_CTRL_CFG_READ_UR_MODE_MASK 0x2000 > > > +#define PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_MASK 0x300000 > > > +#define PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_128 0x0 > > > +#define PCIE_MISC_MISC_CTRL_SCB0_SIZE_MASK 0xf8000000 > > > > If you have a _MASK, don't you need a _SHIFT to allow you to read from > > the field? > > See patch #7, where we import some bitfield helpers from Linux. The shift > calculation happens there. > > > Can you drop the PCIE_MISC prefix? These are very long and local to this file. > > This was also discussed when upstreaming this in Linux, Broadcom engineers > asked to keep the naming scheme as is, convoluted as it is, to help with > debugging in the future. I added Jim and Florian in to the conversation in case > they want to clarify something. > > [...] > > > > + > > > + return !!FIELD_GET(PCIE_MISC_PCIE_STATUS_PCIE_PORT_MASK, val); > > > > What is FIELD_GET? We have this sort of thing rejected in the past. > > This is also part of the bitfield helpers introduced in patch #7 > > [...] > > > > +static int brcm_pcie_probe(struct udevice *dev) > > > +{ > > > + struct udevice *ctlr = pci_get_controller(dev); > > > + struct pci_controller *hose = dev_get_uclass_priv(ctlr); > > > + struct brcm_pcie *pcie = dev_get_priv(dev); > > > + void __iomem *base = pcie->base; > > > + 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 */ > > > + brcm_pcie_bridge_sw_init_set(pcie, 1); > > > + > > > + udelay(150); > > > > Please add a comment as to how you chose the value, and below. > > This was picked from Jim Quinlan's original code submission: > https://lkml.org/lkml/2018/9/19/642 > > Sadly there isn't any comment there. The bridge is being reset and then un-reset. The delay is a safety precaution to preclude the reset signal from looking like a glitch. BTW, what is the context here? Aren't these patches already upstream? Regards, Jim > > Regards, > Nicolas >
Hi Jim, On Fri, 2020-05-08 at 10:25 -0400, Jim Quinlan wrote: > > > > +static int brcm_pcie_probe(struct udevice *dev) > > > > +{ > > > > + struct udevice *ctlr = pci_get_controller(dev); > > > > + struct pci_controller *hose = dev_get_uclass_priv(ctlr); > > > > + struct brcm_pcie *pcie = dev_get_priv(dev); > > > > + void __iomem *base = pcie->base; > > > > + 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 */ > > > > + brcm_pcie_bridge_sw_init_set(pcie, 1); > > > > + > > > > + udelay(150); > > > > > > Please add a comment as to how you chose the value, and below. > > > > This was picked from Jim Quinlan's original code submission: > > https://lkml.org/lkml/2018/9/19/642 > > > > Sadly there isn't any comment there. > > The bridge is being reset and then un-reset. The delay is a safety > precaution to preclude the reset signal from looking like a glitch. > BTW, what is the context here? Aren't these patches already upstream? This is the submission for u-boot. Regards, Nicolas -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 488 bytes Desc: This is a digitally signed message part URL: <https://lists.denx.de/pipermail/u-boot/attachments/20200508/64122183/attachment.sig>
Hi Jim, On Fri, 8 May 2020 at 08:25, Jim Quinlan <james.quinlan at broadcom.com> wrote: > > On Fri, May 8, 2020 at 5:50 AM Nicolas Saenz Julienne > <nsaenzjulienne at suse.de> wrote: > > > > Hi, I'll try to add my two cents. > > > > On Wed, 2020-05-06 at 08:47 -0600, Simon Glass wrote: > > > > +config PCI_BRCMSTB > > > > + bool "Broadcom STB PCIe controller" > > > > + depends on DM_PCI > > > > + depends on ARCH_BCM283X > > > > + help > > > > + Say Y here if you want to enable Broadcom STB PCIe controller > > > > support. > > > > > > What is STB? What features does this support? You should get a warning > > > here to write at least three lines. > > > > STB is short for set top box, which is the wider family of SoCs bcm2711 is part > > of[1]. > > > > This implementation is, for now, tailor made for bcm2711. The bus not being > > exposed, only the relevant bits to enabling xHCI compatibility, which is > > hardwired into the RPi4 board, have been tested. For example there is so far no > > support for IO memory mappings. Note that some work is already being done on > > the Linux version of this driver to incorporate other devices, which will be > > easily ported here, as for now, both are pretty much the same (minus interrupt > > handling). > > > > [1] https://www.broadcom.com/products/broadband/set-top-box > > > > [...] > > > > > > +#define PCIE_MISC_MISC_CTRL 0x4008 > > > > +#define PCIE_MISC_MISC_CTRL_SCB_ACCESS_EN_MASK 0x1000 > > > > +#define PCIE_MISC_MISC_CTRL_CFG_READ_UR_MODE_MASK 0x2000 > > > > +#define PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_MASK 0x300000 > > > > +#define PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_128 0x0 > > > > +#define PCIE_MISC_MISC_CTRL_SCB0_SIZE_MASK 0xf8000000 > > > > > > If you have a _MASK, don't you need a _SHIFT to allow you to read from > > > the field? > > > > See patch #7, where we import some bitfield helpers from Linux. The shift > > calculation happens there. > > > > > Can you drop the PCIE_MISC prefix? These are very long and local to this file. > > > > This was also discussed when upstreaming this in Linux, Broadcom engineers > > asked to keep the naming scheme as is, convoluted as it is, to help with > > debugging in the future. I added Jim and Florian in to the conversation in case > > they want to clarify something. > > > > [...] > > > > > > + > > > > + return !!FIELD_GET(PCIE_MISC_PCIE_STATUS_PCIE_PORT_MASK, val); > > > > > > What is FIELD_GET? We have this sort of thing rejected in the past. > > > > This is also part of the bitfield helpers introduced in patch #7 > > > > [...] > > > > > > +static int brcm_pcie_probe(struct udevice *dev) > > > > +{ > > > > + struct udevice *ctlr = pci_get_controller(dev); > > > > + struct pci_controller *hose = dev_get_uclass_priv(ctlr); > > > > + struct brcm_pcie *pcie = dev_get_priv(dev); > > > > + void __iomem *base = pcie->base; > > > > + 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 */ > > > > + brcm_pcie_bridge_sw_init_set(pcie, 1); > > > > + > > > > + udelay(150); > > > > > > Please add a comment as to how you chose the value, and below. > > > > This was picked from Jim Quinlan's original code submission: > > https://lkml.org/lkml/2018/9/19/642 > > > > Sadly there isn't any comment there. > > The bridge is being reset and then un-reset. The delay is a safety > precaution to preclude the reset signal from looking like a glitch. OK well that is a reasonable comment. It is important to put comments on delays since people coming later may make changes and not know what the delays are for. > BTW, what is the context here? Aren't these patches already upstream? Not yet. Regards, Simon
Hi Jim, On 08.05.2020 16:25, Jim Quinlan wrote: >>>> static int brcm_pcie_probe(struct udevice *dev) >>>> +{ >>>> + struct udevice *ctlr = pci_get_controller(dev); >>>> + struct pci_controller *hose = dev_get_uclass_priv(ctlr); >>>> + struct brcm_pcie *pcie = dev_get_priv(dev); >>>> + void __iomem *base = pcie->base; >>>> + 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 */ >>>> + brcm_pcie_bridge_sw_init_set(pcie, 1); >>>> + >>>> + udelay(150); >>> Please add a comment as to how you chose the value, and below. >> This was picked from Jim Quinlan's original code submission: >> https://protect2.fireeye.com/url?k=9d9c41ed-c002ef77-9d9dcaa2-0cc47a336fae-205e162c16256602&q=1&u=https%3A%2F%2Flkml.org%2Flkml%2F2018%2F9%2F19%2F642 >> >> Sadly there isn't any comment there. > The bridge is being reset and then un-reset. The delay is a safety > precaution to preclude the reset signal from looking like a glitch. If you don't mind I will add that sentence as a comment for the reset delay.
On Tue, May 12, 2020 at 12:42 PM Sylwester Nawrocki <s.nawrocki at samsung.com> wrote: > > Hi Jim, > > On 08.05.2020 16:25, Jim Quinlan wrote: > >>>> static int brcm_pcie_probe(struct udevice *dev) > >>>> +{ > >>>> + struct udevice *ctlr = pci_get_controller(dev); > >>>> + struct pci_controller *hose = dev_get_uclass_priv(ctlr); > >>>> + struct brcm_pcie *pcie = dev_get_priv(dev); > >>>> + void __iomem *base = pcie->base; > >>>> + 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 */ > >>>> + brcm_pcie_bridge_sw_init_set(pcie, 1); > >>>> + > >>>> + udelay(150); > >>> Please add a comment as to how you chose the value, and below. > >> This was picked from Jim Quinlan's original code submission: > >> https://protect2.fireeye.com/url?k=9d9c41ed-c002ef77-9d9dcaa2-0cc47a336fae-205e162c16256602&q=1&u=https%3A%2F%2Flkml.org%2Flkml%2F2018%2F9%2F19%2F642 > >> > >> Sadly there isn't any comment there. > > > The bridge is being reset and then un-reset. The delay is a safety > > precaution to preclude the reset signal from looking like a glitch. > > If you don't mind I will add that sentence as a comment for the reset > delay. Yes, please do. Thanks, Jim > > -- > Regards, > Sylwester
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 437cd9a..056a021 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -197,4 +197,10 @@ config PCIE_MEDIATEK Say Y here if you want to enable Gen2 PCIe controller, which could be found on MT7623 SoC family. +config PCI_BRCMSTB + bool "Broadcom STB PCIe controller" + depends on DM_PCI + depends on ARCH_BCM283X + help + Say Y here if you want to enable Broadcom STB PCIe controller support. endif diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index c051ecc..3e53b1f 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -43,3 +43,4 @@ obj-$(CONFIG_PCI_PHYTIUM) += pcie_phytium.o obj-$(CONFIG_PCIE_INTEL_FPGA) += pcie_intel_fpga.o obj-$(CONFIG_PCI_KEYSTONE) += pcie_dw_ti.o obj-$(CONFIG_PCIE_MEDIATEK) += pcie_mediatek.o +obj-$(CONFIG_PCI_BRCMSTB) += pcie_brcmstb.o diff --git a/drivers/pci/pcie_brcmstb.c b/drivers/pci/pcie_brcmstb.c new file mode 100644 index 0000000..c6ddf92 --- /dev/null +++ b/drivers/pci/pcie_brcmstb.c @@ -0,0 +1,594 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Broadcom STB PCIe controller driver + * + * Copyright (C) 2020 Samsung Electronics Co., Ltd. + * + * Based on upstream Linux kernel driver: + * drivers/pci/controller/pcie-brcmstb.c + * Copyright (C) 2009 - 2017 Broadcom + * + * Based driver by Nicolas Saenz Julienne + * Copyright (C) 2020 Nicolas Saenz Julienne <nsaenzjulienne at suse.de> + */ + +#include <asm/io.h> +#include <common.h> +#include <dm.h> +#include <dm/ofnode.h> +#include <errno.h> +#include <linux/bitfield.h> +#include <linux/log2.h> +#include <pci.h> + +/* BRCM_PCIE_CAP_REGS - Offset for the mandatory capability config regs */ +#define BRCM_PCIE_CAP_REGS 0x00ac + +/* Broadcom STB PCIe Register Offsets */ +#define PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1 0x0188 +#define PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1_ENDIAN_MODE_BAR2_MASK 0xc +#define PCIE_RC_CFG_VENDOR_SPCIFIC_REG1_LITTLE_ENDIAN 0x0 + +#define PCIE_RC_CFG_PRIV1_ID_VAL3 0x043c +#define PCIE_RC_CFG_PRIV1_ID_VAL3_CLASS_CODE_MASK 0xffffff + +#define PCIE_RC_DL_MDIO_ADDR 0x1100 +#define PCIE_RC_DL_MDIO_WR_DATA 0x1104 +#define PCIE_RC_DL_MDIO_RD_DATA 0x1108 + +#define PCIE_MISC_MISC_CTRL 0x4008 +#define PCIE_MISC_MISC_CTRL_SCB_ACCESS_EN_MASK 0x1000 +#define PCIE_MISC_MISC_CTRL_CFG_READ_UR_MODE_MASK 0x2000 +#define PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_MASK 0x300000 +#define PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_128 0x0 +#define PCIE_MISC_MISC_CTRL_SCB0_SIZE_MASK 0xf8000000 + +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LO 0x400c +#define PCIE_MEM_WIN0_LO(win) \ + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LO + ((win) * 4) + +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_HI 0x4010 +#define PCIE_MEM_WIN0_HI(win) \ + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_HI + ((win) * 4) + +#define PCIE_MISC_RC_BAR1_CONFIG_LO 0x402c +#define PCIE_MISC_RC_BAR1_CONFIG_LO_SIZE_MASK 0x1f + +#define PCIE_MISC_RC_BAR2_CONFIG_LO 0x4034 +#define PCIE_MISC_RC_BAR2_CONFIG_LO_SIZE_MASK 0x1f +#define PCIE_MISC_RC_BAR2_CONFIG_HI 0x4038 + +#define PCIE_MISC_RC_BAR3_CONFIG_LO 0x403c +#define PCIE_MISC_RC_BAR3_CONFIG_LO_SIZE_MASK 0x1f + +#define PCIE_MISC_PCIE_STATUS 0x4068 +#define PCIE_MISC_PCIE_STATUS_PCIE_PORT_MASK 0x80 +#define PCIE_MISC_PCIE_STATUS_PCIE_DL_ACTIVE_MASK 0x20 +#define PCIE_MISC_PCIE_STATUS_PCIE_PHYLINKUP_MASK 0x10 +#define PCIE_MISC_PCIE_STATUS_PCIE_LINK_IN_L23_MASK 0x40 + +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT 0x4070 +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT_LIMIT_MASK 0xfff00000 +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT_BASE_MASK 0xfff0 +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT_BASE_HI_SHIFT 12 +#define PCIE_MEM_WIN0_BASE_LIMIT(win) \ + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT + ((win) * 4) + +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_HI 0x4080 +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_HI_BASE_MASK 0xff +#define PCIE_MEM_WIN0_BASE_HI(win) \ + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_HI + ((win) * 8) + +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LIMIT_HI 0x4084 +#define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LIMIT_HI_LIMIT_MASK 0xff +#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_CLKREQ_DEBUG_ENABLE_MASK 0x2 +#define PCIE_MISC_HARD_PCIE_HARD_DEBUG_SERDES_IDDQ_MASK 0x08000000 + +#define PCIE_MSI_INTR2_CLR 0x4508 +#define PCIE_MSI_INTR2_MASK_SET 0x4510 + +#define PCIE_EXT_CFG_DATA 0x8000 + +#define PCIE_EXT_CFG_INDEX 0x9000 +#define PCIE_EXT_BUSNUM_SHIFT 20 +#define PCIE_EXT_SLOT_SHIFT 15 +#define PCIE_EXT_FUNC_SHIFT 12 + +#define PCIE_RGR1_SW_INIT_1 0x9210 +#define PCIE_RGR1_SW_INIT_1_PERST_MASK 0x1 +#define PCIE_RGR1_SW_INIT_1_INIT_MASK 0x2 + +/* PCIe parameters */ +#define BRCM_NUM_PCIE_OUT_WINS 0x4 + +/* MDIO registers */ +#define MDIO_PORT0 0x0 +#define MDIO_DATA_MASK 0x7fffffff +#define MDIO_PORT_MASK 0xf0000 +#define MDIO_REGAD_MASK 0xffff +#define MDIO_CMD_MASK 0xfff00000 +#define MDIO_CMD_READ 0x1 +#define MDIO_CMD_WRITE 0x0 +#define MDIO_DATA_DONE_MASK 0x80000000 +#define MDIO_RD_DONE(x) (((x) & MDIO_DATA_DONE_MASK) ? 1 : 0) +#define MDIO_WT_DONE(x) (((x) & MDIO_DATA_DONE_MASK) ? 0 : 1) +#define SSC_REGS_ADDR 0x1100 +#define SET_ADDR_OFFSET 0x1f +#define SSC_CNTL_OFFSET 0x2 +#define SSC_CNTL_OVRD_EN_MASK 0x8000 +#define SSC_CNTL_OVRD_VAL_MASK 0x4000 +#define SSC_STATUS_OFFSET 0x1 +#define SSC_STATUS_SSC_MASK 0x400 +#define SSC_STATUS_PLL_LOCK_MASK 0x800 + +struct brcm_pcie { + void __iomem *base; + + int gen; + bool ssc; +}; + +#define msleep(a) udelay((a) * 1000) + +/* + * This is to convert the size of the inbound "BAR" region to the + * non-linear values of PCIE_X_MISC_RC_BAR[123]_CONFIG_LO.SIZE + */ +static int brcm_pcie_encode_ibar_size(u64 size) +{ + int log2_in = ilog2(size); + + if (log2_in >= 12 && log2_in <= 15) + /* Covers 4KB to 32KB (inclusive) */ + return (log2_in - 12) + 0x1c; + else if (log2_in >= 16 && log2_in <= 37) + /* Covers 64KB to 32GB, (inclusive) */ + return log2_in - 15; + /* Something is awry so disable */ + return 0; +} + +/* Configuration space read/write support */ +static inline int brcm_pcie_cfg_index(pci_dev_t bdf, int reg) +{ + return (PCI_DEV(bdf) << PCIE_EXT_SLOT_SHIFT) + | (PCI_FUNC(bdf) << PCIE_EXT_FUNC_SHIFT) + | (PCI_BUS(bdf) << PCIE_EXT_BUSNUM_SHIFT) + | (reg & ~3); +} + +/* The controller is capable of serving in both RC and EP roles */ +static bool brcm_pcie_rc_mode(struct brcm_pcie *pcie) +{ + u32 val = readl(pcie->base + PCIE_MISC_PCIE_STATUS); + + return !!FIELD_GET(PCIE_MISC_PCIE_STATUS_PCIE_PORT_MASK, val); +} + +static bool brcm_pcie_link_up(struct brcm_pcie *pcie) +{ + u32 val = readl(pcie->base + PCIE_MISC_PCIE_STATUS); + u32 dla = FIELD_GET(PCIE_MISC_PCIE_STATUS_PCIE_DL_ACTIVE_MASK, val); + u32 plu = FIELD_GET(PCIE_MISC_PCIE_STATUS_PCIE_PHYLINKUP_MASK, val); + + return dla && plu; +} + +static int brcm_pcie_config_address(const struct udevice *udev, pci_dev_t bdf, + uint offset, void **paddress) +{ + struct brcm_pcie *pcie = dev_get_priv(udev); + unsigned int bus = PCI_BUS(bdf); + unsigned int dev = PCI_DEV(bdf); + int idx; + + /* + * Busses 0 (host PCIe bridge) and 1 (its immediate child) + * are limited to a single device each + */ + if ((bus == (udev->seq + 1)) && dev > 0) + return -ENODEV; + + /* Accesses to the RC go right to the RC registers if PCI device == 0 */ + if (bus == udev->seq) { + if (PCI_DEV(bdf)) + return -ENODEV; + + *paddress = pcie->base + offset; + return 0; + } + + /* For devices, write to the config space index register */ + idx = brcm_pcie_cfg_index(bdf, 0); + + writel(idx, pcie->base + PCIE_EXT_CFG_INDEX); + *paddress = pcie->base + PCIE_EXT_CFG_DATA + offset; + + return 0; +} + +static int brcm_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, brcm_pcie_config_address, + bdf, offset, valuep, size); +} + +static int brcm_pcie_write_config(struct udevice *bus, pci_dev_t bdf, + uint offset, ulong value, + enum pci_size_t size) +{ + return pci_generic_mmap_write_config(bus, brcm_pcie_config_address, + bdf, offset, value, size); +} + +static const char *link_speed_to_str(unsigned int s) +{ + static const char * const speed_str[] = { "??", "2.5", "5.0", "8.0" }; + + if (s >= ARRAY_SIZE(speed_str)) + s = 0; + + return speed_str[s]; +} + +static inline void brcm_pcie_bridge_sw_init_set(struct brcm_pcie *pcie, u32 val) +{ + u32 tmp; + + tmp = readl(pcie->base + PCIE_RGR1_SW_INIT_1); + u32p_replace_bits(&tmp, val, PCIE_RGR1_SW_INIT_1_INIT_MASK); + writel(tmp, pcie->base + PCIE_RGR1_SW_INIT_1); +} + +static inline void brcm_pcie_perst_set(struct brcm_pcie *pcie, u32 val) +{ + u32 tmp; + + tmp = readl(pcie->base + PCIE_RGR1_SW_INIT_1); + u32p_replace_bits(&tmp, val, PCIE_RGR1_SW_INIT_1_PERST_MASK); + writel(tmp, pcie->base + PCIE_RGR1_SW_INIT_1); +} + +static u32 brcm_pcie_mdio_form_pkt(int port, int regad, int cmd) +{ + u32 pkt = 0; + + pkt |= FIELD_PREP(MDIO_PORT_MASK, port); + pkt |= FIELD_PREP(MDIO_REGAD_MASK, regad); + pkt |= FIELD_PREP(MDIO_CMD_MASK, cmd); + + return pkt; +} + +/* Negative return value indicates error */ +static int brcm_pcie_mdio_read(void __iomem *base, u8 port, u8 regad, u32 *val) +{ + int tries; + u32 data; + + writel(brcm_pcie_mdio_form_pkt(port, regad, MDIO_CMD_READ), + base + PCIE_RC_DL_MDIO_ADDR); + readl(base + PCIE_RC_DL_MDIO_ADDR); + + data = readl(base + PCIE_RC_DL_MDIO_RD_DATA); + for (tries = 0; !MDIO_RD_DONE(data) && tries < 10; tries++) { + udelay(10); + data = readl(base + PCIE_RC_DL_MDIO_RD_DATA); + } + + *val = FIELD_GET(MDIO_DATA_MASK, data); + return MDIO_RD_DONE(data) ? 0 : -EIO; +} + +/* Negative return value indicates error */ +static int brcm_pcie_mdio_write(void __iomem *base, u8 port, + u8 regad, u16 wrdata) +{ + int tries; + u32 data; + + writel(brcm_pcie_mdio_form_pkt(port, regad, MDIO_CMD_WRITE), + base + PCIE_RC_DL_MDIO_ADDR); + readl(base + PCIE_RC_DL_MDIO_ADDR); + writel(MDIO_DATA_DONE_MASK | wrdata, base + PCIE_RC_DL_MDIO_WR_DATA); + + data = readl(base + PCIE_RC_DL_MDIO_WR_DATA); + for (tries = 0; !MDIO_WT_DONE(data) && tries < 10; tries++) { + udelay(10); + data = readl(base + PCIE_RC_DL_MDIO_WR_DATA); + } + + return MDIO_WT_DONE(data) ? 0 : -EIO; +} + +/* + * Configures device for Spread Spectrum Clocking (SSC) mode; negative + * return value indicates error. + */ +static int brcm_pcie_set_ssc(struct brcm_pcie *pcie) +{ + void __iomem *base = pcie->base; + int pll, ssc; + int ret; + u32 tmp; + + ret = brcm_pcie_mdio_write(base, MDIO_PORT0, SET_ADDR_OFFSET, + SSC_REGS_ADDR); + if (ret < 0) + return ret; + + ret = brcm_pcie_mdio_read(base, MDIO_PORT0, SSC_CNTL_OFFSET, &tmp); + if (ret < 0) + return ret; + + u32p_replace_bits(&tmp, 1, SSC_CNTL_OVRD_EN_MASK); + u32p_replace_bits(&tmp, 1, SSC_CNTL_OVRD_VAL_MASK); + ret = brcm_pcie_mdio_write(base, MDIO_PORT0, SSC_CNTL_OFFSET, tmp); + if (ret < 0) + return ret; + + udelay(1000); + ret = brcm_pcie_mdio_read(base, MDIO_PORT0, SSC_STATUS_OFFSET, &tmp); + if (ret < 0) + return ret; + + ssc = FIELD_GET(SSC_STATUS_SSC_MASK, tmp); + pll = FIELD_GET(SSC_STATUS_PLL_LOCK_MASK, tmp); + + return ssc && pll ? 0 : -EIO; +} + +/* Limits operation to a specific generation (1, 2, or 3) */ +static void brcm_pcie_set_gen(struct brcm_pcie *pcie, int gen) +{ + void __iomem *base = pcie->base; + + u16 lnkctl2 = readw(base + BRCM_PCIE_CAP_REGS + PCI_EXP_LNKCTL2); + u32 lnkcap = readl(base + BRCM_PCIE_CAP_REGS + PCI_EXP_LNKCAP); + + lnkcap = (lnkcap & ~PCI_EXP_LNKCAP_SLS) | gen; + writel(lnkcap, base + BRCM_PCIE_CAP_REGS + PCI_EXP_LNKCAP); + + lnkctl2 = (lnkctl2 & ~0xf) | gen; + writew(lnkctl2, base + BRCM_PCIE_CAP_REGS + PCI_EXP_LNKCTL2); +} + +static void brcm_pcie_set_outbound_win(struct brcm_pcie *pcie, + unsigned int win, u64 phys_addr, + u64 pcie_addr, u64 size) +{ + void __iomem *base = pcie->base; + u32 phys_addr_mb_high, limit_addr_mb_high; + phys_addr_t phys_addr_mb, limit_addr_mb; + int high_addr_shift; + u32 tmp; + + /* Set the base of the pcie_addr window */ + writel(lower_32_bits(pcie_addr), base + PCIE_MEM_WIN0_LO(win)); + writel(upper_32_bits(pcie_addr), base + PCIE_MEM_WIN0_HI(win)); + + /* Write the addr base & limit lower bits (in MBs) */ + phys_addr_mb = phys_addr / SZ_1M; + limit_addr_mb = (phys_addr + size - 1) / SZ_1M; + + tmp = readl(base + PCIE_MEM_WIN0_BASE_LIMIT(win)); + u32p_replace_bits(&tmp, phys_addr_mb, + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT_BASE_MASK); + u32p_replace_bits(&tmp, limit_addr_mb, + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT_LIMIT_MASK); + writel(tmp, base + PCIE_MEM_WIN0_BASE_LIMIT(win)); + + /* Write the cpu & limit addr upper bits */ + high_addr_shift = PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT_BASE_HI_SHIFT; + phys_addr_mb_high = phys_addr_mb >> high_addr_shift; + tmp = readl(base + PCIE_MEM_WIN0_BASE_HI(win)); + u32p_replace_bits(&tmp, phys_addr_mb_high, + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_HI_BASE_MASK); + writel(tmp, base + PCIE_MEM_WIN0_BASE_HI(win)); + + limit_addr_mb_high = limit_addr_mb >> high_addr_shift; + tmp = readl(base + PCIE_MEM_WIN0_LIMIT_HI(win)); + u32p_replace_bits(&tmp, limit_addr_mb_high, + PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LIMIT_HI_LIMIT_MASK); + writel(tmp, base + PCIE_MEM_WIN0_LIMIT_HI(win)); +} + +static int brcm_pcie_probe(struct udevice *dev) +{ + struct udevice *ctlr = pci_get_controller(dev); + struct pci_controller *hose = dev_get_uclass_priv(ctlr); + struct brcm_pcie *pcie = dev_get_priv(dev); + void __iomem *base = pcie->base; + 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 */ + brcm_pcie_bridge_sw_init_set(pcie, 1); + + udelay(150); + + /* Take the bridge out of reset */ + brcm_pcie_bridge_sw_init_set(pcie, 0); + + tmp = readl(base + PCIE_MISC_HARD_PCIE_HARD_DEBUG); + tmp &= ~PCIE_MISC_HARD_PCIE_HARD_DEBUG_SERDES_IDDQ_MASK; + writel(tmp, base + PCIE_MISC_HARD_PCIE_HARD_DEBUG); + /* Wait for SerDes to be stable */ + udelay(150); + + /* Set SCB_MAX_BURST_SIZE, CFG_READ_UR_MODE, SCB_ACCESS_EN */ + u32p_replace_bits(&tmp, 1, PCIE_MISC_MISC_CTRL_SCB_ACCESS_EN_MASK); + u32p_replace_bits(&tmp, 1, PCIE_MISC_MISC_CTRL_CFG_READ_UR_MODE_MASK); + u32p_replace_bits(&tmp, PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_128, + PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_MASK); + writel(tmp, base + PCIE_MISC_MISC_CTRL); + + /* + * TODO: When support for other SoCs than BCM2711 is added we may + * need to use the base address and size(s) provided in the dma-ranges + * property. + */ + rc_bar2_offset = 0; + rc_bar2_size = 0xc0000000; + + tmp = lower_32_bits(rc_bar2_offset); + u32p_replace_bits(&tmp, brcm_pcie_encode_ibar_size(rc_bar2_size), + PCIE_MISC_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, + PCIE_MISC_MISC_CTRL_SCB0_SIZE_MASK); + writel(tmp, base + PCIE_MISC_MISC_CTRL); + + /* Disable the PCIe->GISB memory window (RC_BAR1) */ + tmp = readl(base + PCIE_MISC_RC_BAR1_CONFIG_LO); + tmp &= ~PCIE_MISC_RC_BAR1_CONFIG_LO_SIZE_MASK; + writel(tmp, base + PCIE_MISC_RC_BAR1_CONFIG_LO); + + /* Disable the PCIe->SCB memory window (RC_BAR3) */ + tmp = readl(base + PCIE_MISC_RC_BAR3_CONFIG_LO); + tmp &= ~PCIE_MISC_RC_BAR3_CONFIG_LO_SIZE_MASK; + writel(tmp, base + PCIE_MISC_RC_BAR3_CONFIG_LO); + + /* Mask all interrupts since we are not handling any yet */ + writel(0xffffffff, base + PCIE_MSI_INTR2_MASK_SET); + + /* Clear any interrupts we find on boot */ + writel(0xffffffff, base + PCIE_MSI_INTR2_CLR); + + if (pcie->gen) + brcm_pcie_set_gen(pcie, pcie->gen); + + /* Unassert the fundamental reset */ + brcm_pcie_perst_set(pcie, 0); + + /* Give the RC/EP time to wake up, before trying to configure RC. + * Intermittently check status for link-up, up to a total of 100ms. + */ + for (i = 0; i < 100 && !brcm_pcie_link_up(pcie); i += 5) + msleep(5); + + if (!brcm_pcie_link_up(pcie)) { + printf("PCIe BRCM: link down\n"); + return -ENODEV; + } + + if (!brcm_pcie_rc_mode(pcie)) { + printf("PCIe misconfigured; is in EP mode\n"); + return -EINVAL; + } + + for (i = 0; i < hose->region_count; i++) { + struct pci_region *reg = &hose->regions[i]; + + if (reg->flags != PCI_REGION_MEM) + continue; + + if (num_out_wins >= BRCM_NUM_PCIE_OUT_WINS) + return -EINVAL; + + brcm_pcie_set_outbound_win(pcie, num_out_wins, reg->phys_start, + reg->bus_start, reg->size); + + num_out_wins++; + } + + /* + * For config space accesses on the RC, show the right class for + * a PCIe-PCIe bridge (the default setting is to be EP mode). + */ + tmp = readl(base + PCIE_RC_CFG_PRIV1_ID_VAL3); + u32p_replace_bits(&tmp, 0x060400, + PCIE_RC_CFG_PRIV1_ID_VAL3_CLASS_CODE_MASK); + writel(tmp, base + PCIE_RC_CFG_PRIV1_ID_VAL3); + + if (pcie->ssc) { + ret = brcm_pcie_set_ssc(pcie); + if (ret == 0) + ssc_good = true; + else + printf("PCIe BRCM: failed attempt to enter SSC mode\n"); + } + + lnksta = readw(base + BRCM_PCIE_CAP_REGS + PCI_EXP_LNKSTA); + cls = FIELD_GET(PCI_EXP_LNKSTA_CLS, lnksta); + nlw = FIELD_GET(PCI_EXP_LNKSTA_NLW, lnksta); + + printf("PCIe BRCM: link up, %s Gbps x%u %s\n", link_speed_to_str(cls), + nlw, ssc_good ? "(SSC)" : "(!SSC)"); + + /* PCIe->SCB endian mode for BAR */ + tmp = readl(base + PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1); + u32p_replace_bits(&tmp, PCIE_RC_CFG_VENDOR_SPCIFIC_REG1_LITTLE_ENDIAN, + PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1_ENDIAN_MODE_BAR2_MASK); + writel(tmp, base + PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1); + + /* + * Refclk from RC should be gated with CLKREQ# input when ASPM L0s,L1 + * is enabled => setting the CLKREQ_DEBUG_ENABLE field to 1. + */ + tmp = readl(base + PCIE_MISC_HARD_PCIE_HARD_DEBUG); + tmp |= PCIE_MISC_HARD_PCIE_HARD_DEBUG_CLKREQ_DEBUG_ENABLE_MASK; + writel(tmp, base + PCIE_MISC_HARD_PCIE_HARD_DEBUG); + + return 0; +} + +static int brcm_pcie_ofdata_to_platdata(struct udevice *dev) +{ + struct brcm_pcie *pcie = dev_get_priv(dev); + ofnode dn = dev_ofnode(dev); + u32 max_link_speed; + int ret; + + /* 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"); + + ret = ofnode_read_u32(dn, "max-link-speed", &max_link_speed); + if (ret < 0 || max_link_speed > 4) + pcie->gen = 0; + else + pcie->gen = max_link_speed; + + return 0; +} + +static const struct dm_pci_ops brcm_pcie_ops = { + .read_config = brcm_pcie_read_config, + .write_config = brcm_pcie_write_config, +}; + +static const struct udevice_id brcm_pcie_ids[] = { + { .compatible = "brcm,bcm2711-pcie" }, + { } +}; + +U_BOOT_DRIVER(pcie_brcm_base) = { + .name = "pcie_brcm", + .id = UCLASS_PCI, + .ops = &brcm_pcie_ops, + .of_match = brcm_pcie_ids, + .probe = brcm_pcie_probe, + .ofdata_to_platdata = brcm_pcie_ofdata_to_platdata, + .priv_auto_alloc_size = sizeof(struct brcm_pcie), +};