[PATCH v2 3/3] PCI: ARM: add support for generic PCI host controller

Will Deacon will.deacon at arm.com
Wed Feb 12 15:16:11 EST 2014


This patch adds support for a generic PCI host controller, such as a
firmware-initialised device with static windows or an emulation by
something such as kvmtool.

The controller itself has no configuration registers and has its address
spaces described entirely by the device-tree (using the bindings from
ePAPR). Both CAM and ECAM are supported for Config Space accesses.

Corresponding documentation is added for the DT binding.

Signed-off-by: Will Deacon <will.deacon at arm.com>
---
 .../devicetree/bindings/pci/arm-generic-pci.txt    |  51 ++++
 drivers/pci/host/Kconfig                           |   7 +
 drivers/pci/host/Makefile                          |   1 +
 drivers/pci/host/pci-arm-generic.c                 | 318 +++++++++++++++++++++
 4 files changed, 377 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/pci/arm-generic-pci.txt
 create mode 100644 drivers/pci/host/pci-arm-generic.c

diff --git a/Documentation/devicetree/bindings/pci/arm-generic-pci.txt b/Documentation/devicetree/bindings/pci/arm-generic-pci.txt
new file mode 100644
index 000000000000..cc7a35ecfa2d
--- /dev/null
+++ b/Documentation/devicetree/bindings/pci/arm-generic-pci.txt
@@ -0,0 +1,51 @@
+* ARM generic PCI host controller
+
+Firmware-initialised PCI host controllers and PCI emulations, such as the
+virtio-pci implementations found in kvmtool and other para-virtualised
+systems, do not require driver support for complexities such as regulator and
+clock management. In fact, the controller may not even require the
+configuration of a control interface by the operating system, instead
+presenting a set of fixed windows describing a subset of IO, Memory and
+Configuration Spaces.
+
+Such a controller can be described purely in terms of the standardized device
+tree bindings communicated in pci.txt:
+
+- compatible     : Must be "arm,pci-cam-generic" or "arm,pci-ecam-generic"
+                   depending on the layout of configuration space (CAM vs
+                   ECAM respectively)
+
+- ranges         : As described in IEEE Std 1275-1994, but must provide
+                   at least a definition of one or both of IO and Memory
+                   Space.
+
+- #address-cells : Must be 3
+
+- #size-cells    : Must be 2
+
+- reg            : The Configuration Space base address, as accessed by the
+                   parent bus.
+
+Configuration Space is assumed to be memory-mapped (as opposed to being
+accessed via an ioport) and laid out with a direct correspondence to the
+geography of a PCI bus address by concatenating the various components to form
+an offset.
+
+For CAM, this 24-bit offset is:
+
+        cfg_offset(bus, device, function, register) =
+                   bus << 16 | device << 11 | function << 8 | register
+
+Whilst ECAM extends this by 4 bits to accomodate 4k of function space:
+
+        cfg_offset(bus, device, function, register) =
+                   bus << 20 | device << 15 | function << 12 | register
+
+Interrupt mapping is exactly as described in `Open Firmware Recommended
+Practice: Interrupt Mapping' and requires the following properties:
+
+- #interrupt-cells   : Must be 1
+
+- interrupt-map      : <see aforementioned specification>
+
+- interrupt-map-mask : <see aforementioned specification>
diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig
index 47d46c6d8468..491d74c36f6a 100644
--- a/drivers/pci/host/Kconfig
+++ b/drivers/pci/host/Kconfig
@@ -33,4 +33,11 @@ config PCI_RCAR_GEN2
 	  There are 3 internal PCI controllers available with a single
 	  built-in EHCI/OHCI host controller present on each one.
 
+config PCI_ARM_GENERIC
+	bool "ARM generic PCI host controller"
+	depends on ARM && OF
+	help
+	  Say Y here if you want to support a simple generic PCI host
+	  controller, such as the one emulated by kvmtool.
+
 endmenu
diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile
index 13fb3333aa05..17f5555f8a29 100644
--- a/drivers/pci/host/Makefile
+++ b/drivers/pci/host/Makefile
@@ -4,3 +4,4 @@ obj-$(CONFIG_PCI_IMX6) += pci-imx6.o
 obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o
 obj-$(CONFIG_PCI_TEGRA) += pci-tegra.o
 obj-$(CONFIG_PCI_RCAR_GEN2) += pci-rcar-gen2.o
+obj-$(CONFIG_PCI_ARM_GENERIC) += pci-arm-generic.o
diff --git a/drivers/pci/host/pci-arm-generic.c b/drivers/pci/host/pci-arm-generic.c
new file mode 100644
index 000000000000..31ce03ee2607
--- /dev/null
+++ b/drivers/pci/host/pci-arm-generic.c
@@ -0,0 +1,318 @@
+/*
+ * Simple, generic PCI host controller driver targetting firmware-initialised
+ * systems and virtual machines (e.g. the PCI emulation provided by kvmtool).
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright (C) 2014 ARM Limited
+ *
+ * The current driver is limited to one I/O space per controller, and
+ * only supports a single controller.
+ *
+ * Author: Will Deacon <will.deacon at arm.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_pci.h>
+#include <linux/platform_device.h>
+
+struct gen_pci_cfg_accessors {
+	void __iomem *(*map_bus)(struct pci_bus *, unsigned int, int);
+	void (*unmap_bus)(struct pci_bus *);
+};
+
+struct gen_pci_cfg_window {
+	u64					cpu_phys;
+	void __iomem				*base;
+	u8					bus;
+	spinlock_t				lock;
+	const struct gen_pci_cfg_accessors	*accessors;
+};
+
+struct gen_pci_resource {
+	struct list_head			list;
+	struct resource				cpu_res;
+	resource_size_t				offset;
+};
+
+struct gen_pci {
+	struct device				*dev;
+	struct resource				*io_res;
+	struct list_head			mem_res;
+	struct gen_pci_cfg_window		cfg;
+};
+
+/*
+ * Configuration space accessors. We support CAM (simply map the entire
+ * 16M space) or ECAM (map a single 1M bus at a time).
+ */
+static void __iomem *gen_pci_map_cfg_bus_cam(struct pci_bus *bus,
+					     unsigned int devfn,
+					     int where)
+{
+	struct pci_sys_data *sys = bus->sysdata;
+	struct gen_pci *pci = sys->private_data;
+	u32 busn = bus->number;
+
+	return pci->cfg.base + ((busn << 16) | (devfn << 8) | where);
+}
+
+static void gen_pci_unmap_cfg_bus_cam(struct pci_bus *bus)
+{
+}
+
+static struct gen_pci_cfg_accessors gen_pci_cfg_cam_accessors = {
+	.map_bus	= gen_pci_map_cfg_bus_cam,
+	.unmap_bus	= gen_pci_unmap_cfg_bus_cam,
+};
+
+static void __iomem *gen_pci_map_cfg_bus_ecam(struct pci_bus *bus,
+					      unsigned int devfn,
+					      int where)
+{
+	struct pci_sys_data *sys = bus->sysdata;
+	struct gen_pci *pci = sys->private_data;
+	u32 busn = bus->number;
+
+	spin_lock(&pci->cfg.lock);
+	if (pci->cfg.bus != busn) {
+		resource_size_t offset;
+
+		devm_iounmap(pci->dev, pci->cfg.base);
+		offset = pci->cfg.cpu_phys + (busn << 20);
+		pci->cfg.base = devm_ioremap(pci->dev, offset, SZ_1M);
+		pci->cfg.bus = busn;
+	}
+
+	return pci->cfg.base + ((devfn << 12) | where);
+}
+
+static void gen_pci_unmap_cfg_bus_ecam(struct pci_bus *bus)
+{
+	struct pci_sys_data *sys = bus->sysdata;
+	struct gen_pci *pci = sys->private_data;
+
+	spin_unlock(&pci->cfg.lock);
+}
+
+static struct gen_pci_cfg_accessors gen_pci_cfg_ecam_accessors = {
+	.map_bus	= gen_pci_map_cfg_bus_ecam,
+	.unmap_bus	= gen_pci_unmap_cfg_bus_ecam,
+};
+
+static int gen_pci_config_read(struct pci_bus *bus, unsigned int devfn,
+				int where, int size, u32 *val)
+{
+	struct pci_sys_data *sys = bus->sysdata;
+	struct gen_pci *pci = sys->private_data;
+	void __iomem *addr = pci->cfg.accessors->map_bus(bus, devfn, where);
+
+	switch (size) {
+	case 1:
+		*val = readb(addr);
+		break;
+	case 2:
+		*val = readw(addr);
+		break;
+	default:
+		*val = readl(addr);
+	}
+
+	pci->cfg.accessors->unmap_bus(bus);
+	return PCIBIOS_SUCCESSFUL;
+}
+
+static int gen_pci_config_write(struct pci_bus *bus, unsigned int devfn,
+				 int where, int size, u32 val)
+{
+	struct pci_sys_data *sys = bus->sysdata;
+	struct gen_pci *pci = sys->private_data;
+	void __iomem *addr = pci->cfg.accessors->map_bus(bus, devfn, where);
+
+	switch (size) {
+	case 1:
+		writeb(val, addr);
+		break;
+	case 2:
+		writew(val, addr);
+		break;
+	default:
+		writel(val, addr);
+	}
+
+	pci->cfg.accessors->unmap_bus(bus);
+	return PCIBIOS_SUCCESSFUL;
+}
+
+static struct pci_ops gen_pci_ops = {
+	.read	= gen_pci_config_read,
+	.write	= gen_pci_config_write,
+};
+
+static int gen_pci_setup(int nr, struct pci_sys_data *sys)
+{
+	int err;
+	struct gen_pci_resource *res;
+	struct gen_pci *pci = sys->private_data;
+
+	/* Map an initial Configuration Space window */
+	pci->cfg.base = devm_ioremap(pci->dev, pci->cfg.cpu_phys, SZ_1M);
+	if (!pci->cfg.base)
+		return -ENOMEM;
+
+	/* Register our I/O space */
+	if (pci->io_res) {
+		resource_size_t offset = nr * SZ_64K;
+
+		err = request_resource(&ioport_resource, pci->io_res);
+		if (err)
+			goto out_unmap_cfg_space;
+
+		err = pci_ioremap_io(offset, pci->io_res->start);
+		if (err)
+			goto out_release_io_res;
+
+		pci_add_resource_offset(&sys->resources, pci->io_res, offset);
+	}
+
+	/* Register our memory resources */
+	list_for_each_entry(res, &pci->mem_res, list) {
+		err = request_resource(&iomem_resource, &res->cpu_res);
+		if (err)
+			goto out_release_mem_res;
+
+		pci_add_resource_offset(&sys->resources,
+					&res->cpu_res,
+					res->offset);
+	}
+
+	return 1;
+out_release_mem_res:
+	list_for_each_entry_continue_reverse(res, &pci->mem_res, list)
+		release_resource(&res->cpu_res);
+out_release_io_res:
+	release_resource(pci->io_res);
+out_unmap_cfg_space:
+	devm_iounmap(pci->dev, pci->cfg.base);
+	return err;
+}
+
+static const struct of_device_id gen_pci_of_match[] = {
+	{ .compatible = "arm,pci-cam-generic",
+	  .data = &gen_pci_cfg_cam_accessors },
+
+	{ .compatible = "arm,pci-ecam-generic",
+	  .data = &gen_pci_cfg_ecam_accessors },
+
+	{ },
+};
+MODULE_DEVICE_TABLE(of, gen_pci_of_match);
+
+static int gen_pci_probe(struct platform_device *pdev)
+{
+	struct hw_pci hw;
+	struct of_pci_range range;
+	struct of_pci_range_parser parser;
+	struct gen_pci *pci;
+	const __be32 *reg;
+	const struct of_device_id *of_id;
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+
+	if (!np)
+		return -ENODEV;
+
+	if (of_pci_range_parser_init(&parser, np)) {
+		dev_err(dev, "missing \"ranges\" property\n");
+		return -EINVAL;
+	}
+
+	reg = of_get_property(np, "reg", NULL);
+	if (!reg) {
+		dev_err(dev, "missing \"reg\" property\n");
+		return -EINVAL;
+	}
+
+	pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL);
+	if (!pci)
+		return -ENOMEM;
+
+	pci->cfg.cpu_phys = of_translate_address(np, reg);
+	if (pci->cfg.cpu_phys == OF_BAD_ADDR)
+		return -EINVAL;
+
+	of_id = of_match_node(gen_pci_of_match, np);
+	pci->cfg.accessors = of_id->data;
+	spin_lock_init(&pci->cfg.lock);
+	INIT_LIST_HEAD(&pci->mem_res);
+	pci->dev = dev;
+
+	for_each_of_pci_range(&parser, &range) {
+		struct gen_pci_resource *res;
+		u32 restype = range.flags & IORESOURCE_TYPE_BITS;
+
+		res = devm_kzalloc(dev, sizeof(*res), GFP_KERNEL);
+		if (!res)
+			return -ENOMEM;
+
+		INIT_LIST_HEAD(&res->list);
+		of_pci_range_to_resource(&range, np, &res->cpu_res);
+
+		switch (restype) {
+		case IORESOURCE_IO:
+			if (pci->io_res) {
+				dev_warn(dev,
+					 "ignoring additional io resource\n");
+				devm_kfree(dev, res);
+			} else {
+				pci->io_res = &res->cpu_res;
+			}
+			break;
+		case IORESOURCE_MEM:
+			res->offset = range.cpu_addr - range.pci_addr;
+			list_add(&res->list, &pci->mem_res);
+			break;
+		default:
+			dev_warn(dev,
+				"ignoring unknown/unsupported resource type %x\n",
+				 restype);
+		}
+	}
+
+	hw = (struct hw_pci) {
+		.nr_controllers	= 1,
+		.private_data	= (void **)&pci,
+		.setup		= gen_pci_setup,
+		.map_irq	= of_irq_parse_and_map_pci,
+		.ops		= &gen_pci_ops,
+	};
+
+	pci_common_init_dev(dev, &hw);
+	return 0;
+}
+
+static struct platform_driver gen_pci_driver = {
+	.driver = {
+		.name = "pci-arm-generic",
+		.owner = THIS_MODULE,
+		.of_match_table = gen_pci_of_match,
+	},
+	.probe = gen_pci_probe,
+};
+module_platform_driver(gen_pci_driver);
+
+MODULE_DESCRIPTION("ARM generic PCI host driver");
+MODULE_AUTHOR("Will Deacon <will.deacon at arm.com>");
+MODULE_LICENSE("GPLv2");
-- 
1.8.2.2




More information about the linux-arm-kernel mailing list