[linux-yocto] [PATCH 155/161] arch/powerpc/sysdev: Added PCIe MSI support for AXM35xx

Cristian Bercaru cristian.bercaru at windriver.com
Thu May 21 12:22:17 PDT 2015


From: SangeethaRao <sangeetha.rao at lsi.com>

Signed-off-by: SangeethaRao <sangeetha.rao at lsi.com>
Signed-off-by: Bruce Ashfield <bruce.ashfield at windriver.com>
---
 arch/powerpc/boot/dts/acp35xx.dts |   34 +++-
 arch/powerpc/sysdev/Kconfig       |   11 +-
 arch/powerpc/sysdev/Makefile      |    5 +-
 arch/powerpc/sysdev/lsi_msi.c     |  389 +++++++++++++++++++++++++++++++++++++
 arch/powerpc/sysdev/lsi_msi.h     |   21 ++
 arch/powerpc/sysdev/lsi_pci.c     |   52 +++--
 6 files changed, 488 insertions(+), 24 deletions(-)
 create mode 100644 arch/powerpc/sysdev/lsi_msi.c
 create mode 100644 arch/powerpc/sysdev/lsi_msi.h

diff --git a/arch/powerpc/boot/dts/acp35xx.dts b/arch/powerpc/boot/dts/acp35xx.dts
index ec4fc75..1a5a33e 100644
--- a/arch/powerpc/boot/dts/acp35xx.dts
+++ b/arch/powerpc/boot/dts/acp35xx.dts
@@ -307,6 +307,39 @@
 				sbb-reg = <0x00500000 0x8000>;
 				tzc-reg = <0x00541000 0x1000>;
 			};
+			MSI0: msi0 {
+				compatible = "lsi,msi";
+				device_type = "lsi_msi";
+                		status = "ok";
+                		enabled = <0x1>;
+                		port = <0>;
+				#address-cells = <2>;
+				#size-cells = <1>;
+                		/* config space access MPAGE7 registers*/
+                		reg = <0x580000 0x2000>;
+			};
+			MSI1: msi1 {
+				compatible = "lsi,msi";
+				device_type = "lsi_msi";
+                		status = "ok";
+                		enabled = <0x1>;
+                		port = <1>;
+				#address-cells = <2>;
+				#size-cells = <1>;
+                		/* config space access MPAGE7 registers*/
+                		reg = <0x588000 0x2000>;
+			};
+			MSI2: msi2 {
+				compatible = "lsi,msi";
+				device_type = "lsi_msi";
+                		status = "ok";
+                		enabled = <0x1>;
+                		port = <2>;
+				#address-cells = <2>;
+				#size-cells = <1>;
+                		/* config space access MPAGE7 registers*/
+                		reg = <0x590000 0x2000>;
+			};
 		};
 	};
 
@@ -361,7 +394,6 @@
                         0000 0 0 4 &MPIC 52
                 >;
         };
