[PATCH 5/5] PCI: Add host drivers for Cavium ThunderX processors.
David Daney
ddaney.cavm at gmail.com
Wed Jul 15 09:54:45 PDT 2015
From: David Daney <david.daney at cavium.com>
Signed-off-by: David Daney <david.daney at cavium.com>
---
drivers/pci/host/Kconfig | 12 +
drivers/pci/host/Makefile | 2 +
drivers/pci/host/pcie-thunder-pem.c | 462 ++++++++++++++++++++++++++++++++++++
drivers/pci/host/pcie-thunder.c | 422 ++++++++++++++++++++++++++++++++
4 files changed, 898 insertions(+)
create mode 100644 drivers/pci/host/pcie-thunder-pem.c
create mode 100644 drivers/pci/host/pcie-thunder.c
diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig
index c132bdd..06e26ad 100644
--- a/drivers/pci/host/Kconfig
+++ b/drivers/pci/host/Kconfig
@@ -145,4 +145,16 @@ config PCIE_IPROC_BCMA
Say Y here if you want to use the Broadcom iProc PCIe controller
through the BCMA bus interface
+config PCI_THUNDER_PEM
+ bool
+
+config PCI_THUNDER
+ bool "Thunder PCIe host controller"
+ depends on ARM64 || COMPILE_TEST
+ depends on OF_PCI
+ select PCI_MSI
+ select PCI_THUNDER_PEM
+ help
+ Say Y here if you want internal PCI support on Thunder SoC.
+
endmenu
diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile
index 140d66f..a355155 100644
--- a/drivers/pci/host/Makefile
+++ b/drivers/pci/host/Makefile
@@ -17,3 +17,5 @@ obj-$(CONFIG_PCI_VERSATILE) += pci-versatile.o
obj-$(CONFIG_PCIE_IPROC) += pcie-iproc.o
obj-$(CONFIG_PCIE_IPROC_PLATFORM) += pcie-iproc-platform.o
obj-$(CONFIG_PCIE_IPROC_BCMA) += pcie-iproc-bcma.o
+obj-$(CONFIG_PCI_THUNDER) += pcie-thunder.o
+obj-$(CONFIG_PCI_THUNDER_PEM) += pcie-thunder-pem.o
diff --git a/drivers/pci/host/pcie-thunder-pem.c b/drivers/pci/host/pcie-thunder-pem.c
new file mode 100644
index 0000000..7861a8a
--- /dev/null
+++ b/drivers/pci/host/pcie-thunder-pem.c
@@ -0,0 +1,462 @@
+/*
+ * PCIe host controller driver for Cavium Thunder SOC
+ *
+ * Copyright (C) 2014,2015 Cavium Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+/* #define DEBUG 1 */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/irq.h>
+#include <linux/pci.h>
+#include <linux/irqdomain.h>
+#include <linux/msi.h>
+#include <linux/irqchip/arm-gic-v3.h>
+
+#define THUNDER_SLI_S2M_REG_ACC_BASE 0x874001000000ull
+
+#define THUNDER_GIC 0x801000000000ull
+#define THUNDER_GICD_SETSPI_NSR 0x801000000040ull
+#define THUNDER_GICD_CLRSPI_NSR 0x801000000048ull
+
+#define THUNDER_GSER_PCIE_MASK 0x01
+
+#define PEM_CTL_STATUS 0x000
+#define PEM_RD_CFG 0x030
+#define P2N_BAR0_START 0x080
+#define P2N_BAR1_START 0x088
+#define P2N_BAR2_START 0x090
+#define BAR_CTL 0x0a8
+#define BAR2_MASK 0x0b0
+#define BAR1_INDEX 0x100
+#define PEM_CFG 0x410
+#define PEM_ON 0x420
+
+struct thunder_pem {
+ struct list_head list; /* on thunder_pem_buses */
+ bool connected;
+ unsigned int id;
+ unsigned int sli;
+ unsigned int sli_group;
+ unsigned int node;
+ u64 sli_window_base;
+ void __iomem *bar0;
+ void __iomem *bar4;
+ void __iomem *sli_s2m;
+ void __iomem *cfgregion;
+ struct pci_bus *bus;
+ int vwire_irqs[4];
+ u32 vwire_data[4];
+};
+
+static LIST_HEAD(thunder_pem_buses);
+
+static struct pci_device_id thunder_pem_pci_table[] = {
+ {PCI_VENDOR_ID_CAVIUM, 0xa020, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+ {0,}
+};
+MODULE_DEVICE_TABLE(pci, thunder_pem_pci_table);
+
+enum slix_s2m_ctype {
+ CTYPE_MEMORY = 0,
+ CTYPE_CONFIG = 1,
+ CTYPE_IO = 2
+};
+
+static u64 slix_s2m_reg_val(unsigned mac, enum slix_s2m_ctype ctype,
+ bool merge, bool relaxed, bool snoop, u32 ba_msb)
+{
+ u64 v;
+
+ v = (u64)(mac % 3) << 49;
+ v |= (u64)ctype << 53;
+ if (!merge)
+ v |= 1ull << 48;
+ if (relaxed)
+ v |= 5ull << 40;
+ if (!snoop)
+ v |= 5ull << 41;
+ v |= (u64)ba_msb;
+
+ return v;
+}
+
+static u32 thunder_pcierc_config_read(struct thunder_pem *pem, u32 reg, int size)
+{
+ unsigned int val;
+
+ writeq(reg & ~3u, pem->bar0 + PEM_RD_CFG);
+ val = readq(pem->bar0 + PEM_RD_CFG) >> 32;
+
+ if (size == 1)
+ val = (val >> (8 * (reg & 3))) & 0xff;
+ else if (size == 2)
+ val = (val >> (8 * (reg & 3))) & 0xffff;
+
+ return val;
+}
+
+static int thunder_pem_read_config(struct pci_bus *bus, unsigned int devfn,
+ int reg, int size, u32 *val)
+{
+ void __iomem *addr;
+ struct thunder_pem *pem = bus->sysdata;
+ unsigned int busnr = bus->number;
+
+ if (busnr > 255 || devfn > 255 || reg > 4095)
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ addr = pem->cfgregion + ((busnr << 24) | (devfn << 16) | reg);
+
+ switch (size) {
+ case 1:
+ *val = readb(addr);
+ break;
+ case 2:
+ *val = readw(addr);
+ break;
+ case 4:
+ *val = readl(addr);
+ break;
+ default:
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+ }
+
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static int thunder_pem_write_config(struct pci_bus *bus, unsigned int devfn,
+ int reg, int size, u32 val)
+{
+ void __iomem *addr;
+ struct thunder_pem *pem = bus->sysdata;
+ unsigned int busnr = bus->number;
+
+ if (busnr > 255 || devfn > 255 || reg > 4095)
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ addr = pem->cfgregion + ((busnr << 24) | (devfn << 16) | reg);
+
+ switch (size) {
+ case 1:
+ writeb(val, addr);
+ break;
+ case 2:
+ writew(val, addr);
+ break;
+ case 4:
+ writel(val, addr);
+ break;
+ default:
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+ }
+
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static struct pci_ops thunder_pem_ops = {
+ .read = thunder_pem_read_config,
+ .write = thunder_pem_write_config,
+};
+
+static struct thunder_pem *thunder_pem_from_dev(struct pci_dev *dev)
+{
+ struct thunder_pem *pem;
+ struct pci_bus *bus = dev->bus;
+
+ while (!pci_is_root_bus(bus))
+ bus = bus->parent;
+
+ list_for_each_entry(pem, &thunder_pem_buses, list) {
+ if (pem->bus == bus)
+ return pem;
+ }
+ return NULL;
+}
+
+int thunder_pem_requester_id(struct pci_dev *dev)
+{
+ struct thunder_pem *pem = thunder_pem_from_dev(dev);
+
+ if (!pem)
+ return -ENODEV;
+
+ if (pem->id < 3)
+ return ((1 << 16) |
+ ((dev)->bus->number << 8) |
+ (dev)->devfn);
+
+ if (pem->id < 6)
+ return ((3 << 16) |
+ ((dev)->bus->number << 8) |
+ (dev)->devfn);
+
+ if (pem->id < 9)
+ return ((1 << 19) | (1 << 16) |
+ ((dev)->bus->number << 8) |
+ (dev)->devfn);
+
+ if (pem->id < 12)
+ return ((1 << 19) |
+ (3 << 16) |
+ ((dev)->bus->number << 8) |
+ (dev)->devfn);
+ return -ENODEV;
+}
+
+static int thunder_pem_pcibios_add_device(struct pci_dev *dev)
+{
+ struct thunder_pem *pem;
+ u8 pin;
+
+ pem = thunder_pem_from_dev(dev);
+ if (!pem)
+ return 0;
+
+ pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin);
+
+ /* Cope with illegal. */
+ if (pin > 4)
+ pin = 1;
+
+ dev->irq = pin > 0 ? pem->vwire_irqs[pin - 1] : 0;
+
+ if (pin)
+ dev_dbg(&dev->dev, "assigning IRQ %02d\n", dev->irq);
+
+ pci_write_config_byte(dev, PCI_INTERRUPT_LINE, dev->irq);
+
+ return 0;
+}
+
+static int thunder_pem_pci_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct thunder_pem *pem;
+ resource_size_t bar0_start;
+ u64 regval;
+ u64 sliaddr, pciaddr;
+ u32 cfgval;
+ int primary_bus;
+ int i;
+ int ret = 0;
+ struct resource *res;
+ LIST_HEAD(resources);
+
+ set_pcibios_add_device(thunder_pem_pcibios_add_device);
+
+ pem = devm_kzalloc(&pdev->dev, sizeof(*pem), GFP_KERNEL);
+ if (!pem)
+ return -ENOMEM;
+
+ pci_set_drvdata(pdev, pem);
+
+ bar0_start = pci_resource_start(pdev, 0);
+ pem->node = (bar0_start >> 44) & 3;
+ pem->id = ((bar0_start >> 24) & 7) + (6 * pem->node);
+ pem->sli = pem->id % 3;
+ pem->sli_group = (pem->id / 3) % 2;
+ pem->sli_window_base = 0x880000000000ull | (((u64)pem->node) << 44) | ((u64)pem->sli_group << 40);
+ pem->sli_window_base += 0x4000000000 * pem->sli;
+
+ ret = pci_enable_device_mem(pdev);
+ if (ret)
+ goto out;
+
+ pem->bar0 = pcim_iomap(pdev, 0, 0x100000);
+ if (!pem->bar0) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ pem->bar4 = pcim_iomap(pdev, 4, 0x100000);
+ if (!pem->bar0) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ sliaddr = THUNDER_SLI_S2M_REG_ACC_BASE | ((u64)pem->node << 44) | ((u64)pem->sli_group << 36);
+
+ regval = readq(pem->bar0 + PEM_ON);
+ if (!(regval & 1)) {
+ dev_notice(&pdev->dev, "PEM%u_ON not set, skipping...\n", pem->id);
+ goto out;
+ }
+
+ regval = readq(pem->bar0 + PEM_CTL_STATUS);
+ regval |= 0x10; /* Set Link Enable bit */
+ writeq(regval, pem->bar0 + PEM_CTL_STATUS);
+
+ udelay(1000);
+
+ cfgval = thunder_pcierc_config_read(pem, 32 * 4, 4); /* PCIERC_CFG032 */
+
+ if (((cfgval >> 29 & 0x1) == 0x0) || ((cfgval >> 27 & 0x1) == 0x1)) {
+ dev_notice(&pdev->dev, "PEM%u Link Timeout, skipping...\n", pem->id);
+ goto out;
+ }
+
+ pem->sli_s2m = devm_ioremap(&pdev->dev, sliaddr, 0x1000);
+ if (!pem->sli_s2m) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ pem->cfgregion = devm_ioremap(&pdev->dev, pem->sli_window_base, 0x100000000ull);
+ if (!pem->cfgregion) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ regval = slix_s2m_reg_val(pem->sli, CTYPE_CONFIG, false, false, false, 0);
+ writeq(regval, pem->sli_s2m + 0x10 * ((0x40 * pem->sli) + 0));
+
+ cfgval = thunder_pcierc_config_read(pem, 6 * 4, 4); /* PCIERC_CFG006 */
+ primary_bus = (cfgval >> 8) & 0xff;
+
+ res = kzalloc(sizeof(*res), GFP_KERNEL);
+ if (!res) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ res->start = primary_bus;
+ res->end = 255;
+ res->flags = IORESOURCE_BUS;
+ pci_add_resource(&resources, res);
+
+
+ res = kzalloc(sizeof(*res), GFP_KERNEL);
+ if (!res) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ res->start = 0x100000 * pem->id;
+ res->end = res->start + 0x100000 - 1;
+ res->flags = IORESOURCE_IO;
+ pci_add_resource(&resources, res);
+ regval = slix_s2m_reg_val(pem->sli, CTYPE_IO, false, false, false, 0);
+ writeq(regval, pem->sli_s2m + 0x10 * ((0x40 * pem->sli) + 1));
+
+ res = kzalloc(sizeof(*res), GFP_KERNEL);
+ if (!res) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ pciaddr = 0x10000000ull;
+ res->start = pem->sli_window_base + 0x1000000000ull + pciaddr;
+ res->end = res->start + 0x1000000000ull - pciaddr - 1;
+ res->flags = IORESOURCE_MEM;
+ pci_add_resource_offset(&resources, res, res->start - pciaddr);
+ for (i = 0; i < 16; i++) {
+ regval = slix_s2m_reg_val(pem->sli, CTYPE_MEMORY, false, false, false, i);
+ writeq(regval, pem->sli_s2m + 0x10 * ((0x40 * pem->sli) + (0x10 + i)));
+ }
+
+ res = kzalloc(sizeof(*res), GFP_KERNEL);
+ if (!res) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ pciaddr = 0x1000000000ull;
+ res->start = pem->sli_window_base + 0x1000000000ull + pciaddr;
+ res->end = res->start + 0x1000000000ull - 1;
+ res->flags = IORESOURCE_MEM | IORESOURCE_PREFETCH;
+ pci_add_resource_offset(&resources, res, res->start - pciaddr);
+ for (i = 0; i < 16; i++) {
+ regval = slix_s2m_reg_val(pem->sli, CTYPE_MEMORY, true, true, true, i + 0x10);
+ writeq(regval, pem->sli_s2m + 0x10 * ((0x40 * pem->sli) + (0x20 + i)));
+ }
+
+ writeq(0, pem->bar0 + P2N_BAR0_START);
+ writeq(0, pem->bar0 + P2N_BAR1_START);
+ writeq(0, pem->bar0 + P2N_BAR2_START);
+
+ regval = 0x10; /* BAR_CTL[BAR1_SIZ] = 1 (64MB) */
+ regval |= 0x8; /* BAR_CTL[BAR2_ENB] = 1 */
+ writeq(regval, pem->bar0 + BAR_CTL);
+
+ /* 1st 4MB region -> GIC registers so 32-bit MSI can reach the GIC. */
+ regval = (THUNDER_GIC + (((u64)pem->node) << 44)) >> 18;
+ /* BAR1_INDEX[ADDR_V] = 1 */
+ regval |= 1;
+ writeq(regval, pem->bar0 + BAR1_INDEX);
+ /* Remaining regions linear mapping to physical address space */
+ for (i = 1; i < 16; i++) {
+ regval = (i << 4) | 1;
+ writeq(regval, pem->bar0 + BAR1_INDEX + 8 * i);
+ }
+
+ pem->bus = pci_create_root_bus(&pdev->dev, primary_bus, &thunder_pem_ops, pem, &resources);
+ if (!pem->bus) {
+ ret = -ENODEV;
+ goto err_root_bus;
+ }
+ pem->bus->is_pcierc = 1;
+ list_add_tail(&pem->list, &thunder_pem_buses);
+
+ for (i = 0; i < 3; i++) {
+ pem->vwire_data[i] = 40 + 4 * pem->id + i;
+ pem->vwire_irqs[i] = irq_create_mapping(gic_get_irq_domain(), pem->vwire_data[i]);
+ if (!pem->vwire_irqs[i]) {
+ dev_err(&pdev->dev, "Error: No irq mapping for %u\n", pem->vwire_data[i]);
+ continue;
+ }
+ irq_set_irq_type(pem->vwire_irqs[i], IRQ_TYPE_LEVEL_HIGH);
+
+ writeq(THUNDER_GICD_SETSPI_NSR, pem->bar4 + 0 + (i + 2) * 32);
+ writeq(pem->vwire_data[i], pem->bar4 + 8 + (i + 2) * 32);
+ writeq(THUNDER_GICD_CLRSPI_NSR, pem->bar4 + 16 + (i + 2) * 32);
+ writeq(pem->vwire_data[i], pem->bar4 + 24 + (i + 2) * 32);
+ }
+ ret = pci_read_config_dword(pdev, 44 * 4, &cfgval);
+ if (WARN_ON(ret))
+ goto err_free_root_bus;
+ cfgval &= ~0x40000000; /* Clear FUNM */
+ cfgval |= 0x80000000; /* Set MSIXEN */
+ pci_write_config_dword(pdev, 44 * 4, cfgval);
+ pem->bus->msi = pdev->bus->msi;
+
+ pci_scan_child_bus(pem->bus);
+ pci_bus_add_devices(pem->bus);
+ pci_assign_unassigned_root_bus_resources(pem->bus);
+
+ return 0;
+
+err_free_root_bus:
+ pci_remove_root_bus(pem->bus);
+err_root_bus:
+ pci_free_resource_list(&resources);
+out:
+ return ret;
+}
+
+static void thunder_pem_pci_remove(struct pci_dev *pdev)
+{
+}
+
+static struct pci_driver thunder_pem_driver = {
+ .name = "thunder_pem",
+ .id_table = thunder_pem_pci_table,
+ .probe = thunder_pem_pci_probe,
+ .remove = thunder_pem_pci_remove
+};
+
+static int __init thunder_pcie_init(void)
+{
+ int ret;
+
+ ret = pci_register_driver(&thunder_pem_driver);
+
+ return ret;
+}
+module_init(thunder_pcie_init);
+
+static void __exit thunder_pcie_exit(void)
+{
+ pci_unregister_driver(&thunder_pem_driver);
+}
+module_exit(thunder_pcie_exit);
diff --git a/drivers/pci/host/pcie-thunder.c b/drivers/pci/host/pcie-thunder.c
new file mode 100644
index 0000000..4abab5a
--- /dev/null
+++ b/drivers/pci/host/pcie-thunder.c
@@ -0,0 +1,422 @@
+/*
+ * PCIe host controller driver for Cavium Thunder SOC
+ *
+ * Copyright (C) 2014, 2015 Cavium Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/of_pci.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/msi.h>
+#include <linux/irqchip/arm-gic-v3.h>
+
+#define PCI_DEVICE_ID_THUNDER_BRIDGE 0xa002
+
+#define THUNDER_PCIE_BUS_SHIFT 20
+#define THUNDER_PCIE_DEV_SHIFT 15
+#define THUNDER_PCIE_FUNC_SHIFT 12
+
+#define THUNDER_ECAM0_CFG_BASE 0x848000000000
+#define THUNDER_ECAM1_CFG_BASE 0x849000000000
+#define THUNDER_ECAM2_CFG_BASE 0x84a000000000
+#define THUNDER_ECAM3_CFG_BASE 0x84b000000000
+#define THUNDER_ECAM4_CFG_BASE 0x948000000000
+#define THUNDER_ECAM5_CFG_BASE 0x949000000000
+#define THUNDER_ECAM6_CFG_BASE 0x94a000000000
+#define THUNDER_ECAM7_CFG_BASE 0x94b000000000
+
+struct thunder_pcie {
+ struct device_node *node;
+ struct device *dev;
+ void __iomem *cfg_base;
+ struct msi_controller *msi;
+ int ecam;
+ bool valid;
+};
+
+int thunder_pem_requester_id(struct pci_dev *dev);
+
+static atomic_t thunder_pcie_ecam_probed;
+
+static u32 pci_requester_id_ecam(struct pci_dev *dev)
+{
+ return (((pci_domain_nr(dev->bus) >> 2) << 19) |
+ ((pci_domain_nr(dev->bus) % 4) << 16) |
+ (dev->bus->number << 8) | dev->devfn);
+}
+
+static u32 thunder_pci_requester_id(struct pci_dev *dev, u16 alias)
+{
+ int ret;
+
+ ret = thunder_pem_requester_id(dev);
+ if (ret >= 0)
+ return (u32)ret;
+
+ return pci_requester_id_ecam(dev);
+}
+
+/*
+ * This bridge is just for the sake of supporting ARI for
+ * downstream devices. No resources are attached to it.
+ * Copy upstream root bus resources to bridge which aide in
+ * resource claiming for downstream devices
+ */
+static void pci_bridge_resource_fixup(struct pci_dev *dev)
+{
+ struct pci_bus *bus;
+ int resno;
+
+ bus = dev->subordinate;
+ for (resno = 0; resno < PCI_BRIDGE_RESOURCE_NUM; resno++) {
+ bus->resource[resno] = pci_bus_resource_n(bus->parent,
+ PCI_BRIDGE_RESOURCE_NUM + resno);
+ }
+
+ for (resno = PCI_BRIDGE_RESOURCES;
+ resno <= PCI_BRIDGE_RESOURCE_END; resno++) {
+ dev->resource[resno].start = dev->resource[resno].end = 0;
+ dev->resource[resno].flags = 0;
+ }
+}
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_CAVIUM, PCI_DEVICE_ID_THUNDER_BRIDGE,
+ pci_bridge_resource_fixup);
+
+/*
+ * All PCIe devices in Thunder have fixed resources, shouldn't be reassigned.
+ * Also claim the device's valid resources to set 'res->parent' hierarchy.
+ */
+static void pci_dev_resource_fixup(struct pci_dev *dev)
+{
+ struct resource *res;
+ int resno;
+
+ /*
+ * If the ECAM is not yet probed, we must be in a virtual
+ * machine. In that case, don't mark things as
+ * IORESOURCE_PCI_FIXED
+ */
+ if (!atomic_read(&thunder_pcie_ecam_probed))
+ return;
+
+ for (resno = 0; resno < PCI_NUM_RESOURCES; resno++)
+ dev->resource[resno].flags |= IORESOURCE_PCI_FIXED;
+
+ for (resno = 0; resno < PCI_BRIDGE_RESOURCES; resno++) {
+ res = &dev->resource[resno];
+ if (res->parent || !(res->flags & IORESOURCE_MEM))
+ continue;
+ pci_claim_resource(dev, resno);
+ }
+}
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_CAVIUM, PCI_ANY_ID,
+ pci_dev_resource_fixup);
+
+static void __iomem *thunder_pcie_get_cfg_addr(struct thunder_pcie *pcie,
+ unsigned int busnr,
+ unsigned int devfn, int reg)
+{
+ return pcie->cfg_base +
+ ((busnr << THUNDER_PCIE_BUS_SHIFT)
+ | (PCI_SLOT(devfn) << THUNDER_PCIE_DEV_SHIFT)
+ | (PCI_FUNC(devfn) << THUNDER_PCIE_FUNC_SHIFT)) + reg;
+}
+
+static int thunder_pcie_read_config(struct pci_bus *bus, unsigned int devfn,
+ int reg, int size, u32 *val)
+{
+ struct thunder_pcie *pcie = bus->sysdata;
+ void __iomem *addr;
+ unsigned int busnr = bus->number;
+
+ if (busnr > 255 || devfn > 255 || reg > 4095)
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ addr = thunder_pcie_get_cfg_addr(pcie, busnr, devfn, reg);
+
+ switch (size) {
+ case 1:
+ *val = readb(addr);
+ break;
+ case 2:
+ *val = readw(addr);
+ break;
+ case 4:
+ *val = readl(addr);
+ break;
+ default:
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+ }
+
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static int thunder_pcie_write_config(struct pci_bus *bus, unsigned int devfn,
+ int reg, int size, u32 val)
+{
+ struct thunder_pcie *pcie = bus->sysdata;
+ void __iomem *addr;
+ unsigned int busnr = bus->number;
+
+ if (busnr > 255 || devfn > 255 || reg > 4095)
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ addr = thunder_pcie_get_cfg_addr(pcie, busnr, devfn, reg);
+
+ switch (size) {
+ case 1:
+ writeb(val, addr);
+ break;
+ case 2:
+ writew(val, addr);
+ break;
+ case 4:
+ writel(val, addr);
+ break;
+ default:
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+ }
+
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static struct pci_ops thunder_pcie_ops = {
+ .read = thunder_pcie_read_config,
+ .write = thunder_pcie_write_config,
+};
+
+static int thunder_pcie_msi_enable(struct thunder_pcie *pcie,
+ struct pci_bus *bus)
+{
+ struct device_node *msi_node;
+
+ msi_node = of_parse_phandle(pcie->node, "msi-parent", 0);
+ if (!msi_node)
+ return -ENODEV;
+
+ pcie->msi = of_pci_find_msi_chip_by_node(msi_node);
+ if (!pcie->msi)
+ return -ENODEV;
+
+ pcie->msi->dev = pcie->dev;
+ bus->msi = pcie->msi;
+
+ return 0;
+}
+
+static void thunder_pcie_config(struct thunder_pcie *pcie, u64 addr)
+{
+ atomic_set(&thunder_pcie_ecam_probed, 1);
+ set_its_pci_requester_id(thunder_pci_requester_id);
+
+ pcie->valid = true;
+
+ switch (addr) {
+ case THUNDER_ECAM0_CFG_BASE:
+ pcie->ecam = 0;
+ break;
+ case THUNDER_ECAM1_CFG_BASE:
+ pcie->ecam = 1;
+ break;
+ case THUNDER_ECAM2_CFG_BASE:
+ pcie->ecam = 2;
+ break;
+ case THUNDER_ECAM3_CFG_BASE:
+ pcie->ecam = 3;
+ break;
+ case THUNDER_ECAM4_CFG_BASE:
+ pcie->ecam = 4;
+ break;
+ case THUNDER_ECAM5_CFG_BASE:
+ pcie->ecam = 5;
+ break;
+ case THUNDER_ECAM6_CFG_BASE:
+ pcie->ecam = 6;
+ break;
+ case THUNDER_ECAM7_CFG_BASE:
+ pcie->ecam = 7;
+ break;
+ default:
+ pcie->valid = false;
+ break;
+ }
+}
+
+static int thunder_pcie_probe(struct platform_device *pdev)
+{
+ struct thunder_pcie *pcie;
+ struct resource *cfg_base;
+ struct pci_bus *bus;
+ int ret = 0;
+ LIST_HEAD(res);
+
+ pcie = devm_kzalloc(&pdev->dev, sizeof(*pcie), GFP_KERNEL);
+ if (!pcie)
+ return -ENOMEM;
+
+ pcie->node = of_node_get(pdev->dev.of_node);
+ pcie->dev = &pdev->dev;
+
+ /* Get controller's configuration space range */
+ cfg_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+ thunder_pcie_config(pcie, cfg_base->start);
+
+ pcie->cfg_base = devm_ioremap_resource(&pdev->dev, cfg_base);
+ if (IS_ERR(pcie->cfg_base)) {
+ ret = PTR_ERR(pcie->cfg_base);
+ goto err_ioremap;
+ }
+
+ dev_info(&pdev->dev, "ECAM%d CFG BASE 0x%llx\n",
+ pcie->ecam, (u64)cfg_base->start);
+
+ ret = of_pci_get_host_bridge_resources(pdev->dev.of_node,
+ 0, 255, &res, NULL);
+ if (ret)
+ goto err_root_bus;
+
+ bus = pci_create_root_bus(&pdev->dev, 0, &thunder_pcie_ops, pcie, &res);
+ if (!bus) {
+ ret = -ENODEV;
+ goto err_root_bus;
+ }
+
+ /* Set reference to MSI chip */
+ ret = thunder_pcie_msi_enable(pcie, bus);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Unable to set reference to MSI chip: ret=%d\n", ret);
+ goto err_msi;
+ }
+
+ platform_set_drvdata(pdev, pcie);
+
+ pci_scan_child_bus(bus);
+ pci_bus_add_devices(bus);
+
+ return 0;
+err_msi:
+ pci_remove_root_bus(bus);
+err_root_bus:
+ pci_free_resource_list(&res);
+err_ioremap:
+ of_node_put(pcie->node);
+ return ret;
+}
+
+static const struct of_device_id thunder_pcie_of_match[] = {
+ { .compatible = "cavium,thunder-pcie", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, thunder_pcie_of_match);
+
+static struct platform_driver thunder_pcie_driver = {
+ .driver = {
+ .name = "thunder-pcie",
+ .owner = THIS_MODULE,
+ .of_match_table = thunder_pcie_of_match,
+ },
+ .probe = thunder_pcie_probe,
+};
+module_platform_driver(thunder_pcie_driver);
+
+#ifdef CONFIG_ACPI
+
+static int
+thunder_mmcfg_read_config(struct pci_mmcfg_region *cfg, unsigned int busnr,
+ unsigned int devfn, int reg, int len, u32 *value)
+{
+ struct thunder_pcie *pcie = cfg->data;
+ void __iomem *addr;
+
+ if (!pcie->valid) {
+ /* Not support for now */
+ pr_err("RC PEM not supported !!!\n");
+ return PCIBIOS_DEVICE_NOT_FOUND;
+ }
+
+ addr = thunder_pcie_get_cfg_addr(pcie, busnr, devfn, reg);
+
+ switch (len) {
+ case 1:
+ *value = readb(addr);
+ break;
+ case 2:
+ *value = readw(addr);
+ break;
+ case 4:
+ *value = readl(addr);
+ break;
+ default:
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+ }
+
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static int thunder_mmcfg_write_config(struct pci_mmcfg_region *cfg,
+ unsigned int busnr, unsigned int devfn, int reg, int len,
+ u32 value) {
+ struct thunder_pcie *pcie = cfg->data;
+ void __iomem *addr;
+
+ if (!pcie->valid) {
+ /* Not support for now */
+ pr_err("RC PEM not supported !!!\n");
+ return PCIBIOS_DEVICE_NOT_FOUND;
+ }
+
+ addr = thunder_pcie_get_cfg_addr(pcie, busnr, devfn, reg);
+
+ switch (len) {
+ case 1:
+ writeb(value, addr);
+ break;
+ case 2:
+ writew(value, addr);
+ break;
+ case 4:
+ writel(value, addr);
+ break;
+ default:
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+ }
+
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static int thunder_acpi_mcfg_fixup(struct acpi_pci_root *root,
+ struct pci_mmcfg_region *cfg)
+{
+ struct thunder_pcie *pcie;
+
+ pcie = kzalloc(sizeof(*pcie), GFP_KERNEL);
+ if (!pcie)
+ return -ENOMEM;
+
+ pcie->dev = &root->device->dev;
+
+ thunder_pcie_config(pcie, cfg->address);
+
+ pcie->cfg_base = cfg->virt;
+ cfg->data = pcie;
+ cfg->read = thunder_mmcfg_read_config;
+ cfg->write = thunder_mmcfg_write_config;
+
+ return 0;
+}
+DECLARE_ACPI_MCFG_FIXUP("CAVIUM", "THUNDERX", thunder_acpi_mcfg_fixup);
+#endif
+
+MODULE_AUTHOR("Sunil Goutham");
+MODULE_DESCRIPTION("Cavium Thunder ECAM host controller driver");
+MODULE_LICENSE("GPL v2");
--
1.9.1
More information about the linux-arm-kernel
mailing list