41575d8e4c
This construct is quite long-winded. In earlier days it made some sense since auto-allocation was a strange concept. But with driver model now used pretty universally, we can shorten this to 'auto'. This reduces verbosity and makes it easier to read. Coincidentally it also ensures that every declaration is on one line, thus making dtoc's job easier. Signed-off-by: Simon Glass <sjg@chromium.org>
380 lines
9.7 KiB
C
380 lines
9.7 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright 2020 NXP
|
|
* Layerscape PCIe driver
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <asm/arch/fsl_serdes.h>
|
|
#include <pci.h>
|
|
#include <asm/io.h>
|
|
#include <errno.h>
|
|
#include <malloc.h>
|
|
#include <dm.h>
|
|
#include <dm/devres.h>
|
|
#if defined(CONFIG_FSL_LSCH2) || defined(CONFIG_FSL_LSCH3) || \
|
|
defined(CONFIG_ARM)
|
|
#include <asm/arch/clock.h>
|
|
#endif
|
|
#include "pcie_layerscape.h"
|
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
|
|
|
static void ls_pcie_cfg0_set_busdev(struct ls_pcie_rc *pcie_rc, u32 busdev)
|
|
{
|
|
struct ls_pcie *pcie = pcie_rc->pcie;
|
|
|
|
dbi_writel(pcie, PCIE_ATU_REGION_OUTBOUND | PCIE_ATU_REGION_INDEX0,
|
|
PCIE_ATU_VIEWPORT);
|
|
dbi_writel(pcie, busdev, PCIE_ATU_LOWER_TARGET);
|
|
}
|
|
|
|
static void ls_pcie_cfg1_set_busdev(struct ls_pcie_rc *pcie_rc, u32 busdev)
|
|
{
|
|
struct ls_pcie *pcie = pcie_rc->pcie;
|
|
|
|
dbi_writel(pcie, PCIE_ATU_REGION_OUTBOUND | PCIE_ATU_REGION_INDEX1,
|
|
PCIE_ATU_VIEWPORT);
|
|
dbi_writel(pcie, busdev, PCIE_ATU_LOWER_TARGET);
|
|
}
|
|
|
|
static void ls_pcie_setup_atu(struct ls_pcie_rc *pcie_rc)
|
|
{
|
|
struct pci_region *io, *mem, *pref;
|
|
unsigned long long offset = 0;
|
|
struct ls_pcie *pcie = pcie_rc->pcie;
|
|
int idx = 0;
|
|
uint svr;
|
|
|
|
svr = get_svr();
|
|
if (((svr >> SVR_VAR_PER_SHIFT) & SVR_LS102XA_MASK) == SVR_LS102XA) {
|
|
offset = LS1021_PCIE_SPACE_OFFSET +
|
|
LS1021_PCIE_SPACE_SIZE * pcie->idx;
|
|
}
|
|
|
|
/* ATU 0 : OUTBOUND : CFG0 */
|
|
ls_pcie_atu_outbound_set(pcie, PCIE_ATU_REGION_INDEX0,
|
|
PCIE_ATU_TYPE_CFG0,
|
|
pcie_rc->cfg_res.start + offset,
|
|
0,
|
|
fdt_resource_size(&pcie_rc->cfg_res) / 2);
|
|
/* ATU 1 : OUTBOUND : CFG1 */
|
|
ls_pcie_atu_outbound_set(pcie, PCIE_ATU_REGION_INDEX1,
|
|
PCIE_ATU_TYPE_CFG1,
|
|
pcie_rc->cfg_res.start + offset +
|
|
fdt_resource_size(&pcie_rc->cfg_res) / 2,
|
|
0,
|
|
fdt_resource_size(&pcie_rc->cfg_res) / 2);
|
|
|
|
pci_get_regions(pcie_rc->bus, &io, &mem, &pref);
|
|
idx = PCIE_ATU_REGION_INDEX1 + 1;
|
|
|
|
/* Fix the pcie memory map for LS2088A series SoCs */
|
|
svr = (svr >> SVR_VAR_PER_SHIFT) & 0xFFFFFE;
|
|
if (svr == SVR_LS2088A || svr == SVR_LS2084A ||
|
|
svr == SVR_LS2048A || svr == SVR_LS2044A ||
|
|
svr == SVR_LS2081A || svr == SVR_LS2041A) {
|
|
if (io)
|
|
io->phys_start = (io->phys_start &
|
|
(PCIE_PHYS_SIZE - 1)) +
|
|
LS2088A_PCIE1_PHYS_ADDR +
|
|
LS2088A_PCIE_PHYS_SIZE * pcie->idx;
|
|
if (mem)
|
|
mem->phys_start = (mem->phys_start &
|
|
(PCIE_PHYS_SIZE - 1)) +
|
|
LS2088A_PCIE1_PHYS_ADDR +
|
|
LS2088A_PCIE_PHYS_SIZE * pcie->idx;
|
|
if (pref)
|
|
pref->phys_start = (pref->phys_start &
|
|
(PCIE_PHYS_SIZE - 1)) +
|
|
LS2088A_PCIE1_PHYS_ADDR +
|
|
LS2088A_PCIE_PHYS_SIZE * pcie->idx;
|
|
}
|
|
|
|
if (io)
|
|
/* ATU : OUTBOUND : IO */
|
|
ls_pcie_atu_outbound_set(pcie, idx++,
|
|
PCIE_ATU_TYPE_IO,
|
|
io->phys_start + offset,
|
|
io->bus_start,
|
|
io->size);
|
|
|
|
if (mem)
|
|
/* ATU : OUTBOUND : MEM */
|
|
ls_pcie_atu_outbound_set(pcie, idx++,
|
|
PCIE_ATU_TYPE_MEM,
|
|
mem->phys_start + offset,
|
|
mem->bus_start,
|
|
mem->size);
|
|
|
|
if (pref)
|
|
/* ATU : OUTBOUND : pref */
|
|
ls_pcie_atu_outbound_set(pcie, idx++,
|
|
PCIE_ATU_TYPE_MEM,
|
|
pref->phys_start + offset,
|
|
pref->bus_start,
|
|
pref->size);
|
|
|
|
ls_pcie_dump_atu(pcie, PCIE_ATU_REGION_NUM, PCIE_ATU_REGION_OUTBOUND);
|
|
}
|
|
|
|
/* Return 0 if the address is valid, -errno if not valid */
|
|
static int ls_pcie_addr_valid(struct ls_pcie_rc *pcie_rc, pci_dev_t bdf)
|
|
{
|
|
struct udevice *bus = pcie_rc->bus;
|
|
struct ls_pcie *pcie = pcie_rc->pcie;
|
|
|
|
if (pcie->mode == PCI_HEADER_TYPE_NORMAL)
|
|
return -ENODEV;
|
|
|
|
if (!pcie_rc->enabled)
|
|
return -ENXIO;
|
|
|
|
if (PCI_BUS(bdf) < bus->seq)
|
|
return -EINVAL;
|
|
|
|
if ((PCI_BUS(bdf) > bus->seq) && (!ls_pcie_link_up(pcie)))
|
|
return -EINVAL;
|
|
|
|
if (PCI_BUS(bdf) <= (bus->seq + 1) && (PCI_DEV(bdf) > 0))
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ls_pcie_conf_address(const struct udevice *bus, pci_dev_t bdf,
|
|
uint offset, void **paddress)
|
|
{
|
|
struct ls_pcie_rc *pcie_rc = dev_get_priv(bus);
|
|
struct ls_pcie *pcie = pcie_rc->pcie;
|
|
u32 busdev;
|
|
|
|
if (ls_pcie_addr_valid(pcie_rc, bdf))
|
|
return -EINVAL;
|
|
|
|
if (PCI_BUS(bdf) == bus->seq) {
|
|
*paddress = pcie->dbi + offset;
|
|
return 0;
|
|
}
|
|
|
|
busdev = PCIE_ATU_BUS(PCI_BUS(bdf) - bus->seq) |
|
|
PCIE_ATU_DEV(PCI_DEV(bdf)) |
|
|
PCIE_ATU_FUNC(PCI_FUNC(bdf));
|
|
|
|
if (PCI_BUS(bdf) == bus->seq + 1) {
|
|
ls_pcie_cfg0_set_busdev(pcie_rc, busdev);
|
|
*paddress = pcie_rc->cfg0 + offset;
|
|
} else {
|
|
ls_pcie_cfg1_set_busdev(pcie_rc, busdev);
|
|
*paddress = pcie_rc->cfg1 + offset;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int ls_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, ls_pcie_conf_address,
|
|
bdf, offset, valuep, size);
|
|
}
|
|
|
|
static int ls_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, ls_pcie_conf_address,
|
|
bdf, offset, value, size);
|
|
}
|
|
|
|
/* Clear multi-function bit */
|
|
static void ls_pcie_clear_multifunction(struct ls_pcie_rc *pcie_rc)
|
|
{
|
|
struct ls_pcie *pcie = pcie_rc->pcie;
|
|
|
|
writeb(PCI_HEADER_TYPE_BRIDGE, pcie->dbi + PCI_HEADER_TYPE);
|
|
}
|
|
|
|
/* Fix class value */
|
|
static void ls_pcie_fix_class(struct ls_pcie_rc *pcie_rc)
|
|
{
|
|
struct ls_pcie *pcie = pcie_rc->pcie;
|
|
|
|
writew(PCI_CLASS_BRIDGE_PCI, pcie->dbi + PCI_CLASS_DEVICE);
|
|
}
|
|
|
|
/* Drop MSG TLP except for Vendor MSG */
|
|
static void ls_pcie_drop_msg_tlp(struct ls_pcie_rc *pcie_rc)
|
|
{
|
|
struct ls_pcie *pcie = pcie_rc->pcie;
|
|
u32 val;
|
|
|
|
val = dbi_readl(pcie, PCIE_STRFMR1);
|
|
val &= 0xDFFFFFFF;
|
|
dbi_writel(pcie, val, PCIE_STRFMR1);
|
|
}
|
|
|
|
/* Disable all bars in RC mode */
|
|
static void ls_pcie_disable_bars(struct ls_pcie_rc *pcie_rc)
|
|
{
|
|
struct ls_pcie *pcie = pcie_rc->pcie;
|
|
|
|
dbi_writel(pcie, 0, PCIE_CS2_OFFSET + PCI_BASE_ADDRESS_0);
|
|
dbi_writel(pcie, 0, PCIE_CS2_OFFSET + PCI_BASE_ADDRESS_1);
|
|
dbi_writel(pcie, 0xfffffffe, PCIE_CS2_OFFSET + PCI_ROM_ADDRESS1);
|
|
}
|
|
|
|
static void ls_pcie_setup_ctrl(struct ls_pcie_rc *pcie_rc)
|
|
{
|
|
struct ls_pcie *pcie = pcie_rc->pcie;
|
|
|
|
ls_pcie_setup_atu(pcie_rc);
|
|
|
|
ls_pcie_dbi_ro_wr_en(pcie);
|
|
ls_pcie_fix_class(pcie_rc);
|
|
ls_pcie_clear_multifunction(pcie_rc);
|
|
ls_pcie_drop_msg_tlp(pcie_rc);
|
|
ls_pcie_dbi_ro_wr_dis(pcie);
|
|
|
|
ls_pcie_disable_bars(pcie_rc);
|
|
pcie_rc->stream_id_cur = 0;
|
|
}
|
|
|
|
static int ls_pcie_probe(struct udevice *dev)
|
|
{
|
|
struct ls_pcie_rc *pcie_rc = dev_get_priv(dev);
|
|
const void *fdt = gd->fdt_blob;
|
|
int node = dev_of_offset(dev);
|
|
struct ls_pcie *pcie;
|
|
u16 link_sta;
|
|
uint svr;
|
|
int ret;
|
|
fdt_size_t cfg_size;
|
|
|
|
pcie_rc->bus = dev;
|
|
|
|
pcie = devm_kmalloc(dev, sizeof(*pcie), GFP_KERNEL);
|
|
if (!pcie)
|
|
return -ENOMEM;
|
|
|
|
pcie_rc->pcie = pcie;
|
|
|
|
ret = fdt_get_named_resource(fdt, node, "reg", "reg-names",
|
|
"dbi", &pcie_rc->dbi_res);
|
|
if (ret) {
|
|
printf("ls-pcie: resource \"dbi\" not found\n");
|
|
return ret;
|
|
}
|
|
|
|
pcie->idx = (pcie_rc->dbi_res.start - PCIE_SYS_BASE_ADDR) /
|
|
PCIE_CCSR_SIZE;
|
|
|
|
list_add(&pcie_rc->list, &ls_pcie_list);
|
|
|
|
pcie_rc->enabled = is_serdes_configured(PCIE_SRDS_PRTCL(pcie->idx));
|
|
if (!pcie_rc->enabled) {
|
|
printf("PCIe%d: %s disabled\n", pcie->idx, dev->name);
|
|
return 0;
|
|
}
|
|
|
|
pcie->dbi = map_physmem(pcie_rc->dbi_res.start,
|
|
fdt_resource_size(&pcie_rc->dbi_res),
|
|
MAP_NOCACHE);
|
|
|
|
pcie->mode = readb(pcie->dbi + PCI_HEADER_TYPE) & 0x7f;
|
|
if (pcie->mode == PCI_HEADER_TYPE_NORMAL)
|
|
return 0;
|
|
|
|
ret = fdt_get_named_resource(fdt, node, "reg", "reg-names",
|
|
"lut", &pcie_rc->lut_res);
|
|
if (!ret)
|
|
pcie->lut = map_physmem(pcie_rc->lut_res.start,
|
|
fdt_resource_size(&pcie_rc->lut_res),
|
|
MAP_NOCACHE);
|
|
|
|
ret = fdt_get_named_resource(fdt, node, "reg", "reg-names",
|
|
"ctrl", &pcie_rc->ctrl_res);
|
|
if (!ret)
|
|
pcie->ctrl = map_physmem(pcie_rc->ctrl_res.start,
|
|
fdt_resource_size(&pcie_rc->ctrl_res),
|
|
MAP_NOCACHE);
|
|
if (!pcie->ctrl)
|
|
pcie->ctrl = pcie->lut;
|
|
|
|
if (!pcie->ctrl) {
|
|
printf("%s: NOT find CTRL\n", dev->name);
|
|
return -1;
|
|
}
|
|
|
|
ret = fdt_get_named_resource(fdt, node, "reg", "reg-names",
|
|
"config", &pcie_rc->cfg_res);
|
|
if (ret) {
|
|
printf("%s: resource \"config\" not found\n", dev->name);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Fix the pcie memory map address and PF control registers address
|
|
* for LS2088A series SoCs
|
|
*/
|
|
svr = get_svr();
|
|
svr = (svr >> SVR_VAR_PER_SHIFT) & 0xFFFFFE;
|
|
if (svr == SVR_LS2088A || svr == SVR_LS2084A ||
|
|
svr == SVR_LS2048A || svr == SVR_LS2044A ||
|
|
svr == SVR_LS2081A || svr == SVR_LS2041A) {
|
|
cfg_size = fdt_resource_size(&pcie_rc->cfg_res);
|
|
pcie_rc->cfg_res.start = LS2088A_PCIE1_PHYS_ADDR +
|
|
LS2088A_PCIE_PHYS_SIZE * pcie->idx;
|
|
pcie_rc->cfg_res.end = pcie_rc->cfg_res.start + cfg_size;
|
|
pcie->ctrl = pcie->lut + 0x40000;
|
|
}
|
|
|
|
pcie_rc->cfg0 = map_physmem(pcie_rc->cfg_res.start,
|
|
fdt_resource_size(&pcie_rc->cfg_res),
|
|
MAP_NOCACHE);
|
|
pcie_rc->cfg1 = pcie_rc->cfg0 +
|
|
fdt_resource_size(&pcie_rc->cfg_res) / 2;
|
|
|
|
pcie->big_endian = fdtdec_get_bool(fdt, node, "big-endian");
|
|
|
|
debug("%s dbi:%lx lut:%lx ctrl:0x%lx cfg0:0x%lx, big-endian:%d\n",
|
|
dev->name, (unsigned long)pcie->dbi, (unsigned long)pcie->lut,
|
|
(unsigned long)pcie->ctrl, (unsigned long)pcie_rc->cfg0,
|
|
pcie->big_endian);
|
|
|
|
printf("PCIe%u: %s %s", pcie->idx, dev->name, "Root Complex");
|
|
ls_pcie_setup_ctrl(pcie_rc);
|
|
|
|
if (!ls_pcie_link_up(pcie)) {
|
|
/* Let the user know there's no PCIe link */
|
|
printf(": no link\n");
|
|
return 0;
|
|
}
|
|
|
|
/* Print the negotiated PCIe link width */
|
|
link_sta = readw(pcie->dbi + PCIE_LINK_STA);
|
|
printf(": x%d gen%d\n", (link_sta & PCIE_LINK_WIDTH_MASK) >> 4,
|
|
link_sta & PCIE_LINK_SPEED_MASK);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dm_pci_ops ls_pcie_ops = {
|
|
.read_config = ls_pcie_read_config,
|
|
.write_config = ls_pcie_write_config,
|
|
};
|
|
|
|
static const struct udevice_id ls_pcie_ids[] = {
|
|
{ .compatible = "fsl,ls-pcie" },
|
|
{ }
|
|
};
|
|
|
|
U_BOOT_DRIVER(pci_layerscape) = {
|
|
.name = "pci_layerscape",
|
|
.id = UCLASS_PCI,
|
|
.of_match = ls_pcie_ids,
|
|
.ops = &ls_pcie_ops,
|
|
.probe = ls_pcie_probe,
|
|
.priv_auto = sizeof(struct ls_pcie_rc),
|
|
};
|