summaryrefslogtreecommitdiff
path: root/drivers/pci
diff options
context:
space:
mode:
authorAhmad Fatoum <ahmad@a3f.at>2021-11-15 10:04:32 +0100
committerSascha Hauer <s.hauer@pengutronix.de>2021-11-17 07:39:32 +0100
commit069d7f2bf00be907880cdf7ef4e620d7c5b588db (patch)
tree4ee613ef0a44b167d06a9a5db874667e528084d4 /drivers/pci
parent81ceab95360295cef146e89a1cd1cd5e590aa75e (diff)
downloadbarebox-069d7f2bf00be907880cdf7ef4e620d7c5b588db.tar.gz
pci: add ECAM generic controller support
This has been tested with QEMU AArch64 Virt. The default (-M virt,highmem=on) will have two ranges specified in the device trees for memory bars. One 32-bit and the other 64-bit. As barebox can't yet handle 64-bit BARs, the driver will prefer the 32-bit memory region if available. If none is available, consider using -M virt,highmem=off or fixing 64-bit support. Signed-off-by: Ahmad Fatoum <ahmad@a3f.at> Link: https://lore.barebox.org/20211115090432.147448-1-ahmad@a3f.at Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'drivers/pci')
-rw-r--r--drivers/pci/Kconfig8
-rw-r--r--drivers/pci/Makefile1
-rw-r--r--drivers/pci/pci-ecam-generic.c207
3 files changed, 216 insertions, 0 deletions
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index 60e8e93a07..e847e8e1a3 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -60,6 +60,14 @@ config PCI_EFI
depends on EFI_BOOTUP
select PCI
+config PCI_ECAM_GENERIC
+ bool "Generic ECAM-based PCI host controller support"
+ select OF_PCI
+ select PCI
+ help
+ Say Y here if you want to enable support for generic ECAM-based
+ PCI host controllers, such as the one emulated by QEMU.
+
endmenu
endif
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 60e1439ec7..b8a5c6392a 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -12,3 +12,4 @@ 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
obj-$(CONFIG_PCI_EFI) += pci-efi.o
+obj-$(CONFIG_PCI_ECAM_GENERIC) += pci-ecam-generic.o
diff --git a/drivers/pci/pci-ecam-generic.c b/drivers/pci/pci-ecam-generic.c
new file mode 100644
index 0000000000..ac2b6b9ea2
--- /dev/null
+++ b/drivers/pci/pci-ecam-generic.c
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Generic PCIE host provided by e.g. QEMU
+ *
+ * Heavily based on drivers/pci/pcie_xilinx.c
+ *
+ * Copyright (C) 2016 Imagination Technologies
+ */
+
+#include <common.h>
+#include <malloc.h>
+#include <io.h>
+#include <of.h>
+#include <of_address.h>
+#include <init.h>
+#include <linux/pci.h>
+#include <linux/sizes.h>
+
+struct generic_ecam_pcie {
+ struct pci_controller pci;
+ struct resource *cfg;
+ int first_busno;
+ struct resource io;
+ struct resource mem;
+ struct resource prefetch;
+};
+
+static inline struct generic_ecam_pcie *host_to_ecam(struct pci_controller *host)
+{
+ return container_of(host, struct generic_ecam_pcie, pci);
+}
+
+static void __iomem *pci_generic_ecam_conf_address(const struct pci_bus *bus,
+ u32 devfn, int where)
+{
+ struct generic_ecam_pcie *ecam = host_to_ecam(bus->host);
+ void __iomem *addr;
+
+ addr = IOMEM(ecam->cfg->start);
+ addr += (bus->number - ecam->first_busno) << 20;
+ addr += PCI_SLOT(devfn) << 15;
+ addr += PCI_FUNC(devfn) << 12;
+ addr += where;
+
+ return addr;
+}
+
+static bool pci_generic_ecam_addr_valid(const struct pci_bus *bus, u32 devfn)
+{
+ struct generic_ecam_pcie *ecam = host_to_ecam(bus->host);
+ int num_buses = DIV_ROUND_UP(resource_size(ecam->cfg), 1 << 16);
+
+ return (bus->number >= ecam->first_busno &&
+ bus->number < ecam->first_busno + num_buses);
+}
+
+static int pci_generic_ecam_read_config(struct pci_bus *bus,
+ u32 devfn, int where,
+ int size, u32 *val)
+{
+ void __iomem *addr;
+
+ if (!pci_generic_ecam_addr_valid(bus, devfn))
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ addr = pci_generic_ecam_conf_address(bus, devfn, where);
+
+ if (!IS_ALIGNED((uintptr_t)addr, size)) {
+ *val = 0;
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+ }
+
+ if (size == 4) {
+ *val = readl(addr);
+ } else if (size == 2) {
+ *val = readw(addr);
+ } else if (size == 1) {
+ *val = readb(addr);
+ } else {
+ *val = 0;
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+ }
+
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static int pci_generic_ecam_write_config(struct pci_bus *bus, u32 devfn,
+ int where, int size, u32 val)
+{
+ void __iomem *addr;
+
+ if (!pci_generic_ecam_addr_valid(bus, devfn))
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ addr = pci_generic_ecam_conf_address(bus, devfn, where);
+
+ if (!IS_ALIGNED((uintptr_t)addr, size))
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+
+ if (size == 4)
+ writel(val, addr);
+ else if (size == 2)
+ writew(val, addr);
+ else if (size == 1)
+ writeb(val, addr);
+ else
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static void pcie_ecam_set_local_bus_nr(struct pci_controller *host, int busno)
+{
+ struct generic_ecam_pcie *ecam = host_to_ecam(host);
+
+ ecam->first_busno = busno;
+}
+
+static const struct pci_ops pci_generic_ecam_ops = {
+ .read = pci_generic_ecam_read_config,
+ .write = pci_generic_ecam_write_config,
+};
+
+static inline bool is_64bit(const struct resource *res)
+{
+ return res->flags & IORESOURCE_MEM_64;
+}
+
+static int pcie_ecam_parse_dt(struct generic_ecam_pcie *ecam)
+{
+ struct device_d *dev = ecam->pci.parent;
+ struct device_node *np = dev->device_node;
+ struct of_pci_range_parser parser;
+ struct of_pci_range range;
+ struct resource res;
+
+ if (of_pci_range_parser_init(&parser, np)) {
+ dev_err(dev, "missing \"ranges\" property\n");
+ return -EINVAL;
+ }
+
+ for_each_of_pci_range(&parser, &range) {
+ of_pci_range_to_resource(&range, np, &res);
+
+ switch (res.flags & IORESOURCE_TYPE_BITS) {
+ case IORESOURCE_IO:
+ memcpy(&ecam->io, &res, sizeof(res));
+ ecam->io.name = "I/O";
+ break;
+
+ case IORESOURCE_MEM:
+ if (res.flags & IORESOURCE_PREFETCH) {
+ memcpy(&ecam->prefetch, &res, sizeof(res));
+ ecam->prefetch.name = "PREFETCH";
+ } else {
+ /* Choose 32-bit mappings over 64-bit ones if possible */
+ if (ecam->mem.name && !is_64bit(&ecam->mem) && is_64bit(&res))
+ break;
+
+ memcpy(&ecam->mem, &res, sizeof(res));
+ ecam->mem.name = "MEM";
+ }
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int pcie_ecam_probe(struct device_d *dev)
+{
+ struct generic_ecam_pcie *ecam;
+ struct resource *iores;
+ int ret;
+
+ iores = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(iores))
+ return PTR_ERR(iores);
+
+ ecam = xzalloc(sizeof(*ecam));
+ ecam->cfg = iores;
+ ecam->pci.parent = dev;
+ ecam->pci.pci_ops = &pci_generic_ecam_ops;
+ ecam->pci.set_busno = pcie_ecam_set_local_bus_nr;
+ ecam->pci.mem_resource = &ecam->mem;
+ ecam->pci.io_resource = &ecam->io;
+ ecam->pci.mem_pref_resource = &ecam->prefetch;
+
+ ret = pcie_ecam_parse_dt(ecam);
+ if (ret)
+ return ret;
+
+ register_pci_controller(&ecam->pci);
+ return 0;
+}
+
+static struct of_device_id pcie_ecam_dt_ids[] = {
+ { .compatible = "pci-host-ecam-generic" },
+ { /* sentinel */ },
+};
+
+static struct driver_d pcie_ecam_driver = {
+ .name = "pcie-generic-ecam",
+ .probe = pcie_ecam_probe,
+ .of_compatible = pcie_ecam_dt_ids,
+};
+device_platform_driver(pcie_ecam_driver);