summaryrefslogtreecommitdiffstats
path: root/arm64-pcie-quirks-xgene.patch
diff options
context:
space:
mode:
Diffstat (limited to 'arm64-pcie-quirks-xgene.patch')
-rw-r--r--arm64-pcie-quirks-xgene.patch508
1 files changed, 508 insertions, 0 deletions
diff --git a/arm64-pcie-quirks-xgene.patch b/arm64-pcie-quirks-xgene.patch
new file mode 100644
index 000000000..8e6805df6
--- /dev/null
+++ b/arm64-pcie-quirks-xgene.patch
@@ -0,0 +1,508 @@
+From 767b70aa55d013f0c7589955f410d488fed5776a Mon Sep 17 00:00:00 2001
+From: Peter Robinson <pbrobinson@gmail.com>
+Date: Tue, 5 Jul 2016 23:49:39 +0100
+Subject: [PATCH 1/4] Some platforms may not be fully compliant with generic
+ set of PCI config accessors. For these cases we implement the way to
+ overwrite accessors set. Algorithm traverses available quirk list, matches
+ against <oem_id, oem_table_id, domain, bus number> tuple and returns
+ corresponding PCI config ops. oem_id and oem_table_id come from MCFG table
+ standard header. All quirks can be defined using DECLARE_ACPI_MCFG_FIXUP()
+ macro and kept self contained. Example:
+
+/* Custom PCI config ops */
+static struct pci_generic_ecam_ops foo_pci_ops = {
+ .bus_shift = 24,
+ .pci_ops = {
+ .map_bus = pci_ecam_map_bus,
+ .read = foo_ecam_config_read,
+ .write = foo_ecam_config_write,
+ }
+};
+
+DECLARE_ACPI_MCFG_FIXUP(&foo_pci_ops, <oem_id_str>, <oem_table_id>, <domain_nr>, <bus_nr>);
+
+Signed-off-by: Tomasz Nowicki <tn@semihalf.com>
+Signed-off-by: Dongdong Liu <liudongdong3@huawei.com>
+---
+ drivers/acpi/pci_mcfg.c | 41 ++++++++++++++++++++++++++++++++++++---
+ include/asm-generic/vmlinux.lds.h | 7 +++++++
+ include/linux/pci-acpi.h | 20 +++++++++++++++++++
+ 3 files changed, 65 insertions(+), 3 deletions(-)
+
+diff --git a/drivers/acpi/pci_mcfg.c b/drivers/acpi/pci_mcfg.c
+index d3c3e85..deb0077 100644
+--- a/drivers/acpi/pci_mcfg.c
++++ b/drivers/acpi/pci_mcfg.c
+@@ -22,6 +22,10 @@
+ #include <linux/kernel.h>
+ #include <linux/pci.h>
+ #include <linux/pci-acpi.h>
++#include <linux/pci-ecam.h>
++
++/* Root pointer to the mapped MCFG table */
++static struct acpi_table_mcfg *mcfg_table;
+
+ /* Structure to hold entries from the MCFG table */
+ struct mcfg_entry {
+@@ -35,6 +39,38 @@ struct mcfg_entry {
+ /* List to save mcfg entries */
+ static LIST_HEAD(pci_mcfg_list);
+
++extern struct pci_cfg_fixup __start_acpi_mcfg_fixups[];
++extern struct pci_cfg_fixup __end_acpi_mcfg_fixups[];
++
++struct pci_ecam_ops *pci_mcfg_get_ops(struct acpi_pci_root *root)
++{
++ int bus_num = root->secondary.start;
++ int domain = root->segment;
++ struct pci_cfg_fixup *f;
++
++ if (!mcfg_table)
++ return &pci_generic_ecam_ops;
++
++ /*
++ * Match against platform specific quirks and return corresponding
++ * CAM ops.
++ *
++ * First match against PCI topology <domain:bus> then use OEM ID and
++ * OEM revision from MCFG table standard header.
++ */
++ for (f = __start_acpi_mcfg_fixups; f < __end_acpi_mcfg_fixups; f++) {
++ if ((f->domain == domain || f->domain == PCI_MCFG_DOMAIN_ANY) &&
++ (f->bus_num == bus_num || f->bus_num == PCI_MCFG_BUS_ANY) &&
++ (!strncmp(f->oem_id, mcfg_table->header.oem_id,
++ ACPI_OEM_ID_SIZE)) &&
++ (!strncmp(f->oem_table_id, mcfg_table->header.oem_table_id,
++ ACPI_OEM_TABLE_ID_SIZE)))
++ return f->ops;
++ }
++ /* No quirks, use ECAM */
++ return &pci_generic_ecam_ops;
++}
++
+ phys_addr_t pci_mcfg_lookup(u16 seg, struct resource *bus_res)
+ {
+ struct mcfg_entry *e;
+@@ -54,7 +90,6 @@ phys_addr_t pci_mcfg_lookup(u16 seg, struct resource *bus_res)
+
+ static __init int pci_mcfg_parse(struct acpi_table_header *header)
+ {
+- struct acpi_table_mcfg *mcfg;
+ struct acpi_mcfg_allocation *mptr;
+ struct mcfg_entry *e, *arr;
+ int i, n;
+@@ -64,8 +99,8 @@ static __init int pci_mcfg_parse(struct acpi_table_header *header)
+
+ n = (header->length - sizeof(struct acpi_table_mcfg)) /
+ sizeof(struct acpi_mcfg_allocation);
+- mcfg = (struct acpi_table_mcfg *)header;
+- mptr = (struct acpi_mcfg_allocation *) &mcfg[1];
++ mcfg_table = (struct acpi_table_mcfg *)header;
++ mptr = (struct acpi_mcfg_allocation *) &mcfg_table[1];
+
+ arr = kcalloc(n, sizeof(*arr), GFP_KERNEL);
+ if (!arr)
+diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
+index 6a67ab9..43604fc 100644
+--- a/include/asm-generic/vmlinux.lds.h
++++ b/include/asm-generic/vmlinux.lds.h
+@@ -300,6 +300,13 @@
+ VMLINUX_SYMBOL(__end_pci_fixups_suspend_late) = .; \
+ } \
+ \
++ /* ACPI MCFG quirks */ \
++ .acpi_fixup : AT(ADDR(.acpi_fixup) - LOAD_OFFSET) { \
++ VMLINUX_SYMBOL(__start_acpi_mcfg_fixups) = .; \
++ *(.acpi_fixup_mcfg) \
++ VMLINUX_SYMBOL(__end_acpi_mcfg_fixups) = .; \
++ } \
++ \
+ /* Built-in firmware blobs */ \
+ .builtin_fw : AT(ADDR(.builtin_fw) - LOAD_OFFSET) { \
+ VMLINUX_SYMBOL(__start_builtin_fw) = .; \
+diff --git a/include/linux/pci-acpi.h b/include/linux/pci-acpi.h
+index 7d63a66..c8a6559 100644
+--- a/include/linux/pci-acpi.h
++++ b/include/linux/pci-acpi.h
+@@ -25,6 +25,7 @@ static inline acpi_status pci_acpi_remove_pm_notifier(struct acpi_device *dev)
+ extern phys_addr_t acpi_pci_root_get_mcfg_addr(acpi_handle handle);
+
+ extern phys_addr_t pci_mcfg_lookup(u16 domain, struct resource *bus_res);
++extern struct pci_ecam_ops *pci_mcfg_get_ops(struct acpi_pci_root *root);
+
+ static inline acpi_handle acpi_find_root_bridge_handle(struct pci_dev *pdev)
+ {
+@@ -72,6 +73,25 @@ struct acpi_pci_root_ops {
+ int (*prepare_resources)(struct acpi_pci_root_info *info);
+ };
+
++struct pci_cfg_fixup {
++ struct pci_ecam_ops *ops;
++ char *oem_id;
++ char *oem_table_id;
++ int domain;
++ int bus_num;
++};
++
++#define PCI_MCFG_DOMAIN_ANY -1
++#define PCI_MCFG_BUS_ANY -1
++
++/* Designate a routine to fix up buggy MCFG */
++#define DECLARE_ACPI_MCFG_FIXUP(ops, oem_id, oem_table_id, dom, bus) \
++ static const struct pci_cfg_fixup \
++ __mcfg_fixup_##oem_id##oem_table_id##dom##bus \
++ __used __attribute__((__section__(".acpi_fixup_mcfg"), \
++ aligned((sizeof(void *))))) = \
++ { ops, oem_id, oem_table_id, dom, bus };
++
+ extern int acpi_pci_probe_root_resources(struct acpi_pci_root_info *info);
+ extern struct pci_bus *acpi_pci_root_create(struct acpi_pci_root *root,
+ struct acpi_pci_root_ops *ops,
+--
+2.7.4
+
+From 4f86a9b006b25dd7336043dab26058ed6fb2802d Mon Sep 17 00:00:00 2001
+From: Peter Robinson <pbrobinson@gmail.com>
+Date: Tue, 5 Jul 2016 23:52:46 +0100
+Subject: [PATCH 2/4] pci_generic_ecam_ops is used by default. Since there are
+ platforms which have non-compliant ECAM space we need to overwrite these
+ accessors prior to PCI buses enumeration. In order to do that we call
+ pci_mcfg_get_ops to retrieve pci_ecam_ops structure so that we can use proper
+ PCI config space accessors and bus_shift.
+
+pci_generic_ecam_ops is still used for platforms free from quirks.
+
+Signed-off-by: Tomasz Nowicki <tn@semihalf.com>
+---
+ arch/arm64/kernel/pci.c | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+diff --git a/arch/arm64/kernel/pci.c b/arch/arm64/kernel/pci.c
+index 94cd43c..a891bda 100644
+--- a/arch/arm64/kernel/pci.c
++++ b/arch/arm64/kernel/pci.c
+@@ -139,6 +139,7 @@ pci_acpi_setup_ecam_mapping(struct acpi_pci_root *root)
+ struct pci_config_window *cfg;
+ struct resource cfgres;
+ unsigned int bsz;
++ struct pci_ecam_ops *ops;
+
+ /* Use address from _CBA if present, otherwise lookup MCFG */
+ if (!root->mcfg_addr)
+@@ -150,12 +151,12 @@ pci_acpi_setup_ecam_mapping(struct acpi_pci_root *root)
+ return NULL;
+ }
+
+- bsz = 1 << pci_generic_ecam_ops.bus_shift;
++ ops = pci_mcfg_get_ops(root);
++ bsz = 1 << ops->bus_shift;
+ cfgres.start = root->mcfg_addr + bus_res->start * bsz;
+ cfgres.end = cfgres.start + resource_size(bus_res) * bsz - 1;
+ cfgres.flags = IORESOURCE_MEM;
+- cfg = pci_ecam_create(&root->device->dev, &cfgres, bus_res,
+- &pci_generic_ecam_ops);
++ cfg = pci_ecam_create(&root->device->dev, &cfgres, bus_res, ops);
+ if (IS_ERR(cfg)) {
+ dev_err(&root->device->dev, "%04x:%pR error %ld mapping ECAM\n",
+ seg, bus_res, PTR_ERR(cfg));
+--
+2.7.4
+
+From cbdbd697bd6d716eb9d1705ee55445432e73eabb Mon Sep 17 00:00:00 2001
+From: Peter Robinson <pbrobinson@gmail.com>
+Date: Tue, 5 Jul 2016 23:53:59 +0100
+Subject: [PATCH 3/4] The ECAM quirk matching criteria per the discussion on
+ https://lkml.org/lkml/2016/6/13/944 includes: OEM ID, OEM Table ID and OEM
+ Revision. So this patch adds OEM Table ID into the check to match platform
+ specific ECAM quirks as well.
+
+This patch also improve strncmp check using strlen and
+min_t to ignore the padding spaces in OEM ID and OEM
+Table ID.
+
+Signed-off-by: Duc Dang <dhdang@apm.com>
+---
+ drivers/acpi/pci_mcfg.c | 7 +++++--
+ include/linux/pci-acpi.h | 7 ++++---
+ 2 files changed, 9 insertions(+), 5 deletions(-)
+
+diff --git a/drivers/acpi/pci_mcfg.c b/drivers/acpi/pci_mcfg.c
+index deb0077..307ca9a 100644
+--- a/drivers/acpi/pci_mcfg.c
++++ b/drivers/acpi/pci_mcfg.c
+@@ -62,9 +62,12 @@ struct pci_ecam_ops *pci_mcfg_get_ops(struct acpi_pci_root *root)
+ if ((f->domain == domain || f->domain == PCI_MCFG_DOMAIN_ANY) &&
+ (f->bus_num == bus_num || f->bus_num == PCI_MCFG_BUS_ANY) &&
+ (!strncmp(f->oem_id, mcfg_table->header.oem_id,
+- ACPI_OEM_ID_SIZE)) &&
++ min_t(size_t, strlen(f->oem_id),
++ ACPI_OEM_ID_SIZE))) &&
+ (!strncmp(f->oem_table_id, mcfg_table->header.oem_table_id,
+- ACPI_OEM_TABLE_ID_SIZE)))
++ min_t(size_t, strlen(f->oem_table_id),
++ ACPI_OEM_TABLE_ID_SIZE))) &&
++ (f->oem_revision == mcfg_table->header.oem_revision))
+ return f->ops;
+ }
+ /* No quirks, use ECAM */
+diff --git a/include/linux/pci-acpi.h b/include/linux/pci-acpi.h
+index c8a6559..5148c8d 100644
+--- a/include/linux/pci-acpi.h
++++ b/include/linux/pci-acpi.h
+@@ -77,6 +77,7 @@ struct pci_cfg_fixup {
+ struct pci_ecam_ops *ops;
+ char *oem_id;
+ char *oem_table_id;
++ u32 oem_revision;
+ int domain;
+ int bus_num;
+ };
+@@ -85,12 +86,12 @@ struct pci_cfg_fixup {
+ #define PCI_MCFG_BUS_ANY -1
+
+ /* Designate a routine to fix up buggy MCFG */
+-#define DECLARE_ACPI_MCFG_FIXUP(ops, oem_id, oem_table_id, dom, bus) \
++#define DECLARE_ACPI_MCFG_FIXUP(ops, oem_id, oem_table_id, rev, dom, bus) \
+ static const struct pci_cfg_fixup \
+- __mcfg_fixup_##oem_id##oem_table_id##dom##bus \
++ __mcfg_fixup_##oem_id##oem_table_id##rev##dom##bus \
+ __used __attribute__((__section__(".acpi_fixup_mcfg"), \
+ aligned((sizeof(void *))))) = \
+- { ops, oem_id, oem_table_id, dom, bus };
++ { ops, oem_id, oem_table_id, rev, dom, bus };
+
+ extern int acpi_pci_probe_root_resources(struct acpi_pci_root_info *info);
+ extern struct pci_bus *acpi_pci_root_create(struct acpi_pci_root *root,
+--
+2.7.4
+
+From 78766cf255bc6aafac2f57372a0446f78322da19 Mon Sep 17 00:00:00 2001
+From: Peter Robinson <pbrobinson@gmail.com>
+Date: Tue, 5 Jul 2016 23:55:11 +0100
+Subject: [PATCH 4/4] X-Gene PCIe controller does not fully support ECAM. This
+ patch adds required ECAM fixup to allow X-Gene PCIe controller to be
+ functional in ACPI boot mode.
+
+Signed-off-by: Duc Dang <dhdang@apm.com>
+---
+ drivers/pci/host/Makefile | 2 +-
+ drivers/pci/host/pci-xgene-ecam.c | 194 ++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 195 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/pci/host/pci-xgene-ecam.c
+
+diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile
+index 9c8698e..3480696 100644
+--- a/drivers/pci/host/Makefile
++++ b/drivers/pci/host/Makefile
+@@ -14,7 +14,7 @@ obj-$(CONFIG_PCIE_SPEAR13XX) += pcie-spear13xx.o
+ obj-$(CONFIG_PCI_KEYSTONE) += pci-keystone-dw.o pci-keystone.o
+ obj-$(CONFIG_PCIE_XILINX) += pcie-xilinx.o
+ obj-$(CONFIG_PCIE_XILINX_NWL) += pcie-xilinx-nwl.o
+-obj-$(CONFIG_PCI_XGENE) += pci-xgene.o
++obj-$(CONFIG_PCI_XGENE) += pci-xgene.o pci-xgene-ecam.o
+ obj-$(CONFIG_PCI_XGENE_MSI) += pci-xgene-msi.o
+ obj-$(CONFIG_PCI_LAYERSCAPE) += pci-layerscape.o
+ obj-$(CONFIG_PCI_VERSATILE) += pci-versatile.o
+diff --git a/drivers/pci/host/pci-xgene-ecam.c b/drivers/pci/host/pci-xgene-ecam.c
+new file mode 100644
+index 0000000..1bea63f
+--- /dev/null
++++ b/drivers/pci/host/pci-xgene-ecam.c
+@@ -0,0 +1,194 @@
++/*
++ * APM X-Gene PCIe ECAM fixup driver
++ *
++ * Copyright (c) 2016, Applied Micro Circuits Corporation
++ * Author:
++ * Duc Dang <dhdang@apm.com>
++ *
++ * 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/>.
++ */
++
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/of_address.h>
++#include <linux/of_pci.h>
++#include <linux/pci-acpi.h>
++#include <linux/platform_device.h>
++#include <linux/pci-ecam.h>
++
++#ifdef CONFIG_ACPI
++#define RTDID 0x160
++#define ROOT_CAP_AND_CTRL 0x5C
++
++/* PCIe IP version */
++#define XGENE_PCIE_IP_VER_UNKN 0
++#define XGENE_PCIE_IP_VER_1 1
++
++#define APM_OEM_ID "APM"
++#define APM_XGENE_OEM_TABLE_ID "XGENE"
++#define APM_XGENE_OEM_REV 0x00000002
++
++struct xgene_pcie_acpi_root {
++ void __iomem *csr_base;
++ u32 version;
++};
++
++static acpi_status xgene_pcie_find_csr_base(struct acpi_resource *acpi_res,
++ void *data)
++{
++ struct xgene_pcie_acpi_root *root = data;
++ struct acpi_resource_fixed_memory32 *fixed32;
++
++ if (acpi_res->type == ACPI_RESOURCE_TYPE_FIXED_MEMORY32) {
++ fixed32 = &acpi_res->data.fixed_memory32;
++ root->csr_base = ioremap(fixed32->address,
++ fixed32->address_length);
++ return AE_CTRL_TERMINATE;
++ }
++
++ return AE_OK;
++}
++
++static int xgene_pcie_ecam_init(struct pci_config_window *cfg)
++{
++ struct xgene_pcie_acpi_root *xgene_root;
++ struct device *dev = cfg->parent;
++ struct acpi_device *adev = to_acpi_device(dev);
++ acpi_handle handle = acpi_device_handle(adev);
++
++ xgene_root = devm_kzalloc(dev, sizeof(*xgene_root), GFP_KERNEL);
++ if (!xgene_root)
++ return -ENOMEM;
++
++ acpi_walk_resources(handle, METHOD_NAME__CRS,
++ xgene_pcie_find_csr_base, xgene_root);
++
++ if (!xgene_root->csr_base) {
++ kfree(xgene_root);
++ return -ENODEV;
++ }
++
++ xgene_root->version = XGENE_PCIE_IP_VER_1;
++
++ cfg->priv = xgene_root;
++
++ return 0;
++}
++
++/*
++ * For Configuration request, RTDID register is used as Bus Number,
++ * Device Number and Function number of the header fields.
++ */
++static void xgene_pcie_set_rtdid_reg(struct pci_bus *bus, uint devfn)
++{
++ struct pci_config_window *cfg = bus->sysdata;
++ struct xgene_pcie_acpi_root *port = cfg->priv;
++ unsigned int b, d, f;
++ u32 rtdid_val = 0;
++
++ b = bus->number;
++ d = PCI_SLOT(devfn);
++ f = PCI_FUNC(devfn);
++
++ if (!pci_is_root_bus(bus))
++ rtdid_val = (b << 8) | (d << 3) | f;
++
++ writel(rtdid_val, port->csr_base + RTDID);
++ /* read the register back to ensure flush */
++ readl(port->csr_base + RTDID);
++}
++
++/*
++ * X-Gene PCIe port uses BAR0-BAR1 of RC's configuration space as
++ * the translation from PCI bus to native BUS. Entire DDR region
++ * is mapped into PCIe space using these registers, so it can be
++ * reached by DMA from EP devices. The BAR0/1 of bridge should be
++ * hidden during enumeration to avoid the sizing and resource allocation
++ * by PCIe core.
++ */
++static bool xgene_pcie_hide_rc_bars(struct pci_bus *bus, int offset)
++{
++ if (pci_is_root_bus(bus) && ((offset == PCI_BASE_ADDRESS_0) ||
++ (offset == PCI_BASE_ADDRESS_1)))
++ return true;
++
++ return false;
++}
++
++void __iomem *xgene_pcie_ecam_map_bus(struct pci_bus *bus,
++ unsigned int devfn, int where)
++{
++ struct pci_config_window *cfg = bus->sysdata;
++ unsigned int busn = bus->number;
++ void __iomem *base;
++
++ if (busn < cfg->busr.start || busn > cfg->busr.end)
++ return NULL;
++
++ if ((pci_is_root_bus(bus) && devfn != 0) ||
++ xgene_pcie_hide_rc_bars(bus, where))
++ return NULL;
++
++ xgene_pcie_set_rtdid_reg(bus, devfn);
++
++ if (busn > cfg->busr.start)
++ base = cfg->win + (1 << cfg->ops->bus_shift);
++ else
++ base = cfg->win;
++
++ return base + where;
++}
++
++static int xgene_pcie_config_read32(struct pci_bus *bus, unsigned int devfn,
++ int where, int size, u32 *val)
++{
++ struct pci_config_window *cfg = bus->sysdata;
++ struct xgene_pcie_acpi_root *port = cfg->priv;
++
++ if (pci_generic_config_read32(bus, devfn, where & ~0x3, 4, val) !=
++ PCIBIOS_SUCCESSFUL)
++ return PCIBIOS_DEVICE_NOT_FOUND;
++
++ /*
++ * The v1 controller has a bug in its Configuration Request
++ * Retry Status (CRS) logic: when CRS is enabled and we read the
++ * Vendor and Device ID of a non-existent device, the controller
++ * fabricates return data of 0xFFFF0001 ("device exists but is not
++ * ready") instead of 0xFFFFFFFF ("device does not exist"). This
++ * causes the PCI core to retry the read until it times out.
++ * Avoid this by not claiming to support CRS.
++ */
++ if (pci_is_root_bus(bus) && (port->version == XGENE_PCIE_IP_VER_1) &&
++ ((where & ~0x3) == ROOT_CAP_AND_CTRL))
++ *val &= ~(PCI_EXP_RTCAP_CRSVIS << 16);
++
++ if (size <= 2)
++ *val = (*val >> (8 * (where & 3))) & ((1 << (size * 8)) - 1);
++
++ return PCIBIOS_SUCCESSFUL;
++}
++
++static struct pci_ecam_ops xgene_pcie_ecam_ops = {
++ .bus_shift = 16,
++ .init = xgene_pcie_ecam_init,
++ .pci_ops = {
++ .map_bus = xgene_pcie_ecam_map_bus,
++ .read = xgene_pcie_config_read32,
++ .write = pci_generic_config_write,
++ }
++};
++
++DECLARE_ACPI_MCFG_FIXUP(&xgene_pcie_ecam_ops, APM_OEM_ID,
++ APM_XGENE_OEM_TABLE_ID, APM_XGENE_OEM_REV,
++ PCI_MCFG_DOMAIN_ANY, PCI_MCFG_BUS_ANY);
++#endif
+--
+2.7.4
+