[PATCH V1 1/2] PCI: thunder: Enable ACPI PCI controller for ThunderX pass2.x silicon version

Tomasz Nowicki tn at semihalf.com
Tue Nov 15 01:14:57 PST 2016


ThunderX PCIe controller to off-chip devices (so-called PEM) is not fully
compliant with ECAM standard. It uses non-standard configuration space
accessors (see pci_thunder_pem_ops) and custom configuration space granulation
(see bus_shift = 24). In order to access configuration space and
probe PEM as ACPI based PCI host controller we need to add MCFG quirk
infrastructure. This involves:
1. thunder_pem_init() ACPI extension so that we can probe PEM-specific
   register ranges analogously to DT
2. Export PEM pci_thunder_pem_ops structure so it is visible to MCFG quirk
   code.
3. New quirk entries for each PEM segment. Each contains platform IDs,
   mentioned pci_thunder_pem_ops and CFG resources.

Quirk is considered for ThunderX silicon pass2.x only which is identified
via MCFG revision 1.

Signed-off-by: Tomasz Nowicki <tn at semihalf.com>
---
 drivers/acpi/pci_mcfg.c            |  20 +++++++
 drivers/pci/host/pci-thunder-pem.c | 107 ++++++++++++++++++++++++++++++++-----
 include/linux/pci-ecam.h           |   4 ++
 3 files changed, 117 insertions(+), 14 deletions(-)

diff --git a/drivers/acpi/pci_mcfg.c b/drivers/acpi/pci_mcfg.c
index ac21db3..e4e2b9b 100644
--- a/drivers/acpi/pci_mcfg.c
+++ b/drivers/acpi/pci_mcfg.c
@@ -57,6 +57,26 @@ static struct mcfg_fixup mcfg_quirks[] = {
 	{ "QCOM  ", "QDF2432 ", 1, 5, MCFG_BUS_ANY, &pci_32b_ops },
 	{ "QCOM  ", "QDF2432 ", 1, 6, MCFG_BUS_ANY, &pci_32b_ops },
 	{ "QCOM  ", "QDF2432 ", 1, 7, MCFG_BUS_ANY, &pci_32b_ops },
+#ifdef CONFIG_PCI_HOST_THUNDER_PEM
+#define THUNDER_MCFG_RES(addr, node) \
+	DEFINE_RES_MEM(addr + (node << 44), 0x39 * SZ_16M)
+#define THUNDER_MCFG_QUIRK(rev, node) \
+	{ "CAVIUM", "THUNDERX", rev, 4 + (10 * node), MCFG_BUS_ANY, \
+	  &pci_thunder_pem_ops, THUNDER_MCFG_RES(0x88001f000000UL, node) }, \
+	{ "CAVIUM", "THUNDERX", rev, 5 + (10 * node), MCFG_BUS_ANY, \
+	  &pci_thunder_pem_ops, THUNDER_MCFG_RES(0x884057000000UL, node) }, \
+	{ "CAVIUM", "THUNDERX", rev, 6 + (10 * node), MCFG_BUS_ANY, \
+	  &pci_thunder_pem_ops, THUNDER_MCFG_RES(0x88808f000000UL, node) }, \
+	{ "CAVIUM", "THUNDERX", rev, 7 + (10 * node), MCFG_BUS_ANY, \
+	  &pci_thunder_pem_ops, THUNDER_MCFG_RES(0x89001f000000UL, node) }, \
+	{ "CAVIUM", "THUNDERX", rev, 8 + (10 * node), MCFG_BUS_ANY, \
+	  &pci_thunder_pem_ops, THUNDER_MCFG_RES(0x894057000000UL, node) }, \
+	{ "CAVIUM", "THUNDERX", rev, 9 + (10 * node), MCFG_BUS_ANY, \
+	  &pci_thunder_pem_ops, THUNDER_MCFG_RES(0x89808f000000UL, node) }
+	/* SoC pass2.x */
+	THUNDER_MCFG_QUIRK(1, 0UL),
+	THUNDER_MCFG_QUIRK(1, 1UL),
+#endif
 };
 
 static char mcfg_oem_id[ACPI_OEM_ID_SIZE];
diff --git a/drivers/pci/host/pci-thunder-pem.c b/drivers/pci/host/pci-thunder-pem.c
index 6abaf80..7bdc4cd 100644
--- a/drivers/pci/host/pci-thunder-pem.c
+++ b/drivers/pci/host/pci-thunder-pem.c
@@ -18,6 +18,7 @@
 #include <linux/init.h>
 #include <linux/of_address.h>
 #include <linux/of_pci.h>
+#include <linux/pci-acpi.h>
 #include <linux/pci-ecam.h>
 #include <linux/platform_device.h>
 
@@ -284,6 +285,84 @@ static int thunder_pem_config_write(struct pci_bus *bus, unsigned int devfn,
 	return pci_generic_config_write(bus, devfn, where, size, val);
 }
 
