diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index c20a0cc..f59803d 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -105,4 +105,11 @@ config PCIE_LAYERSCAPE PCIe controllers. The PCIe may works in RC or EP mode according to RCW[HOST_AGT_PEX] setting. +config PCIE_INTEL_FPGA + bool "Intel FPGA PCIe support" + depends on DM_PCI + help + Say Y here if you want to enable PCIe controller support on Intel + FPGA, example Stratix 10. + endif diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index 72c09f4a..4923641 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -33,3 +33,4 @@ obj-$(CONFIG_PCIE_DW_MVEBU) += pcie_dw_mvebu.o obj-$(CONFIG_PCIE_LAYERSCAPE) += pcie_layerscape.o obj-$(CONFIG_PCIE_LAYERSCAPE) += pcie_layerscape_fixup.o obj-$(CONFIG_PCI_XILINX) += pcie_xilinx.o +obj-$(CONFIG_PCIE_INTEL_FPGA) += pcie_intel_fpga.o diff --git a/drivers/pci/pcie_intel_fpga.c b/drivers/pci/pcie_intel_fpga.c new file mode 100644 index 0000000..3cdf05b --- /dev/null +++ b/drivers/pci/pcie_intel_fpga.c @@ -0,0 +1,430 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel FPGA PCIe host controller driver + * + * Copyright (C) 2013-2018 Intel Corporation. All rights reserved + * + */ + +#include +#include +#include +#include + +#define RP_TX_REG0 0x2000 +#define RP_TX_CNTRL 0x2004 +#define RP_TX_SOP BIT(0) +#define RP_TX_EOP BIT(1) +#define RP_RXCPL_STATUS 0x200C +#define RP_RXCPL_SOP BIT(0) +#define RP_RXCPL_EOP BIT(1) +#define RP_RXCPL_REG 0x2008 +#define P2A_INT_STATUS 0x3060 +#define P2A_INT_STS_ALL 0xf +#define P2A_INT_ENABLE 0x3070 +#define RP_CAP_OFFSET 0x70 + +/* TLP configuration type 0 and 1 */ +#define TLP_FMTTYPE_CFGRD0 0x04 /* Configuration Read Type 0 */ +#define TLP_FMTTYPE_CFGWR0 0x44 /* Configuration Write Type 0 */ +#define TLP_FMTTYPE_CFGRD1 0x05 /* Configuration Read Type 1 */ +#define TLP_FMTTYPE_CFGWR1 0x45 /* Configuration Write Type 1 */ +#define TLP_PAYLOAD_SIZE 0x01 +#define TLP_READ_TAG 0x1d +#define TLP_WRITE_TAG 0x10 +#define RP_DEVFN 0 + +#define RP_CFG_ADDR(pcie, reg) \ + ((pcie->hip_base) + (reg) + (1 << 20)) +#define TLP_REQ_ID(bus, devfn) (((bus) << 8) | (devfn)) + +#define TLP_CFGRD_DW0(pcie, bus) \ + ((((bus != pcie->first_busno) ? TLP_FMTTYPE_CFGRD0 \ + : TLP_FMTTYPE_CFGRD1) << 24) | \ + TLP_PAYLOAD_SIZE) + +#define TLP_CFGWR_DW0(pcie, bus) \ + ((((bus != pcie->first_busno) ? TLP_FMTTYPE_CFGWR0 \ + : TLP_FMTTYPE_CFGWR1) << 24) | \ + TLP_PAYLOAD_SIZE) + +#define TLP_CFG_DW1(pcie, tag, be) \ + (((TLP_REQ_ID(pcie->first_busno, RP_DEVFN)) << 16) | (tag << 8) | (be)) +#define TLP_CFG_DW2(bus, dev, fn, offset) \ + (((bus) << 24) | ((dev) << 19) | ((fn) << 16) | (offset)) + +#define TLP_COMP_STATUS(s) (((s) >> 13) & 7) +#define TLP_BYTE_COUNT(s) (((s) >> 0) & 0xfff) +#define TLP_HDR_SIZE 3 +#define TLP_LOOP 500 +#define DWORD_MASK 3 + +#define IS_ROOT_PORT(pcie, bdf) \ + ((PCI_BUS(bdf) == pcie->first_busno) ? true : false) + +#define PCI_EXP_LNKSTA 18 /* Link Status */ +#define PCI_EXP_LNKSTA_DLLLA 0x2000 /* Data Link Layer Link Active */ + +/** + * struct intel_fpga_pcie - Intel FPGA PCIe controller state + * @bus: Pointer to the PCI bus + * @cra_base: The base address of CRA register space + * @hip_base: The base address of Rootport configuration space + * @first_busno: This driver supports multiple PCIe controllers. + * first_busno stores the bus number of the PCIe root-port + * number which may vary depending on the PCIe setup. + */ +struct intel_fpga_pcie { + struct udevice *bus; + void __iomem *cra_base; + void __iomem *hip_base; + int first_busno; +}; + +/** + * Intel FPGA PCIe port uses BAR0 of RC's configuration space as the + * translation from PCI bus to native BUS. Entire DDR region is mapped + * into PCIe space using these registers, so it can be reached by DMA from + * EP devices. + * The BAR0 of bridge should be hidden during enumeration to avoid the + * sizing and resource allocation by PCIe core. + */ +static bool intel_fpga_pcie_hide_rc_bar(struct intel_fpga_pcie *pcie, + pci_dev_t bdf, int offset) +{ + if (IS_ROOT_PORT(pcie, bdf) && PCI_DEV(bdf) == 0 && + PCI_FUNC(bdf) == 0 && offset == PCI_BASE_ADDRESS_0) + return true; + + return false; +} + +static inline void cra_writel(struct intel_fpga_pcie *pcie, const u32 value, + const u32 reg) +{ + writel(value, pcie->cra_base + reg); +} + +static inline u32 cra_readl(struct intel_fpga_pcie *pcie, const u32 reg) +{ + return readl(pcie->cra_base + reg); +} + +static bool intel_fpga_pcie_link_up(struct intel_fpga_pcie *pcie) +{ + return !!(readw(RP_CFG_ADDR(pcie, RP_CAP_OFFSET + PCI_EXP_LNKSTA)) + & PCI_EXP_LNKSTA_DLLLA); +} + +static bool intel_fpga_pcie_addr_valid(struct intel_fpga_pcie *pcie, + pci_dev_t bdf) +{ + /* If there is no link, then there is no device */ + if (!IS_ROOT_PORT(pcie, bdf) && !intel_fpga_pcie_link_up(pcie)) + return false; + + /* access only one slot on each root port */ + if (IS_ROOT_PORT(pcie, bdf) && PCI_DEV(bdf) > 0) + return false; + + if ((PCI_BUS(bdf) == pcie->first_busno + 1) && PCI_DEV(bdf) > 0) + return false; + + return true; +} + +static void tlp_write_tx(struct intel_fpga_pcie *pcie, u32 reg0, u32 ctrl) +{ + cra_writel(pcie, reg0, RP_TX_REG0); + cra_writel(pcie, ctrl, RP_TX_CNTRL); +} + +static int tlp_read_packet(struct intel_fpga_pcie *pcie, u32 *value) +{ + int i; + u32 ctrl; + u32 comp_status; + u32 dw[4]; + u32 count = 0; + + for (i = 0; i < TLP_LOOP; i++) { + ctrl = cra_readl(pcie, RP_RXCPL_STATUS); + if (!(ctrl & RP_RXCPL_SOP)) + continue; + + /* read first DW */ + dw[count++] = cra_readl(pcie, RP_RXCPL_REG); + + /* Poll for EOP */ + for (i = 0; i < TLP_LOOP; i++) { + ctrl = cra_readl(pcie, RP_RXCPL_STATUS); + dw[count++] = cra_readl(pcie, RP_RXCPL_REG); + if (ctrl & RP_RXCPL_EOP) { + comp_status = TLP_COMP_STATUS(dw[1]); + if (comp_status) + return -EFAULT; + + if (value && + TLP_BYTE_COUNT(dw[1]) == sizeof(u32) && + count >= 3) + *value = dw[3]; + + return 0; + } + } + + udelay(5); + } + + dev_err(pcie->dev, "read TLP packet timed out\n"); + return -ENODEV; +} + +static void tlp_write_packet(struct intel_fpga_pcie *pcie, u32 *headers, + u32 data) +{ + tlp_write_tx(pcie, headers[0], RP_TX_SOP); + + tlp_write_tx(pcie, headers[1], 0); + + tlp_write_tx(pcie, headers[2], 0); + + tlp_write_tx(pcie, data, RP_TX_EOP); +} + +static int tlp_cfg_dword_read(struct intel_fpga_pcie *pcie, pci_dev_t bdf, + int offset, u8 byte_en, u32 *value) +{ + u32 headers[TLP_HDR_SIZE]; + u8 busno = PCI_BUS(bdf); + + headers[0] = TLP_CFGRD_DW0(pcie, busno); + headers[1] = TLP_CFG_DW1(pcie, TLP_READ_TAG, byte_en); + headers[2] = TLP_CFG_DW2(busno, PCI_DEV(bdf), PCI_FUNC(bdf), offset); + + tlp_write_packet(pcie, headers, 0); + + return tlp_read_packet(pcie, value); +} + +static int tlp_cfg_dword_write(struct intel_fpga_pcie *pcie, pci_dev_t bdf, + int offset, u8 byte_en, u32 value) +{ + u32 headers[TLP_HDR_SIZE]; + u8 busno = PCI_BUS(bdf); + + headers[0] = TLP_CFGWR_DW0(pcie, busno); + headers[1] = TLP_CFG_DW1(pcie, TLP_WRITE_TAG, byte_en); + headers[2] = TLP_CFG_DW2(busno, PCI_DEV(bdf), PCI_FUNC(bdf), offset); + + tlp_write_packet(pcie, headers, value); + + return tlp_read_packet(pcie, NULL); +} + +int intel_fpga_rp_conf_addr(struct udevice *bus, pci_dev_t bdf, + uint offset, void **paddress) +{ + struct intel_fpga_pcie *pcie = dev_get_priv(bus); + + *paddress = RP_CFG_ADDR(pcie, offset); + + return 0; +} + +static int intel_fpga_pcie_rp_rd_conf(struct udevice *bus, pci_dev_t bdf, + uint offset, ulong *valuep, + enum pci_size_t size) +{ + return pci_generic_mmap_read_config(bus, intel_fpga_rp_conf_addr, + bdf, offset, valuep, size); +} + +static int intel_fpga_pcie_rp_wr_conf(struct udevice *bus, pci_dev_t bdf, + uint offset, ulong value, + enum pci_size_t size) +{ + int ret; + struct intel_fpga_pcie *pcie = dev_get_priv(bus); + + ret = pci_generic_mmap_write_config(bus, intel_fpga_rp_conf_addr, + bdf, offset, value, size); + if (!ret) { + /* Monitor changes to PCI_PRIMARY_BUS register on root port + * and update local copy of root bus number accordingly. + */ + if (offset == PCI_PRIMARY_BUS) + pcie->first_busno = (u8)(value); + } + + return ret; +} + +static u8 pcie_get_byte_en(uint offset, enum pci_size_t size) +{ + switch (size) { + case PCI_SIZE_8: + return 1 << (offset & 3); + case PCI_SIZE_16: + return 3 << (offset & 3); + default: + return 0xf; + } +} + +static int _pcie_intel_fpga_read_config(struct intel_fpga_pcie *pcie, + pci_dev_t bdf, uint offset, + ulong *valuep, enum pci_size_t size) +{ + int ret; + u32 data; + u8 byte_en; + + /* Uses memory mapped method to read rootport config registers */ + if (IS_ROOT_PORT(pcie, bdf)) + return intel_fpga_pcie_rp_rd_conf(pcie->bus, bdf, + offset, valuep, size); + + byte_en = pcie_get_byte_en(offset, size); + ret = tlp_cfg_dword_read(pcie, bdf, offset & ~DWORD_MASK, + byte_en, &data); + if (ret) + return ret; + + dev_dbg(pcie->dev, "(addr,size,val)=(0x%04x, %d, 0x%08x)\n", + offset, size, data); + *valuep = pci_conv_32_to_size(data, offset, size); + + return 0; +} + +static int _pcie_intel_fpga_write_config(struct intel_fpga_pcie *pcie, + pci_dev_t bdf, uint offset, + ulong value, enum pci_size_t size) +{ + u32 data; + u8 byte_en; + + dev_dbg(pcie->dev, "PCIE CFG write: (b.d.f)=(%02d.%02d.%02d)\n", + PCI_BUS(bdf), PCI_DEV(bdf), PCI_FUNC(bdf)); + dev_dbg(pcie->dev, "(addr,size,val)=(0x%04x, %d, 0x%08lx)\n", + offset, size, value); + + /* Uses memory mapped method to read rootport config registers */ + if (IS_ROOT_PORT(pcie, bdf)) + return intel_fpga_pcie_rp_wr_conf(pcie->bus, bdf, offset, + value, size); + + byte_en = pcie_get_byte_en(offset, size); + data = pci_conv_size_to_32(0, value, offset, size); + + return tlp_cfg_dword_write(pcie, bdf, offset & ~DWORD_MASK, + byte_en, data); +} + +static int pcie_intel_fpga_read_config(struct udevice *bus, pci_dev_t bdf, + uint offset, ulong *valuep, + enum pci_size_t size) +{ + struct intel_fpga_pcie *pcie = dev_get_priv(bus); + + dev_dbg(pcie->dev, "PCIE CFG read: (b.d.f)=(%02d.%02d.%02d)\n", + PCI_BUS(bdf), PCI_DEV(bdf), PCI_FUNC(bdf)); + + if (intel_fpga_pcie_hide_rc_bar(pcie, bdf, offset)) { + *valuep = (u32)pci_get_ff(size); + return 0; + } + + if (!intel_fpga_pcie_addr_valid(pcie, bdf)) { + *valuep = (u32)pci_get_ff(size); + return 0; + } + + return _pcie_intel_fpga_read_config(pcie, bdf, offset, valuep, size); +} + +static int pcie_intel_fpga_write_config(struct udevice *bus, pci_dev_t bdf, + uint offset, ulong value, + enum pci_size_t size) +{ + struct intel_fpga_pcie *pcie = dev_get_priv(bus); + + if (intel_fpga_pcie_hide_rc_bar(pcie, bdf, offset)) + return 0; + + if (!intel_fpga_pcie_addr_valid(pcie, bdf)) + return 0; + + return _pcie_intel_fpga_write_config(pcie, bdf, offset, value, + size); +} + +static int pcie_intel_fpga_probe(struct udevice *dev) +{ + struct intel_fpga_pcie *pcie = dev_get_priv(dev); + + pcie->bus = pci_get_controller(dev); + pcie->first_busno = dev->seq; + + /* clear all interrupts */ + cra_writel(pcie, P2A_INT_STS_ALL, P2A_INT_STATUS); + /* disable all interrupts */ + cra_writel(pcie, 0, P2A_INT_ENABLE); + + return 0; +} + +static int pcie_intel_fpga_ofdata_to_platdata(struct udevice *dev) +{ + struct intel_fpga_pcie *pcie = dev_get_priv(dev); + struct fdt_resource reg_res; + int node = dev_of_offset(dev); + int ret; + + DECLARE_GLOBAL_DATA_PTR; + + ret = fdt_get_named_resource(gd->fdt_blob, node, "reg", "reg-names", + "Cra", ®_res); + if (ret) { + dev_err(dev, "resource \"Cra\" not found\n"); + return ret; + } + + pcie->cra_base = map_physmem(reg_res.start, + fdt_resource_size(®_res), + MAP_NOCACHE); + + ret = fdt_get_named_resource(gd->fdt_blob, node, "reg", "reg-names", + "Hip", ®_res); + if (ret) { + dev_err(dev, "resource \"Hip\" not found\n"); + return ret; + } + + pcie->hip_base = map_physmem(reg_res.start, + fdt_resource_size(®_res), + MAP_NOCACHE); + + return 0; +} + +static const struct dm_pci_ops pcie_intel_fpga_ops = { + .read_config = pcie_intel_fpga_read_config, + .write_config = pcie_intel_fpga_write_config, +}; + +static const struct udevice_id pcie_intel_fpga_ids[] = { + { .compatible = "altr,pcie-root-port-2.0" }, + {}, +}; + +U_BOOT_DRIVER(pcie_intel_fpga) = { + .name = "pcie_intel_fpga", + .id = UCLASS_PCI, + .of_match = pcie_intel_fpga_ids, + .ops = &pcie_intel_fpga_ops, + .ofdata_to_platdata = pcie_intel_fpga_ofdata_to_platdata, + .probe = pcie_intel_fpga_probe, + .priv_auto_alloc_size = sizeof(struct intel_fpga_pcie), +};