mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-26 21:13:48 +00:00
431 lines
11 KiB
C
431 lines
11 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
/*
|
||
|
* Intel FPGA PCIe host controller driver
|
||
|
*
|
||
|
* Copyright (C) 2013-2018 Intel Corporation. All rights reserved
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include <common.h>
|
||
|
#include <dm.h>
|
||
|
#include <pci.h>
|
||
|
#include <asm/io.h>
|
||
|
|
||
|
#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),
|
||
|
};
|