325 lines
11 KiB
Diff
325 lines
11 KiB
Diff
From 5aa5769af625c79589fd84b8afc06149c2362218 Mon Sep 17 00:00:00 2001
|
|
From: Deepak Pandey <Deepak.Pandey@arm.com>
|
|
Date: Fri, 31 May 2019 16:42:43 +0100
|
|
Subject: [PATCH] pcie: Add quirk for the Arm Neoverse N1SDP platform
|
|
|
|
The Arm N1SDP SoC suffers from some PCIe integration issues, most
|
|
prominently config space accesses to not existing BDFs being answered
|
|
with a bus abort, resulting in an SError.
|
|
To mitigate this, the firmware scans the bus before boot (catching the
|
|
SErrors) and creates a table with valid BDFs, which acts as a filter for
|
|
Linux' config space accesses.
|
|
|
|
Add code consulting the table as an ACPI PCIe quirk, also register the
|
|
corresponding device tree based description of the host controller.
|
|
Also fix the other two minor issues on the way, namely not being fully
|
|
ECAM compliant and config space accesses being restricted to 32-bit
|
|
accesses only.
|
|
|
|
This allows the Arm Neoverse N1SDP board to boot Linux without crashing
|
|
and to access *any* devices (there are no platform devices except UART).
|
|
|
|
Signed-off-by: Deepak Pandey <Deepak.Pandey@arm.com>
|
|
[Sudipto: extend to cover the CCIX root port as well]
|
|
Signed-off-by: Sudipto Paul <sudipto.paul@arm.com>
|
|
[Andre: fix coding style issues, rewrite some parts, add DT support]
|
|
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
|
|
|
|
Change-Id: I1d3a4b9bf6b3b883d262e3c4ff1f88a0eb81c1fe
|
|
Upstream-Status: Inappropriate [will not be submitted as its a workaround to address hardware issue]
|
|
Signed-off-by: Deepak Pandey <Deepak.Pandey@arm.com>
|
|
Signed-off-by: Vishnu Banavath <vishnu.banavath@arm.com>
|
|
Signed-off-by: Adam Johnston <adam.johnston@arm.com>
|
|
---
|
|
arch/arm64/configs/defconfig | 1 +
|
|
drivers/acpi/pci_mcfg.c | 7 +
|
|
drivers/pci/controller/Kconfig | 11 ++
|
|
drivers/pci/controller/Makefile | 2 +-
|
|
drivers/pci/controller/pcie-n1sdp.c | 198 ++++++++++++++++++++++++++++
|
|
include/linux/pci-ecam.h | 2 +
|
|
6 files changed, 220 insertions(+), 1 deletion(-)
|
|
create mode 100644 drivers/pci/controller/pcie-n1sdp.c
|
|
|
|
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
|
|
index bbbc31391a65..973aa3b4d407 100644
|
|
--- a/arch/arm64/configs/defconfig
|
|
+++ b/arch/arm64/configs/defconfig
|
|
@@ -214,6 +214,7 @@ CONFIG_NFC_S3FWRN5_I2C=m
|
|
CONFIG_PCI=y
|
|
CONFIG_PCIEPORTBUS=y
|
|
CONFIG_PCIEAER=y
|
|
+CONFIG_PCI_QUIRKS=y
|
|
CONFIG_PCI_IOV=y
|
|
CONFIG_PCI_PASID=y
|
|
CONFIG_HOTPLUG_PCI=y
|
|
diff --git a/drivers/acpi/pci_mcfg.c b/drivers/acpi/pci_mcfg.c
|
|
index 860014b89b8e..2d4c1c699ffe 100644
|
|
--- a/drivers/acpi/pci_mcfg.c
|
|
+++ b/drivers/acpi/pci_mcfg.c
|
|
@@ -171,6 +171,13 @@ static struct mcfg_fixup mcfg_quirks[] = {
|
|
ALTRA_ECAM_QUIRK(1, 13),
|
|
ALTRA_ECAM_QUIRK(1, 14),
|
|
ALTRA_ECAM_QUIRK(1, 15),
|
|
+
|
|
+#define N1SDP_ECAM_MCFG(rev, seg, ops) \
|
|
+ {"ARMLTD", "ARMN1SDP", rev, seg, MCFG_BUS_ANY, ops }
|
|
+
|
|
+ /* N1SDP SoC with v1 PCIe controller */
|
|
+ N1SDP_ECAM_MCFG(0x20181101, 0, &pci_n1sdp_pcie_ecam_ops),
|
|
+ N1SDP_ECAM_MCFG(0x20181101, 1, &pci_n1sdp_ccix_ecam_ops),
|
|
#endif /* ARM64 */
|
|
|
|
#ifdef CONFIG_LOONGARCH
|
|
diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig
|
|
index bfd9bac37e24..7a65799dded7 100644
|
|
--- a/drivers/pci/controller/Kconfig
|
|
+++ b/drivers/pci/controller/Kconfig
|
|
@@ -50,6 +50,17 @@ config PCI_IXP4XX
|
|
Say Y here if you want support for the PCI host controller found
|
|
in the Intel IXP4xx XScale-based network processor SoC.
|
|
|
|
+config PCIE_HOST_N1SDP_ECAM
|
|
+ bool "ARM N1SDP PCIe Controller"
|
|
+ depends on ARM64
|
|
+ depends on OF || (ACPI && PCI_QUIRKS)
|
|
+ select PCI_HOST_COMMON
|
|
+ default y if ARCH_VEXPRESS
|
|
+ help
|
|
+ Say Y here if you want PCIe support for the Arm N1SDP platform.
|
|
+ The controller is ECAM compliant, but needs a quirk to workaround
|
|
+ an integration issue.
|
|
+
|
|
config PCI_TEGRA
|
|
bool "NVIDIA Tegra PCIe controller"
|
|
depends on ARCH_TEGRA || COMPILE_TEST
|
|
diff --git a/drivers/pci/controller/Makefile b/drivers/pci/controller/Makefile
|
|
index 37c8663de7fe..08e5afcf6e86 100644
|
|
--- a/drivers/pci/controller/Makefile
|
|
+++ b/drivers/pci/controller/Makefile
|
|
@@ -39,7 +39,7 @@ obj-$(CONFIG_PCI_LOONGSON) += pci-loongson.o
|
|
obj-$(CONFIG_PCIE_HISI_ERR) += pcie-hisi-error.o
|
|
obj-$(CONFIG_PCIE_APPLE) += pcie-apple.o
|
|
obj-$(CONFIG_PCIE_MT7621) += pcie-mt7621.o
|
|
-
|
|
+obj-$(CONFIG_PCIE_HOST_N1SDP_ECAM) += pcie-n1sdp.o
|
|
# pcie-hisi.o quirks are needed even without CONFIG_PCIE_DW
|
|
obj-y += dwc/
|
|
obj-y += mobiveil/
|
|
diff --git a/drivers/pci/controller/pcie-n1sdp.c b/drivers/pci/controller/pcie-n1sdp.c
|
|
new file mode 100644
|
|
index 000000000000..408699b9dcb1
|
|
--- /dev/null
|
|
+++ b/drivers/pci/controller/pcie-n1sdp.c
|
|
@@ -0,0 +1,198 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2018/2019 ARM Ltd.
|
|
+ *
|
|
+ * This quirk is to mask the following issues:
|
|
+ * - PCIE SLVERR: config space accesses to invalid PCIe BDFs cause a bus
|
|
+ * error (signalled as an asynchronous SError)
|
|
+ * - MCFG BDF mapping: the root complex is mapped separately from the device
|
|
+ * config space
|
|
+ * - Non 32-bit accesses to config space are not supported.
|
|
+ *
|
|
+ * At boot time the SCP board firmware creates a discovery table with
|
|
+ * the root complex' base address and the valid BDF values, discovered while
|
|
+ * scanning the config space and catching the SErrors.
|
|
+ * Linux responds only to the EPs listed in this table, returning NULL
|
|
+ * for the rest.
|
|
+ */
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/ioport.h>
|
|
+#include <linux/sizes.h>
|
|
+#include <linux/of_pci.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/pci-ecam.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/module.h>
|
|
+
|
|
+#include "../pci.h"
|
|
+
|
|
+/* Platform specific values as hardcoded in the firmware. */
|
|
+#define AP_NS_SHARED_MEM_BASE 0x06000000
|
|
+#define MAX_SEGMENTS 2 /* Two PCIe root complexes. */
|
|
+#define BDF_TABLE_SIZE SZ_16K
|
|
+
|
|
+/*
|
|
+ * Shared memory layout as written by the SCP upon boot time:
|
|
+ * ----
|
|
+ * Discover data header --> RC base address
|
|
+ * \-> BDF Count
|
|
+ * Discover data --> BDF 0...n
|
|
+ * ----
|
|
+ */
|
|
+struct pcie_discovery_data {
|
|
+ u32 rc_base_addr;
|
|
+ u32 nr_bdfs;
|
|
+ u32 valid_bdfs[0];
|
|
+} *pcie_discovery_data[MAX_SEGMENTS];
|
|
+
|
|
+void __iomem *rc_remapped_addr[MAX_SEGMENTS];
|
|
+
|
|
+/*
|
|
+ * map_bus() is called before we do a config space access for a certain
|
|
+ * device. We use this to check whether this device is valid, avoiding
|
|
+ * config space accesses which would result in an SError otherwise.
|
|
+ */
|
|
+static void __iomem *pci_n1sdp_map_bus(struct pci_bus *bus, unsigned int devfn,
|
|
+ int where)
|
|
+{
|
|
+ struct pci_config_window *cfg = bus->sysdata;
|
|
+ unsigned int devfn_shift = cfg->ops->bus_shift - 8;
|
|
+ unsigned int busn = bus->number;
|
|
+ unsigned int segment = bus->domain_nr;
|
|
+ unsigned int bdf_addr;
|
|
+ unsigned int table_count, i;
|
|
+ struct pci_dev *dev;
|
|
+
|
|
+ if (segment >= MAX_SEGMENTS ||
|
|
+ busn < cfg->busr.start || busn > cfg->busr.end)
|
|
+ return NULL;
|
|
+
|
|
+ /* The PCIe root complex has a separate config space mapping. */
|
|
+ if (busn == 0 && devfn == 0)
|
|
+ return rc_remapped_addr[segment] + where;
|
|
+
|
|
+ dev = pci_get_domain_bus_and_slot(segment, busn, devfn);
|
|
+ if (dev && dev->is_virtfn)
|
|
+ return pci_ecam_map_bus(bus, devfn, where);
|
|
+
|
|
+ /* Accesses beyond the vendor ID always go to existing devices. */
|
|
+ if (where > 0)
|
|
+ return pci_ecam_map_bus(bus, devfn, where);
|
|
+
|
|
+ busn -= cfg->busr.start;
|
|
+ bdf_addr = (busn << cfg->ops->bus_shift) + (devfn << devfn_shift);
|
|
+ table_count = pcie_discovery_data[segment]->nr_bdfs;
|
|
+ for (i = 0; i < table_count; i++) {
|
|
+ if (bdf_addr == pcie_discovery_data[segment]->valid_bdfs[i])
|
|
+ return pci_ecam_map_bus(bus, devfn, where);
|
|
+ }
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static int pci_n1sdp_init(struct pci_config_window *cfg, unsigned int segment)
|
|
+{
|
|
+ phys_addr_t table_base;
|
|
+ struct device *dev = cfg->parent;
|
|
+ struct pcie_discovery_data *shared_data;
|
|
+ size_t bdfs_size;
|
|
+
|
|
+ if (segment >= MAX_SEGMENTS)
|
|
+ return -ENODEV;
|
|
+
|
|
+ table_base = AP_NS_SHARED_MEM_BASE + segment * BDF_TABLE_SIZE;
|
|
+
|
|
+ if (!request_mem_region(table_base, BDF_TABLE_SIZE,
|
|
+ "PCIe valid BDFs")) {
|
|
+ dev_err(dev, "PCIe BDF shared region request failed\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ shared_data = devm_ioremap(dev,
|
|
+ table_base, BDF_TABLE_SIZE);
|
|
+ if (!shared_data)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ /* Copy the valid BDFs structure to allocated normal memory. */
|
|
+ bdfs_size = sizeof(struct pcie_discovery_data) +
|
|
+ sizeof(u32) * shared_data->nr_bdfs;
|
|
+ pcie_discovery_data[segment] = devm_kmalloc(dev, bdfs_size, GFP_KERNEL);
|
|
+ if (!pcie_discovery_data[segment])
|
|
+ return -ENOMEM;
|
|
+
|
|
+ memcpy_fromio(pcie_discovery_data[segment], shared_data, bdfs_size);
|
|
+
|
|
+ rc_remapped_addr[segment] = devm_ioremap(dev,
|
|
+ shared_data->rc_base_addr,
|
|
+ PCI_CFG_SPACE_EXP_SIZE);
|
|
+ if (!rc_remapped_addr[segment]) {
|
|
+ dev_err(dev, "Cannot remap root port base\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ devm_iounmap(dev, shared_data);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Called for ACPI segment 0, and for all segments when using DT. */
|
|
+static int pci_n1sdp_pcie_init(struct pci_config_window *cfg)
|
|
+{
|
|
+ struct platform_device *pdev = to_platform_device(cfg->parent);
|
|
+ int segment = 0;
|
|
+
|
|
+ if (pdev->dev.of_node)
|
|
+ segment = of_get_pci_domain_nr(pdev->dev.of_node);
|
|
+ if (segment < 0 || segment > MAX_SEGMENTS) {
|
|
+ dev_err(&pdev->dev, "N1SDP PCI controllers require linux,pci-domain property\n");
|
|
+ dev_err(&pdev->dev, "Or invalid segment number, must be smaller than %d\n",
|
|
+ MAX_SEGMENTS);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return pci_n1sdp_init(cfg, segment);
|
|
+}
|
|
+
|
|
+/* Called for ACPI segment 1. */
|
|
+static int pci_n1sdp_ccix_init(struct pci_config_window *cfg)
|
|
+{
|
|
+ return pci_n1sdp_init(cfg, 1);
|
|
+}
|
|
+
|
|
+const struct pci_ecam_ops pci_n1sdp_pcie_ecam_ops = {
|
|
+ .bus_shift = 20,
|
|
+ .init = pci_n1sdp_pcie_init,
|
|
+ .pci_ops = {
|
|
+ .map_bus = pci_n1sdp_map_bus,
|
|
+ .read = pci_generic_config_read32,
|
|
+ .write = pci_generic_config_write32,
|
|
+ }
|
|
+};
|
|
+
|
|
+const struct pci_ecam_ops pci_n1sdp_ccix_ecam_ops = {
|
|
+ .bus_shift = 20,
|
|
+ .init = pci_n1sdp_ccix_init,
|
|
+ .pci_ops = {
|
|
+ .map_bus = pci_n1sdp_map_bus,
|
|
+ .read = pci_generic_config_read32,
|
|
+ .write = pci_generic_config_write32,
|
|
+ }
|
|
+};
|
|
+
|
|
+static const struct of_device_id n1sdp_pcie_of_match[] = {
|
|
+ { .compatible = "arm,n1sdp-pcie", .data = &pci_n1sdp_pcie_ecam_ops },
|
|
+ { },
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, n1sdp_pcie_of_match);
|
|
+
|
|
+static struct platform_driver n1sdp_pcie_driver = {
|
|
+ .driver = {
|
|
+ .name = KBUILD_MODNAME,
|
|
+ .of_match_table = n1sdp_pcie_of_match,
|
|
+ .suppress_bind_attrs = true,
|
|
+ },
|
|
+ .probe = pci_host_common_probe,
|
|
+};
|
|
+builtin_platform_driver(n1sdp_pcie_driver);
|
|
diff --git a/include/linux/pci-ecam.h b/include/linux/pci-ecam.h
|
|
index 6b1301e2498e..b3cf3adeab28 100644
|
|
--- a/include/linux/pci-ecam.h
|
|
+++ b/include/linux/pci-ecam.h
|
|
@@ -88,6 +88,8 @@ extern const struct pci_ecam_ops xgene_v2_pcie_ecam_ops; /* APM X-Gene PCIe v2.x
|
|
extern const struct pci_ecam_ops al_pcie_ops; /* Amazon Annapurna Labs PCIe */
|
|
extern const struct pci_ecam_ops tegra194_pcie_ops; /* Tegra194 PCIe */
|
|
extern const struct pci_ecam_ops loongson_pci_ecam_ops; /* Loongson PCIe */
|
|
+extern const struct pci_ecam_ops pci_n1sdp_pcie_ecam_ops; /* Arm N1SDP PCIe */
|
|
+extern const struct pci_ecam_ops pci_n1sdp_ccix_ecam_ops; /* Arm N1SDP PCIe */
|
|
#endif
|
|
|
|
#if IS_ENABLED(CONFIG_PCI_HOST_COMMON)
|