-
         PCIE1: pei1 {
                 compatible = "lsi,plb-pciex";
                 device_type = "pci";
diff --git a/arch/powerpc/sysdev/Kconfig b/arch/powerpc/sysdev/Kconfig
index 1ea267b..ded32ef 100644
--- a/arch/powerpc/sysdev/Kconfig
+++ b/arch/powerpc/sysdev/Kconfig
@@ -3,10 +3,17 @@
 #
 
 config LSI_PPC_PCI
-	bool "LSI ACP34XX PCIe support"
+	bool "LSI ACP34XX/AXM35XX PCIe support"
 	default y
         help
-          PCIe driver support for LSI's ACP34xx
+          PCIe driver support for LSI's ACP34xx/AXM35xx
+
+config LSI_PPC_PCI_MSI
+	bool "LSI ACP34XX/AXM35XX PCIe MSI support"
+	depends on PCI_MSI
+	default y
+        help
+          PCIe MSI driver support for LSI's ACP34xx/AXM35xx
 
 config PPC4xx_PCI_EXPRESS
 	bool
diff --git a/arch/powerpc/sysdev/Makefile b/arch/powerpc/sysdev/Makefile
index b46b432..63dea41 100644
--- a/arch/powerpc/sysdev/Makefile
+++ b/arch/powerpc/sysdev/Makefile
@@ -1,4 +1,4 @@
-subdir-ccflags-$(CONFIG_PPC_WERROR) := -Werror
+#subdir-ccflags-$(CONFIG_PPC_WERROR) := -Werror
 
 ccflags-$(CONFIG_PPC64)		:= -mno-minimal-toc
 
@@ -41,6 +41,7 @@ obj-$(CONFIG_4xx_SOC)		+= ppc4xx_soc.o
 obj-$(CONFIG_XILINX_VIRTEX)	+= xilinx_intc.o
 obj-$(CONFIG_XILINX_PCI)	+= xilinx_pci.o
 obj-$(CONFIG_LSI_PPC_PCI)	+= lsi_pci.o
+obj-$(CONFIG_LSI_PPC_PCI_MSI)	+= lsi_msi.o
 obj-$(CONFIG_OF_RTC)		+= of_rtc.o
 ifeq ($(CONFIG_PCI),y)
 ifeq ($(CONFIG_LSI_PCI),n)
@@ -67,7 +68,7 @@ endif
 
 obj-$(CONFIG_PPC_SCOM)		+= scom.o
 
-subdir-ccflags-$(CONFIG_PPC_WERROR) := -Werror
+#subdir-ccflags-$(CONFIG_PPC_WERROR) := -Werror
 
 obj-$(CONFIG_PPC_XICS)		+= xics/
 
diff --git a/arch/powerpc/sysdev/lsi_msi.c b/arch/powerpc/sysdev/lsi_msi.c
new file mode 100644
index 0000000..f89ba7a
--- /dev/null
+++ b/arch/powerpc/sysdev/lsi_msi.c
@@ -0,0 +1,389 @@
+/*
+* LSI specific PCI-Express MSI support;
+*/
+
+#undef DEBUG
+
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/of.h>
+#include <linux/bootmem.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+
+#include <linux/io.h>
+#include <asm/pci-bridge.h>
+#include <asm/machdep.h>
+#include <asm/dcr.h>
+#include <asm/dcr-regs.h>
+#include <mm/mmu_decl.h>
+
+#include "../../drivers/misc/lsi-ncr.h"
+#include "lsi_msi.h"
+
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/bitmap.h>
+#include <linux/msi.h>
+#include <linux/of_platform.h>
+#include <asm/prom.h>
+#include <asm/hw_irq.h>
+#include <asm/ppc-pci.h>
+
+#define MAX_NUMBER_PCI_SLOTS 3
+static struct acp_pci_msi *acp_pci_msi[MAX_NUMBER_PCI_SLOTS];
+
+static void acp_pci_msi_end_irq(struct irq_data *d)
+{
+}
+
+static struct irq_chip acp_pci_msi_chip = {
+	.irq_mask       = mask_msi_irq,
+	.irq_unmask     = unmask_msi_irq,
+	.irq_ack        = acp_pci_msi_end_irq,
+	.name   = " ACP-MSI  ",
+};
+
+static int acp_pci_msi_host_map(struct irq_domain *h, unsigned int virq,
+	irq_hw_number_t hw)
+{
+	struct irq_chip *chip = &acp_pci_msi_chip;
+	irq_set_status_flags(virq, IRQ_TYPE_EDGE_FALLING);
+	irq_set_chip_and_handler(virq, chip, handle_edge_irq);
+	return 0;
+}
+
+static struct irq_domain_ops acp_pci_msi_host_ops = {
+	.map = acp_pci_msi_host_map,
+};
+
+static void acp_pci_msi_free_hwirqs(struct acp_pci_msi *msi, int offset,
+	int num)
+{
+	unsigned long flags;
+	int order = get_count_order(num);
+
+	pr_debug("%s: freeing 0x%x (2^%d) at offset 0x%x\n",
+		__func__, num, order, offset);
+
+	spin_lock_irqsave(&msi->bitmap_lock, flags);
+	bitmap_release_region(msi->acp_pci_msi_bitmap, offset, order);
+	spin_unlock_irqrestore(&msi->bitmap_lock, flags);
+}
+
+static int acp_pci_msi_free_dt_hwirqs(struct acp_pci_msi *msi_data)
+{
+	bitmap_allocate_region(msi_data->acp_pci_msi_bitmap, 0,
+		get_count_order(NR_MSI_IRQS));
+
+	acp_pci_msi_free_hwirqs(msi_data, 0, 0x100);
+	return 0;
+}
+
+static int acp_pci_msi_init_allocator(struct acp_pci_msi *msi_data)
+{
+	int rc;
+	int size = BITS_TO_LONGS(NR_MSI_IRQS) * sizeof(u32);
+
+	msi_data->acp_pci_msi_bitmap = kzalloc(size, GFP_KERNEL);
+	if (msi_data->acp_pci_msi_bitmap == NULL) {
+		pr_debug("%s: out of memory at line %d\n",
+			__func__, __LINE__);
+		return -ENOMEM;
+	}
+
+	rc = acp_pci_msi_free_dt_hwirqs(msi_data);
+	if (rc)
+		goto out_free;
+
+	return 0;
+out_free:
+	kfree(msi_data->acp_pci_msi_bitmap);
+	msi_data->acp_pci_msi_bitmap = NULL;
+	return rc;
+}
+
+static int acp_pci_msi_hw_init(struct acp_pci_msi *msi,
+	struct platform_device *pdev)
+{
+	u32 int_enable;
+	u32 msg_phys_adrs;
+	int rc, i;
+
+	if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(64)) != 0) {
+		if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)) != 0) {
+			dev_err(&pdev->dev, "No memory for MSI table\n");
+			rc = -ENOMEM;
+			return rc;
+		} else {
+			dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
+		}
+	} else {
+		dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64));
+	}
+
+	msi->msg_buf = dma_alloc_coherent(&pdev->dev, PAGE_SIZE,
+		&msi->msg_buf_dma, GFP_KERNEL);
+	if (!msi->msg_buf) {
+		pr_debug("%s: out of memory at line %d\n", __func__, __LINE__);
+		rc = -ENOMEM;
+		return rc;
+	}
+
+	msi->msi_addr_hi = 0;
+	/* Align address to 1K */
+	if (msi->msg_buf_dma & 0x3f)
+		msi->msg_buf_dma = (msi->msg_buf_dma + 0x400) & 0xfffffc00;
+	msi->msi_addr_lo = msi->msg_buf_dma;
+
+	/* Set MSI buffer address. */
+	msg_phys_adrs = msi->msg_buf_dma & 0x0fffffff;
+
+	pr_debug("%s: MSI buffer address = x%x\n", __func__, msg_phys_adrs);
+	printk(KERN_INFO "%s: MSI buffer address = x%x\n", __func__,
+		msg_phys_adrs);
+	out_le32(msi->utl_base + 0x1190, (msg_phys_adrs) >> 10);
+
+	/* Enable MSI interrupts. */
+	int_enable = in_le32(msi->utl_base + 0x10C4);
+	int_enable |= (1 << 31);
+	out_le32(msi->utl_base + 0x10C4, int_enable);
+	out_le32(msi->utl_base + 0x1234, 0xffff);
+	for (i = 0; i < 16; i++)
+		out_le32(msi->utl_base + 0x1240 + (i*0xc), 0xffff);
+
+	return 0;
+}
+
+static int acp_pci_msi_setup_irqs(struct pci_dev *pdev, int nvec, int type);
+static void acp_pci_msi_teardown_irqs(struct pci_dev *pdev);
+static int acp_pci_msi_check_device(struct pci_dev *pdev, int nvec, int type);
+
+static int acp_pci_msi_init(struct platform_device *pdev, int port)
+{
+	int rc;
+	struct acp_pci_msi *msi;
+
+	msi = acp_pci_msi[port];
+
+	msi->irqhost = irq_domain_add_linear(msi->node,
+		NR_MSI_IRQS, &acp_pci_msi_host_ops, 0);
+
+	if (msi->irqhost == NULL) {
+		pr_debug("%s: out of memory at line %d\n",
+			__func__, __LINE__);
+		rc = -ENOMEM;
+		goto error_out;
+	}
+
+	rc = acp_pci_msi_init_allocator(msi);
+	if (rc)
+		goto error_out;
+
+	rc = acp_pci_msi_hw_init(msi, pdev);
+	if (rc != 0)
+		goto error_out;
+
+	spin_lock_init(&msi->bitmap_lock);
+	acp_pci_msi[port] = msi;
+	ppc_md.setup_msi_irqs = acp_pci_msi_setup_irqs;
+	ppc_md.teardown_msi_irqs = acp_pci_msi_teardown_irqs;
+	ppc_md.msi_check_device = acp_pci_msi_check_device;
+
+	return 0;
+
+error_out:
+	kfree(msi);
+	return rc;
+}
+
+static int __devinit lsi_msi_probe(struct platform_device *pdev)
+{
+
+	const u32 *pval;
+	int rc = 0;
+	struct device_node *np = pdev->dev.of_node;
+	struct acp_pci_msi *pci_msi;
+	u32 int_enable;
+
+	pci_msi = kzalloc(sizeof(struct acp_pci_msi), GFP_KERNEL);
+	if (!pci_msi) {
+		rc = -ENOMEM;
+		return -1;
+	}
+
+	pci_msi->node = of_node_get(np);
+
+	/* Get the port number from the device-tree */
+	pval = of_get_property(np, "port", NULL);
+	if (pval == NULL) {
+		printk(KERN_ERR "PCIE: Can't find port number for %s\n",
+			np->full_name);
+		return -1;
+	}
+	pci_msi->port = *pval;
+
+	pci_msi->utl_base = of_iomap(np, 0);
+	if (!pci_msi->utl_base) {
+		printk(KERN_ERR "No memory for utl_base\n");
+		return -1;
+	}
+	int_enable = in_le32(pci_msi->utl_base + 0x1004);
+
+	acp_pci_msi[pci_msi->port] = pci_msi;
+
+	acp_pci_msi_init(pdev, pci_msi->port);
+
+	return 0;
+}
+
+static int lsi_msi_remove(struct platform_device *pdev)
+{
+	platform_device_unregister(pdev);
+	return 0;
+}
+
+int acp_pci_msi_irq_handle(int port)
+{
+	struct acp_pci_msi *msi = acp_pci_msi[port];
+	u32 *msg = (u32 *) msi->msg_buf;
+	int rc = -1;
+	int i;
+
+	/*
+	 * Check each message slot.  If the slot is not zero,
+	 * then handle the associated virq.
+	 */
+	for (i = 0; i < 256; i++) {
+		if (msg[i] != 0) {
+			pr_debug("%s msg[%d] = x%x\n", __func__, i, msg[i]);
+			msg[i] = 0;
+			generic_handle_irq(i);
+			rc = 0;
+		}
+	}
+	/* Clear MSI interrupts */
+	out_le32(msi->utl_base + 0x1230, 0xffff);
+	for (i = 0; i < 16; i++)
+		out_le32(msi->utl_base + 0x123c + (i*0xc), 0xffff);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(acp_pci_msi_irq_handle);
+
+static irq_hw_number_t acp_pci_msi_alloc_hwirqs(struct acp_pci_msi *msi,
+	int num)
+{
+	unsigned long flags;
+	int order = get_count_order(num);
+	int offset;
+
+	spin_lock_irqsave(&msi->bitmap_lock, flags);
+	offset = bitmap_find_free_region(msi->acp_pci_msi_bitmap,
+		NR_MSI_IRQS, order);
+
+	spin_unlock_irqrestore(&msi->bitmap_lock, flags);
+
+	pr_debug("%s: allocated 0x%x (2^%d) at offset 0x%x\n",
+		__func__, num, order, offset);
+
+	return offset;
+}
+
+static int acp_pci_msi_check_device(struct pci_dev *pdev, int nvec, int type)
+{
+	int rc = 0;
+	return rc;
+}
+
+static void acp_pci_msi_teardown_irqs(struct pci_dev *pdev)
+{
+	struct msi_desc *entry;
+	struct pci_bus *bus = pdev->bus;
+	struct pci_controller *hose = (struct pci_controller *) bus->sysdata;
+	struct acp_pci_msi *msi_data = acp_pci_msi[hose->indirect_type];
+
+	list_for_each_entry(entry, &pdev->msi_list, list) {
+		if (entry->irq == NO_IRQ)
+			continue;
+		irq_set_msi_desc(entry->irq, NULL);
+		acp_pci_msi_free_hwirqs(msi_data, virq_to_hw(entry->irq), 1);
+		irq_dispose_mapping(entry->irq);
+	}
+	return;
+}
+
+static int acp_pci_msi_setup_irqs(struct pci_dev *pdev, int nvec, int type)
+{
+	irq_hw_number_t hwirq;
+	int rc;
+	unsigned int virq;
+	struct msi_desc *entry;
+	struct msi_msg msg;
+	struct pci_bus *bus = pdev->bus;
+	struct pci_controller *hose = (struct pci_controller *) bus->sysdata;
+	struct acp_pci_msi *msi = acp_pci_msi[hose->indirect_type];
+
+	list_for_each_entry(entry, &pdev->msi_list, list) {
+		hwirq = acp_pci_msi_alloc_hwirqs(msi, 1);
+		if (hwirq < 0) {
+			rc = hwirq;
+			pr_debug("%s: fail allocating msi interrupt\n",
+				__func__);
+			goto out_free;
+		}
+
+		virq = irq_create_mapping(msi->irqhost, hwirq);
+		if ((virq == NO_IRQ) || (virq >= NR_MSI_IRQS)) {
+			pr_debug("%s: fail mapping hwirq 0x%lx\n",
+				__func__, hwirq);
+			acp_pci_msi_free_hwirqs(msi, hwirq, 1);
+			rc = -ENOSPC;
+			goto out_free;
+		}
+
+		pr_debug("%s: hwirq = %d, virq = %d entry = %p\n", __func__,
+			(int) hwirq, virq, entry);
+		printk(KERN_INFO "%s: hwirq = %d, virq = %d entry = %p\n",
+			__func__,
+			(int) hwirq, virq, entry);
+		irq_set_msi_desc(virq, entry);
+
+		msg.address_hi = msi->msi_addr_hi;
+		msg.address_lo = msi->msi_addr_lo + (virq * sizeof(u32));
+		msg.data = MSI_MSG_DATA;
+		pr_debug("%s: msi_msg: hi = x%x, lo = x%x, data = x%x\n",
+			__func__, msg.address_hi, msg.address_lo, msg.data);
+		printk(KERN_INFO "%s: msi_msg: hi = x%x, lo = x%x, data = x%x\n",
+			__func__, msg.address_hi, msg.address_lo, msg.data);
+		write_msi_msg(virq, &msg);
+	}
+	return 0;
+
+out_free:
+	return rc;
+}
+
+static const struct of_device_id lsi_msi_match[] = {
+	{.compatible = "lsi,msi"},
+	{}
+};
+
+MODULE_DEVICE_TABLE(of, lsi_msi_match);
+
+static struct platform_driver lsi_msi_driver = {
+	.driver = {
+		.name = "lsi_msi",
+		.owner = THIS_MODULE,
+		.of_match_table = lsi_msi_match,
+		},
+	.probe = lsi_msi_probe,
+	.remove = lsi_msi_remove,
+};
+
+module_platform_driver(lsi_msi_driver);
+MODULE_AUTHOR("Sangeetha Rao <sangeetha.rao at avagotech.com>");
+MODULE_DESCRIPTION("LSI/Avago PCIe MSI Driver");
+
diff --git a/arch/powerpc/sysdev/lsi_msi.h b/arch/powerpc/sysdev/lsi_msi.h
new file mode 100644
index 0000000..25e3b83
--- /dev/null
+++ b/arch/powerpc/sysdev/lsi_msi.h
@@ -0,0 +1,21 @@
+/*
+* LSI specific PCI-Express support;
+*/
+
+#define NR_MSI_IRQS (256)
+#define MSI_MSG_DATA (0xcece)
+
+struct acp_pci_msi {
+	struct irq_domain *irqhost;
+	u32 msi_addr_lo;
+	u32 msi_addr_hi;
+	unsigned long *acp_pci_msi_bitmap;
+	spinlock_t bitmap_lock;
+	void *msg_buf;
+	void __iomem		*utl_base;
+	struct device_node *node;
+	dma_addr_t msg_buf_dma;
+	int port;
+};
+int acp_pci_msi_irq_handle(int port);
+
diff --git a/arch/powerpc/sysdev/lsi_pci.c b/arch/powerpc/sysdev/lsi_pci.c
index 1236fdc..417488a 100644
--- a/arch/powerpc/sysdev/lsi_pci.c
+++ b/arch/powerpc/sysdev/lsi_pci.c
@@ -20,15 +20,41 @@
 #include <mm/mmu_decl.h>
 
 #include "ppc4xx_pci.h"
+#include "lsi_msi.h"
 #include "../../../drivers/misc/lsi-ncr.h"
 
 #include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/bitmap.h>
+#include <linux/msi.h>
+#include <linux/of_platform.h>
+#include <asm/prom.h>
+#include <asm/hw_irq.h>
+#include <asm/ppc-pci.h>
 
 static int acp_plx;
 
 #undef PRINT_CONFIG_ACCESSES
 /*#define PRINT_CONFIG_ACCESSES*/
 
+struct pciex_port {
+	struct pci_controller	*hose;
+	struct device_node	*node;
+	unsigned int		index;
+	int			endpoint;
+	int			link;
+	int			has_ibpre;
+	unsigned int		sdr_base;
+	dcr_host_t		dcrs;
+	struct resource		cfg_space;
+	struct resource		utl_regs;
+	void __iomem		*utl_base;
+	int	acpChipType;
+};
+
+static struct pciex_port *acp_pciex_ports;
+static unsigned int acp_pciex_port_count;
+
 static int dma_offset_set;
 static u32 last_mpage;
 static u32 last_port;
@@ -188,24 +214,6 @@ out:
 
 #define MAX_PCIE_BUS_MAPPED	0x40
 
-struct pciex_port {
-	struct pci_controller	*hose;
-	struct device_node	*node;
-	unsigned int		index;
-	int			endpoint;
-	int			link;
-	int			has_ibpre;
-	unsigned int		sdr_base;
-	dcr_host_t		dcrs;
-	struct resource		cfg_space;
-	struct resource		utl_regs;
-	void __iomem		*utl_base;
-	int	acpChipType;
-};
-
-static struct pciex_port *acp_pciex_ports;
-static unsigned int acp_pciex_port_count;
-
 struct pciex_hwops {
 	bool want_sdr;
 	int (*core_init)(struct device_node *np);
@@ -854,8 +862,14 @@ acp_pcie_isr(int irq, void *arg)
 
 	/* read the PEI interrupt status register */
 	intr_status = in_le32(mbase + 0x10c0);
-	in_le32(mbase + 0x10c4);
 
+	if (intr_status & 0x80000000) {
+		printk(KERN_INFO "Received MSI interrupt\n");
+		acp_pci_msi_irq_handle(hose->indirect_type);
+		/* Clear MSI interrupt */
+		out_le32(mbase + 0x10c0, 0x80000000);
+		return IRQ_HANDLED;
+	}
 	/* check if this is a PCIe message from an external device */
 	if (intr_status & 0x00000010) {
 		externalPciIntr = 1;
-- 
1.7.9.5



More information about the linux-yocto mailing list