This adds support for the designware based PCIe controller found on Layerscape SoCs. The driver is based on Linux-5.4. The device tree fixups have been taken from U-Boot 2019.10. Signed-off-by: Sascha Hauer <s.hauer@xxxxxxxxxxxxxx> --- arch/arm/Kconfig | 1 + drivers/pci/Kconfig | 7 + drivers/pci/Makefile | 1 + drivers/pci/pci-layerscape.c | 484 +++++++++++++++++++++++++++++++++++ 4 files changed, 493 insertions(+) create mode 100644 drivers/pci/pci-layerscape.c diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 9589a6a511..a4a4e03a56 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -106,6 +106,7 @@ config ARCH_LAYERSCAPE select COMMON_CLK select CLKDEV_LOOKUP select COMMON_CLK_OF_PROVIDER + select HW_HAS_PCI config ARCH_MVEBU bool "Marvell EBU platforms" diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 44a89d005f..025c418f2b 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -47,6 +47,13 @@ config PCI_IMX6 select OF_PCI select PCI +config PCI_LAYERSCAPE + bool "Freescale Layerscape PCIe controller" + depends on ARCH_LAYERSCAPE + select PCIE_DW + select OF_PCI + select PCI + endmenu endif diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index 562304c65d..3ca6708657 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -11,3 +11,4 @@ obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o pci-mvebu-phy.o obj-$(CONFIG_PCI_TEGRA) += pci-tegra.o obj-$(CONFIG_PCIE_DW) += pcie-designware.o pcie-designware-host.o obj-$(CONFIG_PCI_IMX6) += pci-imx6.o +obj-$(CONFIG_PCI_LAYERSCAPE) += pci-layerscape.o diff --git a/drivers/pci/pci-layerscape.c b/drivers/pci/pci-layerscape.c new file mode 100644 index 0000000000..ccdc025c2e --- /dev/null +++ b/drivers/pci/pci-layerscape.c @@ -0,0 +1,484 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PCIe host controller driver for Freescale Layerscape SoCs + * + * Copyright (C) 2014 Freescale Semiconductor. + * + * Author: Minghuan Lian <Minghuan.Lian@xxxxxxxxxxxxx> + */ + +#include <common.h> +#include <clock.h> +#include <abort.h> +#include <malloc.h> +#include <io.h> +#include <init.h> +#include <gpio.h> +#include <asm/mmu.h> +#include <of_gpio.h> +#include <of_device.h> +#include <linux/clk.h> +#include <linux/kernel.h> +#include <of_address.h> +#include <of_pci.h> +#include <regmap.h> +#include <mfd/syscon.h> +#include <linux/pci.h> +#include <linux/phy/phy.h> +#include <linux/reset.h> +#include <linux/sizes.h> + +#include "pcie-designware.h" + +/* PEX1/2 Misc Ports Status Register */ +#define SCFG_PEXMSCPORTSR(pex_idx) (0x94 + (pex_idx) * 4) +#define LTSSM_STATE_SHIFT 20 +#define LTSSM_STATE_MASK 0x3f +#define LTSSM_PCIE_L0 0x11 /* L0 state */ + +/* PEX Internal Configuration Registers */ +#define PCIE_STRFMR1 0x71c /* Symbol Timer & Filter Mask Register1 */ +#define PCIE_ABSERR 0x8d0 /* Bridge Slave Error Response Register */ +#define PCIE_ABSERR_SETTING 0x9401 /* Forward error of non-posted request */ + +#define PCIE_IATU_NUM 6 + +#define PCIE_LUT_UDR(n) (0x800 + (n) * 8) +#define PCIE_LUT_ENABLE (1 << 31) +#define PCIE_LUT_LDR(n) (0x804 + (n) * 8) + +struct ls_pcie_drvdata { + u32 lut_offset; + u32 ltssm_shift; + u32 lut_dbg; + int pex_stream_id_start; + int pex_stream_id_end; + const struct dw_pcie_host_ops *ops; + const struct dw_pcie_ops *dw_pcie_ops; +}; + +struct ls_pcie { + struct dw_pcie pci; + void __iomem *lut; + struct regmap *scfg; + const char *compatible; + const struct ls_pcie_drvdata *drvdata; + int index; + int next_lut_index; +}; + +static struct ls_pcie *to_ls_pcie(struct dw_pcie *pci) +{ + return container_of(pci, struct ls_pcie, pci); +} + +static u32 lut_readl(struct ls_pcie *pcie, unsigned int offset) +{ + return in_be32(pcie->lut + offset); +} + +static void lut_writel(struct ls_pcie *pcie, unsigned int value, + unsigned int offset) +{ + out_be32(pcie->lut + offset, value); +} + +static int ls_pcie_next_streamid(struct ls_pcie *pcie) +{ + static int next_stream_id; + int first = pcie->drvdata->pex_stream_id_start; + int last = pcie->drvdata->pex_stream_id_end; + int current = next_stream_id + first; + + if (current > last) + return -EINVAL; + + next_stream_id++; + + return current; +} + +#define PCIE_LUT_ENTRY_COUNT 32 + +static int ls_pcie_next_lut_index(struct ls_pcie *pcie) +{ + if (pcie->next_lut_index < PCIE_LUT_ENTRY_COUNT) + return pcie->next_lut_index++; + else + return -ENOSPC; /* LUT is full */ +} + +static void ls_pcie_lut_set_mapping(struct ls_pcie *pcie, int index, u32 devid, + u32 stream_id) +{ + /* leave mask as all zeroes, want to match all bits */ + lut_writel(pcie, devid << 16, PCIE_LUT_UDR(index)); + lut_writel(pcie, stream_id | PCIE_LUT_ENABLE, PCIE_LUT_LDR(index)); +} + +static bool ls_pcie_is_bridge(struct ls_pcie *pcie) +{ + struct dw_pcie *pci = &pcie->pci; + u32 header_type; + + header_type = ioread8(pci->dbi_base + PCI_HEADER_TYPE); + header_type &= 0x7f; + + return header_type == PCI_HEADER_TYPE_BRIDGE; +} + +/* Clear multi-function bit */ +static void ls_pcie_clear_multifunction(struct ls_pcie *pcie) +{ + struct dw_pcie *pci = &pcie->pci; + + iowrite8(PCI_HEADER_TYPE_BRIDGE, pci->dbi_base + PCI_HEADER_TYPE); +} + +/* Drop MSG TLP except for Vendor MSG */ +static void ls_pcie_drop_msg_tlp(struct ls_pcie *pcie) +{ + u32 val; + struct dw_pcie *pci = &pcie->pci; + + val = ioread32(pci->dbi_base + PCIE_STRFMR1); + val &= 0xDFFFFFFF; + iowrite32(val, pci->dbi_base + PCIE_STRFMR1); +} + +static void ls_pcie_disable_outbound_atus(struct ls_pcie *pcie) +{ + int i; + + for (i = 0; i < PCIE_IATU_NUM; i++) + dw_pcie_disable_atu(&pcie->pci, i, DW_PCIE_REGION_OUTBOUND); +} + +static int ls1021_pcie_link_up(struct dw_pcie *pci) +{ + u32 state; + struct ls_pcie *pcie = to_ls_pcie(pci); + + if (!pcie->scfg) + return 0; + + regmap_read(pcie->scfg, SCFG_PEXMSCPORTSR(pcie->index), &state); + state = (state >> LTSSM_STATE_SHIFT) & LTSSM_STATE_MASK; + + if (state < LTSSM_PCIE_L0) + return 0; + + return 1; +} + +static int ls_pcie_link_up(struct dw_pcie *pci) +{ + struct ls_pcie *pcie = to_ls_pcie(pci); + u32 state; + + state = (ioread32(pcie->lut + pcie->drvdata->lut_dbg) >> + pcie->drvdata->ltssm_shift) & + LTSSM_STATE_MASK; + + if (state < LTSSM_PCIE_L0) + return 0; + + return 1; +} + +/* Forward error response of outbound non-posted requests */ +static void ls_pcie_fix_error_response(struct ls_pcie *pcie) +{ + struct dw_pcie *pci = &pcie->pci; + + iowrite32(PCIE_ABSERR_SETTING, pci->dbi_base + PCIE_ABSERR); +} + +static int ls_pcie_host_init(struct pcie_port *pp) +{ + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); + struct ls_pcie *pcie = to_ls_pcie(pci); + + /* + * Disable outbound windows configured by the bootloader to avoid + * one transaction hitting multiple outbound windows. + * dw_pcie_setup_rc() will reconfigure the outbound windows. + */ + ls_pcie_disable_outbound_atus(pcie); + ls_pcie_fix_error_response(pcie); + + dw_pcie_dbi_ro_wr_en(pci); + ls_pcie_clear_multifunction(pcie); + dw_pcie_dbi_ro_wr_dis(pci); + + ls_pcie_drop_msg_tlp(pcie); + + dw_pcie_setup_rc(pp); + + return 0; +} + +static int ls1021_pcie_host_init(struct pcie_port *pp) +{ + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); + struct ls_pcie *pcie = to_ls_pcie(pci); + struct device_d *dev = pci->dev; + u32 index[2]; + int ret; + + pcie->scfg = syscon_regmap_lookup_by_phandle(dev->device_node, + "fsl,pcie-scfg"); + if (IS_ERR(pcie->scfg)) { + ret = PTR_ERR(pcie->scfg); + dev_err(dev, "No syscfg phandle specified\n"); + pcie->scfg = NULL; + return ret; + } + + if (of_property_read_u32_array(dev->device_node, + "fsl,pcie-scfg", index, 2)) { + pcie->scfg = NULL; + return -EINVAL; + } + pcie->index = index[1]; + + return ls_pcie_host_init(pp); +} + +static const struct dw_pcie_host_ops ls1021_pcie_host_ops = { + .host_init = ls1021_pcie_host_init, +}; + +static const struct dw_pcie_host_ops ls_pcie_host_ops = { + .host_init = ls_pcie_host_init, +}; + +static const struct dw_pcie_ops dw_ls1021_pcie_ops = { + .link_up = ls1021_pcie_link_up, +}; + +static const struct dw_pcie_ops dw_ls_pcie_ops = { + .link_up = ls_pcie_link_up, +}; + +static const struct ls_pcie_drvdata ls1021_drvdata = { + .ops = &ls1021_pcie_host_ops, + .dw_pcie_ops = &dw_ls1021_pcie_ops, +}; + +static const struct ls_pcie_drvdata ls1043_drvdata = { + .lut_offset = 0x10000, + .ltssm_shift = 24, + .lut_dbg = 0x7fc, + .pex_stream_id_start = 11, + .pex_stream_id_end = 26, + .ops = &ls_pcie_host_ops, + .dw_pcie_ops = &dw_ls_pcie_ops, +}; + +static const struct ls_pcie_drvdata ls1046_drvdata = { + .lut_offset = 0x80000, + .ltssm_shift = 24, + .lut_dbg = 0x407fc, + .pex_stream_id_start = 11, + .pex_stream_id_end = 26, + .ops = &ls_pcie_host_ops, + .dw_pcie_ops = &dw_ls_pcie_ops, +}; + +static const struct ls_pcie_drvdata ls2080_drvdata = { + .lut_offset = 0x80000, + .ltssm_shift = 0, + .lut_dbg = 0x7fc, + .pex_stream_id_start = 7, + .pex_stream_id_end = 22, + .ops = &ls_pcie_host_ops, + .dw_pcie_ops = &dw_ls_pcie_ops, +}; + +static const struct ls_pcie_drvdata ls2088_drvdata = { + .lut_offset = 0x80000, + .ltssm_shift = 0, + .lut_dbg = 0x407fc, + .pex_stream_id_start = 7, + .pex_stream_id_end = 18, + .ops = &ls_pcie_host_ops, + .dw_pcie_ops = &dw_ls_pcie_ops, +}; + +static const struct of_device_id ls_pcie_of_match[] = { + { .compatible = "fsl,ls1012a-pcie", .data = &ls1046_drvdata }, + { .compatible = "fsl,ls1021a-pcie", .data = &ls1021_drvdata }, + { .compatible = "fsl,ls1043a-pcie", .data = &ls1043_drvdata }, + { .compatible = "fsl,ls1046a-pcie", .data = &ls1046_drvdata }, + { .compatible = "fsl,ls2080a-pcie", .data = &ls2080_drvdata }, + { .compatible = "fsl,ls2085a-pcie", .data = &ls2080_drvdata }, + { .compatible = "fsl,ls2088a-pcie", .data = &ls2088_drvdata }, + { .compatible = "fsl,ls1088a-pcie", .data = &ls2088_drvdata }, + { }, +}; + +static int __init ls_add_pcie_port(struct ls_pcie *pcie) +{ + struct dw_pcie *pci = &pcie->pci; + struct pcie_port *pp = &pci->pp; + struct device_d *dev = pci->dev; + int ret; + + pp->ops = pcie->drvdata->ops; + + ret = dw_pcie_host_init(pp); + if (ret) { + dev_err(dev, "failed to initialize host\n"); + return ret; + } + + return 0; +} + +static int ls_pcie_of_fixup(struct device_node *root, void *ctx) +{ + struct ls_pcie *pcie = ctx; + struct device_d *dev = pcie->pci.dev; + struct device_node *np; + char *name; + u32 *arr, phandle; + int nluts; + int ret, i; + u32 devid, stream_id; + + name = of_get_reproducible_name(dev->device_node); + + np = root; + do { + np = of_find_node_by_reproducible_name(np, name); + if (!np) + return -ENODEV; + } while (!of_device_is_compatible(np, pcie->compatible)); + + nluts = pcie->next_lut_index; + + ret = of_property_read_u32(np, "msi-parent", &phandle); + if (ret) { + dev_err(pcie->pci.dev, "Unable to get \"msi-parent\" phandle\n"); + return ret; + } + + arr = xmalloc(nluts * sizeof(u32) * 4); + + for (i = 0; i < nluts; i++) { + u32 udr = lut_readl(pcie, PCIE_LUT_UDR(i)); + u32 ldr = lut_readl(pcie, PCIE_LUT_LDR(i)); + + if (!(ldr & PCIE_LUT_ENABLE)) + break; + + devid = udr >> 16; + stream_id = ldr & 0x7fff; + + arr[i * 4] = devid; + arr[i * 4 + 1] = phandle; + arr[i * 4 + 2] = stream_id; + arr[i * 4 + 3] = 1; + } + + /* + * An msi-map is a property to be added to the pci controller + * node. It is a table, where each entry consists of 4 fields + * e.g.: + * + * msi-map = <[devid] [phandle-to-msi-ctrl] [stream-id] [count] + * [devid] [phandle-to-msi-ctrl] [stream-id] [count]>; + */ + + ret = of_property_write_u32_array(np, "msi-map", arr, nluts * 4); + if (ret) + goto out; + + of_device_enable(np); + + ret = 0; + +out: + free(arr); + return ret; +} + +static int __init ls_pcie_probe(struct device_d *dev) +{ + struct dw_pcie *pci; + struct ls_pcie *pcie; + struct resource *dbi_base; + const struct of_device_id *match; + int ret; + + pcie = xzalloc(sizeof(*pcie)); + if (!pcie) + return -ENOMEM; + + pci = &pcie->pci; + + match = of_match_device(dev->driver->of_compatible, dev); + if (!match) + return -ENODEV; + + pcie->drvdata = match->data; + pcie->compatible = match->compatible; + + pci->dev = dev; + pci->ops = pcie->drvdata->dw_pcie_ops; + + dbi_base = dev_request_mem_resource(dev, 0); + if (IS_ERR(dbi_base)) + return PTR_ERR(dbi_base); + + pci->dbi_base = IOMEM(dbi_base->start); + + pcie->lut = pci->dbi_base + pcie->drvdata->lut_offset; + + if (!ls_pcie_is_bridge(pcie)) + return -ENODEV; + + dev->priv = pcie; + + ret = ls_add_pcie_port(pcie); + if (ret < 0) + return ret; + + of_register_fixup(ls_pcie_of_fixup, pcie); + + return 0; +} + +static struct driver_d ls_pcie_driver = { + .name = "layerscape-pcie", + .of_compatible = DRV_OF_COMPAT(ls_pcie_of_match), + .probe = ls_pcie_probe, +}; +device_platform_driver(ls_pcie_driver); + +static void ls_pcie_fixup(struct pci_dev *pcidev) +{ + struct pci_bus *bus = pcidev->bus, *p; + struct pci_controller *host = bus->host; + struct pcie_port *pp = container_of(host, struct pcie_port, pci); + struct dw_pcie *dwpcie = container_of(pp, struct dw_pcie, pp); + struct ls_pcie *lspcie = container_of(dwpcie, struct ls_pcie, pci); + int stream_id, index; + int base_bus_num = 0; + + stream_id = ls_pcie_next_streamid(lspcie); + index = ls_pcie_next_lut_index(lspcie); + + p = pcidev->bus; + while (p) { + base_bus_num = p->number; + p = p->parent_bus; + } + + ls_pcie_lut_set_mapping(lspcie, index, + (pcidev->bus->number - base_bus_num) << 8 | pcidev->devfn, + stream_id); +} + +DECLARE_PCI_FIXUP_EARLY(PCI_ANY_ID, PCI_ANY_ID, ls_pcie_fixup); -- 2.24.0 _______________________________________________ barebox mailing list barebox@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/barebox