// 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), };