Here’s the driver, if you want to try it out. You have to add it to makefile and Kconfig. Look at the intel 10 series driver entries for examples.
// SPDX-License-Identifier: GPL-2.0
/*
* Altera FPGA PCIe host controller driver
*
* Portions Copyright (C) 2013-2018 Intel Corporation. All rights reserved
* Portions Copyright Altera Corporation (C) 2013-2015. All rights reserved
*
* Portions written by: Ley Foon Tan <lftan@altera.com>
*
*/
//#define DEBUG
//#undef CONFIG_LOGLEVEL
//#define CONFIG_LOGLEVEL 8
#include <common.h>
#include <dm.h>
#include <pci.h>
#include <asm/io.h>
#include <dm/device_compat.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#define RP_TX_REG0 0x2000
#define RP_TX_REG1 0x2004
#define RP_TX_CNTRL 0x2008
#define RP_TX_SOP BIT(0)
#define RP_TX_EOP BIT(1)
#define RP_RXCPL_STATUS 0x2010
#define RP_RXCPL_SOP BIT(0)
#define RP_RXCPL_EOP BIT(1)
#define RP_RXCPL_REG0 0x2014
#define RP_RXCPL_REG1 0x2018
#define P2A_INT_STATUS 0x3060
#define P2A_INT_STS_ALL 0xf
#define P2A_INT_ENABLE 0x3070
#define RP_LTSSM 0x3c64
#define RP_LTSSM_MASK 0x1f
#define LTSSM_L0 0xf
/* 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 TLP_REQ_ID(bus, devfn) (((bus) << 8) | (devfn))
#define TLP_CFGRD_DW0(pcie, bus) \
((((bus > pcie->first_busno) ? TLP_FMTTYPE_CFGRD1 \
: TLP_FMTTYPE_CFGRD0) << 24) | \
TLP_PAYLOAD_SIZE)
#define TLP_CFGWR_DW0(pcie, bus) \
((((bus > pcie->first_busno) ? TLP_FMTTYPE_CFGWR1 \
: TLP_FMTTYPE_CFGWR0) << 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 20000
#define DWORD_MASK 3
#define IS_ROOT_PORT(pcie, bdf) \
((PCI_BUS(bdf) == pcie->first_busno) ? true : false)
/**
* struct altera_fpga_pcie - Altera FPGA PCIe controller state
* @bus: Pointer to the PCI bus
* @cra_base: The base address of CRA register space
* @first_busno: This driver will not work with
* multiple PCIE controllers.
*/
struct altera_fpga_pcie {
struct udevice *bus;
void __iomem *cra_base;
int first_busno;
};
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;
}
}
/**
* Altera 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 altera_fpga_pcie_hide_rc_bar(struct altera_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 altera_fpga_pcie *pcie, const u32 value,
const u32 reg)
{
writel(value, pcie->cra_base + reg);
}
static inline u32 cra_readl(struct altera_fpga_pcie *pcie, const u32 reg)
{
return readl(pcie->cra_base + reg);
}
static bool altera_fpga_pcie_link_up(struct altera_fpga_pcie *pcie)
{
return !!((cra_readl(pcie, RP_LTSSM) & RP_LTSSM_MASK) == LTSSM_L0);
}
static bool altera_pcie_valid_device(struct altera_fpga_pcie *pcie,
pci_dev_t bdf)
{
if (!IS_ROOT_PORT(pcie, bdf) && !altera_fpga_pcie_link_up(pcie)) {
return false;
}
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 altera_fpga_pcie *pcie, u32 reg0, u32 reg1, u32 ctrl)
{
cra_writel(pcie, reg0, RP_TX_REG0);
cra_writel(pcie, reg1, RP_TX_REG1);
cra_writel(pcie, ctrl, RP_TX_CNTRL);
}
static int tlp_read_packet(struct altera_fpga_pcie *pcie, u32 *value)
{
int i;
bool sop = false;
u32 ctrl;
u32 reg0, reg1;
u32 comp_status = 1;
/*
* Minimum 2 loops to read TLP headers and 1 loop to read data
* payload.
*/
for (i = 0; i < TLP_LOOP; i++) {
ctrl = cra_readl(pcie, RP_RXCPL_STATUS);
if ((ctrl & RP_RXCPL_SOP) || (ctrl & RP_RXCPL_EOP) || sop) {
reg0 = cra_readl(pcie, RP_RXCPL_REG0);
reg1 = cra_readl(pcie, RP_RXCPL_REG1);
if (ctrl & RP_RXCPL_SOP) {
sop = true;
comp_status = TLP_COMP_STATUS(reg1);
}
if (ctrl & RP_RXCPL_EOP) {
if (comp_status)
return -ENODEV;
if (value)
*value = reg0;
return 0;
}
}
udelay(5);
}
dev_err(pcie->dev, "read TLP packet timed out\n");
return -ENODEV;
}
static void tlp_write_packet(struct altera_fpga_pcie *pcie, u32 *headers,
u32 data)
{
tlp_write_tx(pcie, headers[0], headers[1], RP_TX_SOP);
tlp_write_tx(pcie, headers[2], data, RP_TX_EOP);
}
static int tlp_cfg_dword_read(struct altera_fpga_pcie *pcie, pci_dev_t bdf,
int offset, u8 byte_en, u32 *value)
{
u32 headers[TLP_HDR_SIZE];
u8 busno = PCI_BUS(bdf);
dev_dbg(pcie->dev, "entering tlp_cfg_dword_read, busno = %d\n", busno);
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 altera_fpga_pcie *pcie, pci_dev_t bdf,
int offset, u8 byte_en, u32 value)
{
u32 headers[TLP_HDR_SIZE];
u8 busno = PCI_BUS(bdf);
dev_dbg(pcie->dev, "entering tlp_cfg_dword_write, busno = %d\n", busno);
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);
}
static int _pcie_altera_fpga_read_config(struct altera_fpga_pcie *pcie,
pci_dev_t bdf, uint offset,
ulong *valuep, enum pci_size_t size)
{
int ret;
u32 data;
u8 byte_en;
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_altera_fpga_read_config(const struct udevice *bus, pci_dev_t bdf,
uint offset, ulong *valuep,
enum pci_size_t size)
{
struct altera_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 (altera_fpga_pcie_hide_rc_bar(pcie, bdf, offset)) {
*valuep = (u32)pci_get_ff(size);
return 0;
}
if (!altera_pcie_valid_device(pcie, bdf)) {
*valuep = (u32)pci_get_ff(size);
return 0;
}
return _pcie_altera_fpga_read_config(pcie, bdf, offset, valuep, size);
}
static int _pcie_altera_fpga_write_config(struct altera_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, "Write(addr,size,val)=(0x%04x, %d, 0x%08lx)\n",
offset, size, value);
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_altera_fpga_write_config(struct udevice *bus, pci_dev_t bdf,
uint offset, ulong value,
enum pci_size_t size)
{
struct altera_fpga_pcie *pcie = dev_get_priv(bus);
if (altera_fpga_pcie_hide_rc_bar(pcie, bdf, offset))
return 0;
if (!altera_pcie_valid_device(pcie, bdf))
return 0;
return _pcie_altera_fpga_write_config(pcie, bdf, offset, value,
size);
}
static int pcie_altera_fpga_probe(struct udevice *dev)
{
struct altera_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_altera_fpga_ofdata_to_platdata(struct udevice *dev)
{
struct altera_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;
}
dev_dbg(pcie->dev, "Mapping Cra to 0x%0x, size = 0x%0x \n", (int) reg_res.start, (int) fdt_resource_size(®_res));
pcie->cra_base = map_physmem(reg_res.start,
fdt_resource_size(®_res),
MAP_NOCACHE);
return 0;
}
static const struct dm_pci_ops pcie_altera_fpga_ops = {
.read_config = pcie_altera_fpga_read_config,
.write_config = pcie_altera_fpga_write_config,
};
static const struct udevice_id pcie_altera_fpga_ids[] = {
{ .compatible = "altr,pcie-root-port-1.0" },
{},
};
U_BOOT_DRIVER(pcie_altera_fpga) = {
.name = "pcie_altera_fpga",
.id = UCLASS_PCI,
.of_match = pcie_altera_fpga_ids,
.ops = &pcie_altera_fpga_ops,
.ofdata_to_platdata = pcie_altera_fpga_ofdata_to_platdata,
.probe = pcie_altera_fpga_probe,
.priv_auto_alloc_size = sizeof(struct altera_fpga_pcie),
};