+#ifdef CONFIG_ACPI
+
+/*
+ * Retrieve PEM bridge register base and size from PNP0C02 sub-device under
+ * the RC.
+ *
+ * Device (RES0)
+ * {
+ *	Name (_HID, "THRX0002")
+ *	Name (_CID, "PNP0C02")
+ *	Name (_CRS, ResourceTemplate () {
+ *		// Device specific registers range
+ *		QWordMemory(ResourceConsumer, PosDecode, MinFixed,
+ *		MaxFixed, Cacheable, ReadWrite, 0,
+ *		0x87e0c2000000, 0x87E0C2FFFFFF, 0, 0x1000000)
+ *	})
+ * }
+ */
+
+static const struct acpi_device_id thunder_pem_reg_ids[] = {
+	{"THRX0002", 0},
+	{"", 0},
+};
+
+static struct resource *thunder_pem_acpi_res(struct pci_config_window *cfg)
+{
+	struct device *dev = cfg->parent;
+	struct acpi_device *adev = to_acpi_device(dev);
+	struct acpi_device *child_adev;
+	struct resource *res_pem;
+
+	res_pem = devm_kzalloc(dev, sizeof(*res_pem), GFP_KERNEL);
+	if (!res_pem) {
+		dev_err(dev, "failed to allocate PEM resource\n");
+		return NULL;
+	}
+
+	list_for_each_entry(child_adev, &adev->children, node) {
+		struct resource_entry *entry;
+		struct list_head list;
+		unsigned long flags;
+		int ret;
+
+		ret = acpi_match_device_ids(child_adev, thunder_pem_reg_ids);
+		if (ret)
+			continue;
+
+		INIT_LIST_HEAD(&list);
+		flags = IORESOURCE_MEM;
+		ret = acpi_dev_get_resources(child_adev, &list,
+					     acpi_dev_filter_resource_type_cb,
+					     (void *)flags);
+		if (ret < 0) {
+			dev_err(&child_adev->dev,
+				"failed to parse _CRS method, error code %d\n",
+				ret);
+			return NULL;
+		} else if (ret == 0) {
+			dev_err(&child_adev->dev,
+				"no memory resources present in _CRS\n");
+			return NULL;
+		}
+
+		entry = list_first_entry(&list, struct resource_entry, node);
+		*res_pem = *entry->res;
+		acpi_dev_free_resource_list(&list);
+		return res_pem;
+	}
+
+	return NULL;
+}
+#else
+static struct resource *thunder_pem_acpi_res(struct pci_config_window *cfg)
+{
+	return NULL;
+}
+#endif
+
 static int thunder_pem_init(struct pci_config_window *cfg)
 {
 	struct device *dev = cfg->parent;
@@ -292,24 +371,24 @@ static int thunder_pem_init(struct pci_config_window *cfg)
 	struct thunder_pem_pci *pem_pci;
 	struct platform_device *pdev;
 
-	/* Only OF support for now */
-	if (!dev->of_node)
-		return -EINVAL;
-
 	pem_pci = devm_kzalloc(dev, sizeof(*pem_pci), GFP_KERNEL);
 	if (!pem_pci)
 		return -ENOMEM;
 
-	pdev = to_platform_device(dev);
-
-	/*
-	 * The second register range is the PEM bridge to the PCIe
-	 * bus.  It has a different config access method than those
-	 * devices behind the bridge.
-	 */
-	res_pem = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	if (acpi_disabled) {
+		pdev = to_platform_device(dev);
+
+		/*
+		 * The second register range is the PEM bridge to the PCIe
+		 * bus.  It has a different config access method than those
+		 * devices behind the bridge.
+		 */
+		res_pem = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	} else {
+		res_pem = thunder_pem_acpi_res(cfg);
+	}
 	if (!res_pem) {
-		dev_err(dev, "missing \"reg[1]\"property\n");
+		dev_err(dev, "missing configuration region\n");
 		return -EINVAL;
 	}
 
@@ -332,7 +411,7 @@ static int thunder_pem_init(struct pci_config_window *cfg)
 	return 0;
 }
 
-static struct pci_ecam_ops pci_thunder_pem_ops = {
+struct pci_ecam_ops pci_thunder_pem_ops = {
 	.bus_shift	= 24,
 	.init		= thunder_pem_init,
 	.pci_ops	= {
diff --git a/include/linux/pci-ecam.h b/include/linux/pci-ecam.h
index f5740b7..3f2a98f 100644
--- a/include/linux/pci-ecam.h
+++ b/include/linux/pci-ecam.h
@@ -58,6 +58,10 @@ void __iomem *pci_ecam_map_bus(struct pci_bus *bus, unsigned int devfn,
 			       int where);
 /* default ECAM ops */
 extern struct pci_ecam_ops pci_generic_ecam_ops;
+/* ECAM ops for known quirks */
+#ifdef CONFIG_PCI_HOST_THUNDER_PEM
+extern struct pci_ecam_ops pci_thunder_pem_ops;
+#endif
 
 /* ops for buggy ECAM that supports only 32-bit accesses */
 extern struct pci_ecam_ops pci_32b_ops;
-- 
2.7.4




More information about the linux-arm-kernel mailing list