u-boot/drivers/pci/pcie_fsl.c
Simon Glass 1e94b46f73 common: Drop linux/printk.h from common header
This old patch was marked as deferred. Bring it back to life, to continue
towards the removal of common.h

Move this out of the common header and include it only where needed.

Signed-off-by: Simon Glass <sjg@chromium.org>
2023-09-24 09:54:57 -04:00

685 lines
16 KiB
C

// SPDX-License-Identifier: GPL-2.0+ OR X11
/*
* Copyright 2019 NXP
*
* PCIe DM U-Boot driver for Freescale PowerPC SoCs
* Author: Hou Zhiqiang <Zhiqiang.Hou@nxp.com>
*/
#include <common.h>
#include <dm.h>
#include <malloc.h>
#include <mapmem.h>
#include <pci.h>
#include <asm/fsl_pci.h>
#include <asm/fsl_serdes.h>
#include <asm/global_data.h>
#include <asm/io.h>
#include <linux/delay.h>
#include <linux/printk.h>
#include "pcie_fsl.h"
#include <dm/device_compat.h>
LIST_HEAD(fsl_pcie_list);
static int fsl_pcie_link_up(struct fsl_pcie *pcie);
static int fsl_pcie_addr_valid(struct fsl_pcie *pcie, pci_dev_t bdf)
{
struct udevice *bus = pcie->bus;
if (!pcie->enabled)
return -ENXIO;
if (PCI_BUS(bdf) < dev_seq(bus))
return -EINVAL;
if (PCI_BUS(bdf) > dev_seq(bus) && (!fsl_pcie_link_up(pcie) || pcie->mode))
return -EINVAL;
if (PCI_BUS(bdf) == dev_seq(bus) && (PCI_DEV(bdf) > 0 || PCI_FUNC(bdf) > 0))
return -EINVAL;
if (PCI_BUS(bdf) == (dev_seq(bus) + 1) && (PCI_DEV(bdf) > 0))
return -EINVAL;
return 0;
}
static int fsl_pcie_read_config(const struct udevice *bus, pci_dev_t bdf,
uint offset, ulong *valuep,
enum pci_size_t size)
{
struct fsl_pcie *pcie = dev_get_priv(bus);
ccsr_fsl_pci_t *regs = pcie->regs;
u32 val;
if (fsl_pcie_addr_valid(pcie, bdf)) {
*valuep = pci_get_ff(size);
return 0;
}
/* Skip Freescale PCIe controller's PEXCSRBAR register */
if (PCI_BUS(bdf) - dev_seq(bus) == 0 &&
PCI_DEV(bdf) == 0 && PCI_FUNC(bdf) == 0 &&
(offset & ~3) == PCI_BASE_ADDRESS_0) {
*valuep = 0;
return 0;
}
val = PCI_CONF1_EXT_ADDRESS(PCI_BUS(bdf) - dev_seq(bus),
PCI_DEV(bdf), PCI_FUNC(bdf),
offset);
out_be32(&regs->cfg_addr, val);
sync();
switch (size) {
case PCI_SIZE_8:
*valuep = in_8((u8 *)&regs->cfg_data + (offset & 3));
break;
case PCI_SIZE_16:
*valuep = in_le16((u16 *)((u8 *)&regs->cfg_data +
(offset & 2)));
break;
case PCI_SIZE_32:
*valuep = in_le32(&regs->cfg_data);
break;
}
return 0;
}
static int fsl_pcie_write_config(struct udevice *bus, pci_dev_t bdf,
uint offset, ulong value,
enum pci_size_t size)
{
struct fsl_pcie *pcie = dev_get_priv(bus);
ccsr_fsl_pci_t *regs = pcie->regs;
u32 val;
u8 val_8;
u16 val_16;
u32 val_32;
if (fsl_pcie_addr_valid(pcie, bdf))
return 0;
/* Skip Freescale PCIe controller's PEXCSRBAR register */
if (PCI_BUS(bdf) - dev_seq(bus) == 0 &&
PCI_DEV(bdf) == 0 && PCI_FUNC(bdf) == 0 &&
(offset & ~3) == PCI_BASE_ADDRESS_0)
return 0;
val = PCI_CONF1_EXT_ADDRESS(PCI_BUS(bdf) - dev_seq(bus),
PCI_DEV(bdf), PCI_FUNC(bdf),
offset);
out_be32(&regs->cfg_addr, val);
sync();
switch (size) {
case PCI_SIZE_8:
val_8 = value;
out_8((u8 *)&regs->cfg_data + (offset & 3), val_8);
break;
case PCI_SIZE_16:
val_16 = value;
out_le16((u16 *)((u8 *)&regs->cfg_data + (offset & 2)), val_16);
break;
case PCI_SIZE_32:
val_32 = value;
out_le32(&regs->cfg_data, val_32);
break;
}
return 0;
}
static int fsl_pcie_hose_read_config(struct fsl_pcie *pcie, uint offset,
ulong *valuep, enum pci_size_t size)
{
int ret;
struct udevice *bus = pcie->bus;
ret = fsl_pcie_read_config(bus, PCI_BDF(dev_seq(bus), 0, 0),
offset, valuep, size);
return ret;
}
static int fsl_pcie_hose_write_config(struct fsl_pcie *pcie, uint offset,
ulong value, enum pci_size_t size)
{
struct udevice *bus = pcie->bus;
return fsl_pcie_write_config(bus, PCI_BDF(dev_seq(bus), 0, 0),
offset, value, size);
}
static int fsl_pcie_hose_read_config_byte(struct fsl_pcie *pcie, uint offset,
u8 *valuep)
{
ulong val;
int ret;
ret = fsl_pcie_hose_read_config(pcie, offset, &val, PCI_SIZE_8);
*valuep = val;
return ret;
}
static int fsl_pcie_hose_read_config_word(struct fsl_pcie *pcie, uint offset,
u16 *valuep)
{
ulong val;
int ret;
ret = fsl_pcie_hose_read_config(pcie, offset, &val, PCI_SIZE_16);
*valuep = val;
return ret;
}
static int fsl_pcie_hose_read_config_dword(struct fsl_pcie *pcie, uint offset,
u32 *valuep)
{
ulong val;
int ret;
ret = fsl_pcie_hose_read_config(pcie, offset, &val, PCI_SIZE_32);
*valuep = val;
return ret;
}
static int fsl_pcie_hose_write_config_byte(struct fsl_pcie *pcie, uint offset,
u8 value)
{
return fsl_pcie_hose_write_config(pcie, offset, value, PCI_SIZE_8);
}
static int fsl_pcie_hose_write_config_word(struct fsl_pcie *pcie, uint offset,
u16 value)
{
return fsl_pcie_hose_write_config(pcie, offset, value, PCI_SIZE_16);
}
static int fsl_pcie_hose_write_config_dword(struct fsl_pcie *pcie, uint offset,
u32 value)
{
return fsl_pcie_hose_write_config(pcie, offset, value, PCI_SIZE_32);
}
static int fsl_pcie_link_up(struct fsl_pcie *pcie)
{
ccsr_fsl_pci_t *regs = pcie->regs;
u16 ltssm;
if (pcie->block_rev >= PEX_IP_BLK_REV_3_0) {
ltssm = (in_be32(&regs->pex_csr0)
& PEX_CSR0_LTSSM_MASK) >> PEX_CSR0_LTSSM_SHIFT;
return ltssm == LTSSM_L0_REV3;
}
fsl_pcie_hose_read_config_word(pcie, PCI_LTSSM, &ltssm);
return ltssm == LTSSM_L0;
}
static bool fsl_pcie_is_agent(struct fsl_pcie *pcie)
{
u8 header_type;
fsl_pcie_hose_read_config_byte(pcie, PCI_HEADER_TYPE, &header_type);
return (header_type & 0x7f) == PCI_HEADER_TYPE_NORMAL;
}
static int fsl_pcie_setup_law(struct fsl_pcie *pcie)
{
struct pci_region *io, *mem, *pref;
pci_get_regions(pcie->bus, &io, &mem, &pref);
if (mem)
set_next_law(mem->phys_start,
law_size_bits(mem->size),
pcie->law_trgt_if);
if (io)
set_next_law(io->phys_start,
law_size_bits(io->size),
pcie->law_trgt_if);
return 0;
}
static void fsl_pcie_config_ready(struct fsl_pcie *pcie)
{
ccsr_fsl_pci_t *regs = pcie->regs;
if (pcie->block_rev >= PEX_IP_BLK_REV_3_0) {
setbits_be32(&regs->config, FSL_PCIE_V3_CFG_RDY);
return;
}
fsl_pcie_hose_write_config_byte(pcie, FSL_PCIE_CFG_RDY, 0x1);
}
static int fsl_pcie_setup_outbound_win(struct fsl_pcie *pcie, int idx,
int type, u64 phys, u64 bus_addr,
pci_size_t size)
{
ccsr_fsl_pci_t *regs = pcie->regs;
pot_t *po = &regs->pot[idx];
u32 war, sz;
if (idx < 0)
return -EINVAL;
out_be32(&po->powbar, phys >> 12);
out_be32(&po->potar, bus_addr >> 12);
#ifdef CONFIG_SYS_PCI_64BIT
out_be32(&po->potear, bus_addr >> 44);
#else
out_be32(&po->potear, 0);
#endif
sz = (__ilog2_u64((u64)size) - 1);
war = POWAR_EN | sz;
if (type == PCI_REGION_IO)
war |= POWAR_IO_READ | POWAR_IO_WRITE;
else
war |= POWAR_MEM_READ | POWAR_MEM_WRITE;
out_be32(&po->powar, war);
return 0;
}
static int fsl_pcie_setup_inbound_win(struct fsl_pcie *pcie, int idx,
bool pf, u64 phys, u64 bus_addr,
pci_size_t size)
{
ccsr_fsl_pci_t *regs = pcie->regs;
pit_t *pi = &regs->pit[idx];
u32 sz = (__ilog2_u64(size) - 1);
u32 flag = PIWAR_LOCAL;
if (idx < 0)
return -EINVAL;
out_be32(&pi->pitar, phys >> 12);
out_be32(&pi->piwbar, bus_addr >> 12);
#ifdef CONFIG_SYS_PCI_64BIT
out_be32(&pi->piwbear, bus_addr >> 44);
#else
out_be32(&pi->piwbear, 0);
#endif
#ifdef CONFIG_SYS_FSL_ERRATUM_A005434
flag = 0;
#endif
flag |= PIWAR_EN | PIWAR_READ_SNOOP | PIWAR_WRITE_SNOOP;
if (pf)
flag |= PIWAR_PF;
out_be32(&pi->piwar, flag | sz);
return 0;
}
static int fsl_pcie_setup_outbound_wins(struct fsl_pcie *pcie)
{
struct pci_region *io, *mem, *pref;
int idx = 1; /* skip 0 */
pci_get_regions(pcie->bus, &io, &mem, &pref);
if (io)
/* ATU : OUTBOUND : IO */
fsl_pcie_setup_outbound_win(pcie, idx++,
PCI_REGION_IO,
io->phys_start,
io->bus_start,
io->size);
if (mem)
/* ATU : OUTBOUND : MEM */
fsl_pcie_setup_outbound_win(pcie, idx++,
PCI_REGION_MEM,
mem->phys_start,
mem->bus_start,
mem->size);
return 0;
}
static int fsl_pcie_setup_inbound_wins(struct fsl_pcie *pcie)
{
phys_addr_t phys_start = CFG_SYS_PCI_MEMORY_PHYS;
pci_addr_t bus_start = CFG_SYS_PCI_MEMORY_BUS;
u64 sz = min((u64)gd->ram_size, (1ull << 32));
pci_size_t pci_sz;
int idx;
if (pcie->block_rev >= PEX_IP_BLK_REV_2_2)
idx = 2;
else
idx = 3;
pci_sz = 1ull << __ilog2_u64(sz);
dev_dbg(pcie->bus, "R0 bus_start: %llx phys_start: %llx size: %llx\n",
(u64)bus_start, (u64)phys_start, (u64)sz);
/* if we aren't an exact power of two match, pci_sz is smaller
* round it up to the next power of two. We report the actual
* size to pci region tracking.
*/
if (pci_sz != sz)
sz = 2ull << __ilog2_u64(sz);
fsl_pcie_setup_inbound_win(pcie, idx--, true,
CFG_SYS_PCI_MEMORY_PHYS,
CFG_SYS_PCI_MEMORY_BUS, sz);
#if defined(CONFIG_PHYS_64BIT) && defined(CONFIG_SYS_PCI_64BIT)
/*
* On 64-bit capable systems, set up a mapping for all of DRAM
* in high pci address space.
*/
pci_sz = 1ull << __ilog2_u64(gd->ram_size);
/* round up to the next largest power of two */
if (gd->ram_size > pci_sz)
pci_sz = 1ull << (__ilog2_u64(gd->ram_size) + 1);
dev_dbg(pcie->bus, "R64 bus_start: %llx phys_start: %llx size: %llx\n",
(u64)CFG_SYS_PCI64_MEMORY_BUS,
(u64)CFG_SYS_PCI_MEMORY_PHYS, (u64)pci_sz);
fsl_pcie_setup_inbound_win(pcie, idx--, true,
CFG_SYS_PCI_MEMORY_PHYS,
CFG_SYS_PCI64_MEMORY_BUS, pci_sz);
#endif
return 0;
}
static int fsl_pcie_init_atmu(struct fsl_pcie *pcie)
{
fsl_pcie_setup_outbound_wins(pcie);
fsl_pcie_setup_inbound_wins(pcie);
return 0;
}
static void fsl_pcie_dbi_read_only_reg_write_enable(struct fsl_pcie *pcie,
bool enable)
{
u32 val;
fsl_pcie_hose_read_config_dword(pcie, DBI_RO_WR_EN, &val);
if (enable)
val |= 1;
else
val &= ~1;
fsl_pcie_hose_write_config_dword(pcie, DBI_RO_WR_EN, val);
}
static int fsl_pcie_init_port(struct fsl_pcie *pcie)
{
ccsr_fsl_pci_t *regs = pcie->regs;
u32 val_32;
u16 val_16;
fsl_pcie_init_atmu(pcie);
#ifdef CONFIG_FSL_PCIE_DISABLE_ASPM
val_32 = 0;
fsl_pcie_hose_read_config_dword(pcie, PCI_LCR, &val_32);
val_32 &= ~0x03;
fsl_pcie_hose_write_config_dword(pcie, PCI_LCR, val_32);
udelay(1);
#endif
#ifdef CONFIG_FSL_PCIE_RESET
u16 ltssm;
int i;
if (pcie->block_rev >= PEX_IP_BLK_REV_3_0) {
/* assert PCIe reset */
setbits_be32(&regs->pdb_stat, 0x08000000);
(void)in_be32(&regs->pdb_stat);
udelay(1000);
/* clear PCIe reset */
clrbits_be32(&regs->pdb_stat, 0x08000000);
asm("sync;isync");
for (i = 0; i < 100 && !fsl_pcie_link_up(pcie); i++)
udelay(1000);
} else {
fsl_pcie_hose_read_config_word(pcie, PCI_LTSSM, &ltssm);
if (ltssm == 1) {
/* assert PCIe reset */
setbits_be32(&regs->pdb_stat, 0x08000000);
(void)in_be32(&regs->pdb_stat);
udelay(100);
/* clear PCIe reset */
clrbits_be32(&regs->pdb_stat, 0x08000000);
asm("sync;isync");
for (i = 0; i < 100 &&
!fsl_pcie_link_up(pcie); i++)
udelay(1000);
}
}
#endif
#ifdef CONFIG_SYS_P4080_ERRATUM_PCIE_A003
if (!fsl_pcie_link_up(pcie)) {
serdes_corenet_t *srds_regs;
srds_regs = (void *)CFG_SYS_FSL_CORENET_SERDES_ADDR;
val_32 = in_be32(&srds_regs->srdspccr0);
if ((val_32 >> 28) == 3) {
int i;
out_be32(&srds_regs->srdspccr0, 2 << 28);
setbits_be32(&regs->pdb_stat, 0x08000000);
in_be32(&regs->pdb_stat);
udelay(100);
clrbits_be32(&regs->pdb_stat, 0x08000000);
asm("sync;isync");
for (i = 0; i < 100 && !fsl_pcie_link_up(pcie); i++)
udelay(1000);
}
}
#endif
/*
* The Read-Only Write Enable bit defaults to 1 instead of 0.
* Set to 0 to protect the read-only registers.
*/
#ifdef CONFIG_SYS_FSL_ERRATUM_A007815
fsl_pcie_dbi_read_only_reg_write_enable(pcie, false);
#endif
/*
* Enable All Error Interrupts except
* - Master abort (pci)
* - Master PERR (pci)
* - ICCA (PCIe)
*/
out_be32(&regs->peer, ~0x20140);
/* set URR, FER, NFER (but not CER) */
fsl_pcie_hose_read_config_dword(pcie, PCI_DCR, &val_32);
val_32 |= 0xf000e;
fsl_pcie_hose_write_config_dword(pcie, PCI_DCR, val_32);
/* Clear all error indications */
out_be32(&regs->pme_msg_det, 0xffffffff);
out_be32(&regs->pme_msg_int_en, 0xffffffff);
out_be32(&regs->pedr, 0xffffffff);
fsl_pcie_hose_read_config_word(pcie, PCI_DSR, &val_16);
if (val_16)
fsl_pcie_hose_write_config_word(pcie, PCI_DSR, 0xffff);
fsl_pcie_hose_read_config_word(pcie, PCI_SEC_STATUS, &val_16);
if (val_16)
fsl_pcie_hose_write_config_word(pcie, PCI_SEC_STATUS, 0xffff);
return 0;
}
static int fsl_pcie_fixup_classcode(struct fsl_pcie *pcie)
{
u32 classcode_reg;
u32 val;
if (pcie->block_rev >= PEX_IP_BLK_REV_3_0) {
classcode_reg = PCI_CLASS_REVISION;
fsl_pcie_dbi_read_only_reg_write_enable(pcie, true);
} else {
classcode_reg = CSR_CLASSCODE;
}
fsl_pcie_hose_read_config_dword(pcie, classcode_reg, &val);
val &= 0xff;
val |= PCI_CLASS_BRIDGE_PCI_NORMAL << 8;
fsl_pcie_hose_write_config_dword(pcie, classcode_reg, val);
if (pcie->block_rev >= PEX_IP_BLK_REV_3_0)
fsl_pcie_dbi_read_only_reg_write_enable(pcie, false);
return 0;
}
static int fsl_pcie_init_rc(struct fsl_pcie *pcie)
{
return fsl_pcie_fixup_classcode(pcie);
}
static int fsl_pcie_init_ep(struct fsl_pcie *pcie)
{
fsl_pcie_config_ready(pcie);
return 0;
}
static int fsl_pcie_probe(struct udevice *dev)
{
struct fsl_pcie *pcie = dev_get_priv(dev);
ccsr_fsl_pci_t *regs = pcie->regs;
u16 val_16;
pcie->bus = dev;
pcie->block_rev = in_be32(&regs->block_rev1);
list_add(&pcie->list, &fsl_pcie_list);
pcie->enabled = is_serdes_configured(PCIE1 + pcie->idx);
if (!pcie->enabled) {
printf("PCIe%d: %s disabled\n", pcie->idx, dev->name);
return 0;
}
fsl_pcie_setup_law(pcie);
pcie->mode = fsl_pcie_is_agent(pcie);
fsl_pcie_init_port(pcie);
printf("PCIe%d: %s ", pcie->idx, dev->name);
if (pcie->mode) {
printf("Endpoint");
fsl_pcie_init_ep(pcie);
} else {
printf("Root Complex");
fsl_pcie_init_rc(pcie);
}
if (!fsl_pcie_link_up(pcie)) {
printf(": %s\n", pcie->mode ? "undetermined link" : "no link");
return 0;
}
fsl_pcie_hose_read_config_word(pcie, PCI_LSR, &val_16);
printf(": x%d gen%d\n", (val_16 & 0x3f0) >> 4, (val_16 & 0xf));
return 0;
}
static int fsl_pcie_of_to_plat(struct udevice *dev)
{
struct fsl_pcie *pcie = dev_get_priv(dev);
struct fsl_pcie_data *info;
int ret;
pcie->regs = dev_remap_addr(dev);
if (!pcie->regs) {
pr_err("\"reg\" resource not found\n");
return -EINVAL;
}
ret = dev_read_u32(dev, "law_trgt_if", &pcie->law_trgt_if);
if (ret < 0) {
pr_err("\"law_trgt_if\" not found\n");
return ret;
}
info = (struct fsl_pcie_data *)dev_get_driver_data(dev);
pcie->info = info;
pcie->idx = abs((u32)(dev_read_addr(dev) & info->block_offset_mask) -
info->block_offset) / info->stride;
return 0;
}
static const struct dm_pci_ops fsl_pcie_ops = {
.read_config = fsl_pcie_read_config,
.write_config = fsl_pcie_write_config,
};
static struct fsl_pcie_data p1_p2_data = {
.block_offset = 0xa000,
.block_offset_mask = 0xffff,
.stride = 0x1000,
};
static struct fsl_pcie_data p2041_data = {
.block_offset = 0x200000,
.block_offset_mask = 0x3fffff,
.stride = 0x1000,
};
static struct fsl_pcie_data t2080_data = {
.block_offset = 0x240000,
.block_offset_mask = 0x3fffff,
.stride = 0x10000,
};
static const struct udevice_id fsl_pcie_ids[] = {
{ .compatible = "fsl,mpc8548-pcie", .data = (ulong)&p1_p2_data },
{ .compatible = "fsl,pcie-p1_p2", .data = (ulong)&p1_p2_data },
{ .compatible = "fsl,pcie-p2041", .data = (ulong)&p2041_data },
{ .compatible = "fsl,pcie-p3041", .data = (ulong)&p2041_data },
{ .compatible = "fsl,pcie-p4080", .data = (ulong)&p2041_data },
{ .compatible = "fsl,pcie-p5040", .data = (ulong)&p2041_data },
{ .compatible = "fsl,pcie-t102x", .data = (ulong)&t2080_data },
{ .compatible = "fsl,pcie-t104x", .data = (ulong)&t2080_data },
{ .compatible = "fsl,pcie-t2080", .data = (ulong)&t2080_data },
{ .compatible = "fsl,pcie-t4240", .data = (ulong)&t2080_data },
{ }
};
U_BOOT_DRIVER(fsl_pcie) = {
.name = "fsl_pcie",
.id = UCLASS_PCI,
.of_match = fsl_pcie_ids,
.ops = &fsl_pcie_ops,
.of_to_plat = fsl_pcie_of_to_plat,
.probe = fsl_pcie_probe,
.priv_auto = sizeof(struct fsl_pcie),
};