// SPDX-License-Identifier: GPL-2.0+ OR X11 /* * Copyright 2019 NXP * * PCIe DM U-Boot driver for Freescale PowerPC SoCs * Author: Hou Zhiqiang */ #include #include #include #include #include #include #include #include #include #include #include "pcie_fsl.h" #include 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; } val = PCI_CONF1_EXT_ADDRESS(PCI_BUS(bdf) - dev_seq(bus), PCI_DEV(bdf), PCI_FUNC(bdf), offset); out_be32(®s->cfg_addr, val); sync(); switch (size) { case PCI_SIZE_8: *valuep = in_8((u8 *)®s->cfg_data + (offset & 3)); break; case PCI_SIZE_16: *valuep = in_le16((u16 *)((u8 *)®s->cfg_data + (offset & 2))); break; case PCI_SIZE_32: *valuep = in_le32(®s->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; val = PCI_CONF1_EXT_ADDRESS(PCI_BUS(bdf) - dev_seq(bus), PCI_DEV(bdf), PCI_FUNC(bdf), offset); out_be32(®s->cfg_addr, val); sync(); switch (size) { case PCI_SIZE_8: val_8 = value; out_8((u8 *)®s->cfg_data + (offset & 3), val_8); break; case PCI_SIZE_16: val_16 = value; out_le16((u16 *)((u8 *)®s->cfg_data + (offset & 2)), val_16); break; case PCI_SIZE_32: val_32 = value; out_le32(®s->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(®s->pex_csr0) & PEX_CSR0_LTSSM_MASK) >> PEX_CSR0_LTSSM_SHIFT; return ltssm == LTSSM_L0_REV3; } fsl_pcie_hose_read_config_word(pcie, PCI_LTSSM, <ssm); 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(®s->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 = ®s->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 = ®s->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 = CONFIG_SYS_PCI_MEMORY_PHYS; pci_addr_t bus_start = CONFIG_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, CONFIG_SYS_PCI_MEMORY_PHYS, CONFIG_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)CONFIG_SYS_PCI64_MEMORY_BUS, (u64)CONFIG_SYS_PCI_MEMORY_PHYS, (u64)pci_sz); fsl_pcie_setup_inbound_win(pcie, idx--, true, CONFIG_SYS_PCI_MEMORY_PHYS, CONFIG_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(®s->pdb_stat, 0x08000000); (void)in_be32(®s->pdb_stat); udelay(1000); /* clear PCIe reset */ clrbits_be32(®s->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, <ssm); if (ltssm == 1) { /* assert PCIe reset */ setbits_be32(®s->pdb_stat, 0x08000000); (void)in_be32(®s->pdb_stat); udelay(100); /* clear PCIe reset */ clrbits_be32(®s->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(®s->pdb_stat, 0x08000000); in_be32(®s->pdb_stat); udelay(100); clrbits_be32(®s->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(®s->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(®s->pme_msg_det, 0xffffffff); out_be32(®s->pme_msg_int_en, 0xffffffff); out_be32(®s->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(®s->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), };