[PATCH] pci: add ECAM generic controller support
Ahmad Fatoum
ahmad at a3f.at
Mon Nov 15 01:04:32 PST 2021
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 at a3f.at>
---
drivers/pci/Kconfig | 8 ++
drivers/pci/Makefile | 1 +
drivers/pci/pci-ecam-generic.c | 207 +++++++++++++++++++++++++++++++++
3 files changed, 216 insertions(+)
create mode 100644 drivers/pci/pci-ecam-generic.c
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index 60e8e93a0739..e847e8e1a3a2 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 60e1439ec7b2..b8a5c6392ad6 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 000000000000..ac2b6b9ea26c
--- /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);
--
2.33.0
More information about the barebox
mailing list