Subject: [PATCH 2/2] powerpc: Add IPIC MSI support

From: Li Li <[email-masked]>
Date: Tue, 04 Dec 2007 18:39:20 +0800

Modified based on discussion on list.

1. Adopt virq_to_hw routine
2. Correct a legacy bug

Implements the IPIC MSI as two level interrupt controller.

Signed-off-by: Tony Li <[email-masked]>
---
 arch/powerpc/boot/dts/mpc8377_mds.dts     |   14 ++
 arch/powerpc/boot/dts/mpc8378_mds.dts     |   14 ++
 arch/powerpc/boot/dts/mpc8379_mds.dts     |   14 ++
 arch/powerpc/platforms/83xx/Kconfig       |    6 +
 arch/powerpc/platforms/83xx/mpc837x_mds.c |    7 +
 arch/powerpc/sysdev/Makefile              |    1 +
 arch/powerpc/sysdev/ipic_msi.c            |  359 +++++++++++++++++++++++++++++
 include/asm-powerpc/ipic_msi.h            |   66 ++++++
 8 files changed, 481 insertions(+), 0 deletions(-)
 create mode 100644 arch/powerpc/sysdev/ipic_msi.c
 create mode 100644 include/asm-powerpc/ipic_msi.h

diff --git a/arch/powerpc/boot/dts/mpc8377_mds.dts b/arch/powerpc/boot/dts/mpc8377_mds.dts
index 8f54349..c11e529 100644
--- a/arch/powerpc/boot/dts/mpc8377_mds.dts
+++ b/arch/powerpc/boot/dts/mpc8377_mds.dts
@@ -210,6 +210,20 @@
 			#interrupt-cells = <2>;
 			reg = <700 100>;
 		};
+
+		ipic-msi@7c0 {
+			compatible = "fsl,ipic-msi";
+			reg = <7c0 40>;
+			interrupts = < 43 8
+				       4  8
+				       51 8
+				       52 8
+				       56 8
+				       57 8
+				       58 8
+				       59 8 >;
+			interrupt-parent = < &ipic >;
+		};
 	};
 
 	pci@e0008500 {
diff --git a/arch/powerpc/boot/dts/mpc8378_mds.dts b/arch/powerpc/boot/dts/mpc8378_mds.dts
index e09b0c5..7b0bf1b 100644
--- a/arch/powerpc/boot/dts/mpc8378_mds.dts
+++ b/arch/powerpc/boot/dts/mpc8378_mds.dts
@@ -192,6 +192,20 @@
 			#interrupt-cells = <2>;
 			reg = <700 100>;
 		};
+
+		ipic-msi@7c0 {
+			compatible = "fsl,ipic-msi";
+			reg = <7c0 40>;
+			interrupts = < 43 8
+				       4  8
+				       51 8
+				       52 8
+				       56 8
+				       57 8
+				       58 8
+				       59 8 >;
+			interrupt-parent = < &ipic >;
+		};
 	};
 
 	pci@e0008500 {
diff --git a/arch/powerpc/boot/dts/mpc8379_mds.dts b/arch/powerpc/boot/dts/mpc8379_mds.dts
index cdb4426..9fe4bd2 100644
--- a/arch/powerpc/boot/dts/mpc8379_mds.dts
+++ b/arch/powerpc/boot/dts/mpc8379_mds.dts
@@ -236,6 +236,20 @@
 			#interrupt-cells = <2>;
 			reg = <700 100>;
 		};
+
+		ipic-msi@7c0 {
+			compatible = "fsl,ipic-msi";
+			reg = <7c0 40>;
+			interrupts = < 43 8
+				       4  8
+				       51 8
+				       52 8
+				       56 8
+				       57 8
+				       58 8
+				       59 8 >;
+			interrupt-parent = < &ipic >;
+		};
 	};
 
 	pci@e0008500 {
diff --git a/arch/powerpc/platforms/83xx/Kconfig b/arch/powerpc/platforms/83xx/Kconfig
index 00154c5..4c51f78 100644
--- a/arch/powerpc/platforms/83xx/Kconfig
+++ b/arch/powerpc/platforms/83xx/Kconfig
@@ -88,6 +88,12 @@ config PPC_MPC837x
 	select FSL_SERDES
 	default y if MPC837x_MDS
 
+config IPIC_MSI
+	bool
+	depends on PCI_MSI
+	default y if PPC_MPC837x
+	default n
+
 config PPC_MPC83XX_PCIE
 	bool "MPC837X PCI Express support"
 	depends on PCIEPORTBUS && PPC_MPC837x
diff --git a/arch/powerpc/platforms/83xx/mpc837x_mds.c b/arch/powerpc/platforms/83xx/mpc837x_mds.c
index b1d3570..f68a7e9 100644
--- a/arch/powerpc/platforms/83xx/mpc837x_mds.c
+++ b/arch/powerpc/platforms/83xx/mpc837x_mds.c
@@ -17,6 +17,7 @@
 
 #include <asm/time.h>
 #include <asm/ipic.h>
+#include <asm/ipic_msi.h>
 #include <asm/udbg.h>
 #include <asm/prom.h>
 
@@ -94,6 +95,12 @@ static void __init mpc837x_mds_init_IRQ(void)
 	 * in case the boot rom changed something on us.
 	 */
 	ipic_set_default_priority();
+
+	np = of_find_compatible_node(NULL, NULL, "fsl,ipic-msi");
+	if (!np)
+		return;
+
+	ipic_msi_init(np, ipic_msi_cascade);
 }
 
 /*
diff --git a/arch/powerpc/sysdev/Makefile b/arch/powerpc/sysdev/Makefile
index 99a77d7..5946b6a 100644
--- a/arch/powerpc/sysdev/Makefile
+++ b/arch/powerpc/sysdev/Makefile
@@ -25,6 +25,7 @@ ifeq ($(CONFIG_PPC_MERGE),y)
 obj-$(CONFIG_PPC_INDIRECT_PCI)	+= indirect_pci.o
 obj-$(CONFIG_PPC_I8259)		+= i8259.o
 obj-$(CONFIG_PPC_83xx)		+= ipic.o
+obj-$(CONFIG_IPIC_MSI)		+= ipic_msi.o
 obj-$(CONFIG_4xx)		+= uic.o
 obj-$(CONFIG_XILINX_VIRTEX)	+= xilinx_intc.o
 endif
diff --git a/arch/powerpc/sysdev/ipic_msi.c b/arch/powerpc/sysdev/ipic_msi.c
new file mode 100644
index 0000000..6c54f50
--- /dev/null
+++ b/arch/powerpc/sysdev/ipic_msi.c
@@ -0,0 +1,359 @@
+/*
+ * arch/powerpc/sysdev/ipic_msi.c
+ *
+ * IPIC MSI routines implementations.
+ *
+ * Auther: Tony Li <[email-masked]>
+ *
+ * Copyright (c) 2007 Freescale Semiconductor, 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/pci.h>
+#include <linux/msi.h>
+#include <linux/irq.h>
+
+#include <asm/ipic_msi.h>
+
+#define MSIR0	0x43
+#define MSIR1	0x4
+#define MSIR2	0x51
+#define MSIR3	0x52
+#define MSIR4	0x56
+#define MSIR5	0x57
+#define MSIR6	0x58
+#define MSIR7	0x59
+
+
+static struct ipic_msi *ipic_msi;
+static DEFINE_SPINLOCK(ipic_msi_lock);
+static DEFINE_SPINLOCK(ipic_msi_bitmap_lock);
+
+static inline u32 ipic_msi_read(volatile u32 __iomem *base, unsigned int reg)
+{
+	return in_be32(base + (reg >> 2));
+}
+
+static inline void ipic_msi_write(volatile u32 __iomem *base,
+					unsigned int reg, u32 value)
+{
+	out_be32(base + (reg >> 2), value);
+}
+
+static struct ipic_msi * ipic_msi_from_irq(unsigned int virq)
+{
+	return ipic_msi;
+}
+
+#define	ipic_msi_irq_to_hw(virq)	virq_to_hw(virq)
+static void ipic_msi_unmask(unsigned int virq)
+{
+	struct ipic_msi *msi = ipic_msi_from_irq(virq);
+	unsigned int src = ipic_msi_irq_to_hw(virq);
+	unsigned long flags;
+	u32 temp;
+
+	spin_lock_irqsave(&ipic_msi_lock, flags);
+	temp = ipic_msi_read(msi->regs, IPIC_MSIMR);
+	ipic_msi_write(msi->regs, IPIC_MSIMR,
+		temp & ~(1 << (src / msi->int_per_msir)));
+
+	spin_unlock_irqrestore(&ipic_msi_lock, flags);
+}
+
+static void ipic_msi_mask(unsigned int virq)
+{
+	struct ipic_msi *msi = ipic_msi_from_irq(virq);
+	unsigned int src = ipic_msi_irq_to_hw(virq);
+	unsigned long flags;
+	u32 temp;
+
+	spin_lock_irqsave(&ipic_msi_lock, flags);
+
+	temp = ipic_msi_read(msi->regs, IPIC_MSIMR);
+	ipic_msi_write(msi->regs, IPIC_MSIMR,
+		temp | (1 << (src / msi->int_per_msir)));
+
+	spin_unlock_irqrestore(&ipic_msi_lock, flags);
+}
+/*
+ * We do not need this actually. The MSIR register has been read once
+ * in ipic_msi_get_irq. So, this MSI interrupt has been acked
+ */
+static void ipic_msi_ack(unsigned int virq)
+{
+	struct ipic_msi *msi = ipic_msi_from_irq(virq);
+	unsigned int src = ipic_msi_irq_to_hw(virq);
+	unsigned long flags;
+
+	spin_lock_irqsave(&ipic_msi_lock, flags);
+
+	ipic_msi_read(msi->regs, IPIC_MSIR0 + (4 * (src / msi->int_per_msir)));
+
+	spin_unlock_irqrestore(&ipic_msi_lock,flags);
+}
+
+static struct irq_chip ipic_msi_chip = {
+	.typename = " IPIC MSI ",
+	.unmask = ipic_msi_unmask,
+	.mask = ipic_msi_mask,
+	.ack = ipic_msi_ack,
+};
+
+static int ipic_msi_host_map(struct irq_host *h, unsigned int virq,
+				irq_hw_number_t hw)
+{
+	struct ipic_msi *msi = h->host_data;
+	struct irq_chip *chip = msi->hc_irq;
+
+	set_irq_chip_data(virq, msi);
+	get_irq_desc(virq)->status |= IRQ_TYPE_EDGE_FALLING;
+
+	set_irq_chip_and_handler(virq, chip, handle_edge_irq);
+	return 0;
+}
+
+static struct irq_host_ops ipic_msi_host_ops = {
+	.map = ipic_msi_host_map,
+};
+
+irq_hw_number_t ipic_msi_alloc_hwirqs(struct ipic_msi *msi, int num)
+{
+	unsigned long flags;
+	int offset, order = get_count_order(num);
+
+	spin_lock_irqsave(&ipic_msi_bitmap_lock, flags);
+
+	offset = bitmap_find_free_region(msi->ipic_msi_bitmap,
+			msi->nr_msir * msi->int_per_msir, order);
+
+	spin_unlock_irqrestore(&ipic_msi_bitmap_lock, flags);
+
+	pr_debug("%s: allocated 0x%x (2^%d) at offset 0x%x\n",
+		__FUNCTION__, num, order, offset);
+
+	return offset;
+}
+
+void ipic_msi_free_hwirqs(struct ipic_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",
+		__FUNCTION__, num, order, offset);
+
+	spin_lock_irqsave(&ipic_msi_bitmap_lock, flags);
+	bitmap_release_region(msi->ipic_msi_bitmap, offset, order);
+	spin_unlock_irqrestore(&ipic_msi_bitmap_lock, flags);
+}
+
+static int ipic_msi_init_allocator(struct ipic_msi *msi)
+{
+	int size;
+
+	size = BITS_TO_LONGS(msi->nr_msir * msi->int_per_msir) * sizeof(long);
+	msi->ipic_msi_bitmap = alloc_maybe_bootmem(size, GFP_KERNEL);
+
+	if (msi->ipic_msi_bitmap == NULL) {
+		pr_debug("%s: ENOMEM allocating allocator bitmap!\n",
+				__FUNCTION__);
+		return -ENOMEM;
+	}
+	memset(msi->ipic_msi_bitmap, 0, size);
+
+	return 0;
+}
+
+static void ipic_msi_compose_msg(struct ipic_msi *msi, int hwirq,
+						struct msi_msg *msg)
+{
+	unsigned int srs;
+	unsigned int ibs;
+
+	srs = hwirq / msi->int_per_msir;
+	ibs = hwirq - srs * msi->int_per_msir;
+
+	msg->address_lo = msi->msi_addr_lo;
+	msg->address_hi = msi->msi_addr_hi;
+	msg->data = (srs << 5) | (ibs & 0x1F);
+
+	pr_debug("%s: allocated srs: %d, ibs: %d\n",
+		__FUNCTION__, srs, ibs);
+
+}
+
+static int ipic_msi_setup_irqs(struct pci_dev *pdev, int nvec, int type)
+{
+	struct ipic_msi *msi = ipic_msi;
+	irq_hw_number_t hwirq;
+	unsigned int virq;
+	struct msi_desc *entry;
+	struct msi_msg msg;
+
+	list_for_each_entry(entry, &pdev->msi_list, list) {
+		hwirq = ipic_msi_alloc_hwirqs(msi, 1);
+		if (hwirq < 0) {
+			pr_debug("%s: fail allocating msi interrupt\n",
+					__FUNCTION__);
+			return hwirq;
+		}
+
+		/* This hwirq belongs to the irq_host other than irq_host of IPIC
+ 		 * So, it is independent to hwirq of IPIC */
+		virq = irq_create_mapping(msi->irqhost, hwirq);
+		if (virq == NO_IRQ) {
+			pr_debug("%s: fail mapping hwirq 0x%lx\n",
+					__FUNCTION__, hwirq);
+			ipic_msi_free_hwirqs(msi, hwirq, 1);
+			return -ENOSPC;
+		}
+		set_irq_msi(virq, entry);
+		ipic_msi_compose_msg(msi, hwirq, &msg);
+		write_msi_msg(virq, &msg);
+	}
+
+	return 0;
+}
+
+static void ipic_msi_teardown_irqs(struct pci_dev *pdev)
+{
+	struct msi_desc *entry;
+	struct ipic_msi *msi = ipic_msi;
+
+	list_for_each_entry(entry, &pdev->msi_list, list) {
+		if (entry->irq == NO_IRQ)
+			continue;
+		set_irq_msi(entry->irq, NULL);
+		ipic_msi_free_hwirqs(msi, virq_to_hw(entry->irq), 1);
+		irq_dispose_mapping(entry->irq);
+	}
+
+	return;
+}
+
+void __init ipic_msi_init(struct device_node *node,
+			void (*handler)(unsigned int irq, struct irq_desc *desc))
+{
+	struct ipic_msi *msi;
+	struct resource res;
+	int i;
+	int rc = 0;
+
+	msi = alloc_maybe_bootmem(sizeof(*msi), GFP_KERNEL);
+	if (msi == NULL)
+		return;
+
+	memset(msi, 0, sizeof(*msi));
+
+
+	msi->irqhost = irq_alloc_host(of_node_get(node), IRQ_HOST_MAP_LINEAR,
+					NR_MSIR, &ipic_msi_host_ops, 0);
+	if (msi->irqhost == NULL) {
+		of_node_put(node);
+		goto out;
+	}
+
+	rc = of_address_to_resource(node, 0, &res);
+	if (rc) {
+		of_node_put(node);
+		goto out;
+	}
+
+	msi->regs = ioremap(res.start, res.end - res.start + 1);
+	msi->irqhost->host_data = msi;
+	msi->hc_irq = &ipic_msi_chip;
+
+	msi->msi_addr_lo = 0xE00007F8;
+	msi->msi_addr_hi = 0;
+	msi->nr_msir = ARRAY_SIZE(msi->msir);
+	msi->int_per_msir = 32;
+	for (i = 0; i < msi->nr_msir; i++) {
+		unsigned int virt_msir = irq_of_parse_and_map(node, i);
+		if (virt_msir != NO_IRQ) {
+			set_irq_data(virt_msir, msi);
+			set_irq_chained_handler(virt_msir, handler);
+			msi->msir[i] = virt_msir;
+		} else
+			msi->msir[i] = NO_IRQ;
+	}
+
+	rc = ipic_msi_init_allocator(msi);
+	if (rc)
+		goto out;
+
+	ipic_msi = msi;
+
+	WARN_ON(ppc_md.setup_msi_irqs);
+	ppc_md.setup_msi_irqs = ipic_msi_setup_irqs;
+	ppc_md.teardown_msi_irqs = ipic_msi_teardown_irqs;
+
+	return;
+
+out:
+	if (mem_init_done)
+		kfree(msi);
+
+	return;
+}
+
+static int ipic_msi_get_irq(struct ipic_msi *msi, int virt_msir)
+{
+	int msir = -1;
+	unsigned int temp;
+	unsigned int offset;
+	int i;
+
+	for (i = 0; i < msi->nr_msir; i++)
+		if (virt_msir == msi->msir[i]) {
+			msir = i;
+			break;
+		}
+
+	if (i >= msi->nr_msir)
+		return NO_IRQ;
+
+	temp = ipic_msi_read(msi->regs, IPIC_MSIR0 + (i * 4));
+	offset = ffs(temp) - 1;
+
+	return irq_linear_revmap(msi->irqhost, (msir * msi->int_per_msir + offset));
+}
+
+void ipic_msi_cascade(unsigned int irq, struct irq_desc *desc)
+{
+	struct ipic_msi *msi;
+	unsigned int cascade_irq;
+
+	spin_lock(&desc->lock);
+	if (desc->chip->mask_ack)
+		desc->chip->mask_ack(irq);
+	else {
+		desc->chip->mask(irq);
+		desc->chip->ack(irq);
+	}
+
+	if (unlikely(desc->status & IRQ_INPROGRESS))
+		goto unlock;
+
+	desc->status |= IRQ_INPROGRESS;
+	msi = desc->handler_data;
+	cascade_irq = ipic_msi_get_irq(msi, irq);
+
+	spin_unlock(&desc->lock);
+
+	if (cascade_irq != NO_IRQ)
+		generic_handle_irq(cascade_irq);
+
+	spin_lock(&desc->lock);
+	desc->status &= ~IRQ_INPROGRESS;
+	if (!(desc->status & IRQ_DISABLED) && desc->chip->unmask)
+		desc->chip->unmask(irq);
+
+unlock:
+	spin_unlock(&desc->lock);
+}
diff --git a/include/asm-powerpc/ipic_msi.h b/include/asm-powerpc/ipic_msi.h
new file mode 100644
index 0000000..2f578da
--- /dev/null
+++ b/include/asm-powerpc/ipic_msi.h
@@ -0,0 +1,66 @@
+/*
+ * include/asm-powerpc/ipic_msi.h
+ *
+ * IPIC MSI structure.
+ *
+ * Author: Tony Li <[email-masked]>
+ *
+ * Copyright (c) 2007 Freescale Semiconductor, 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.
+ */
+#ifndef _ASM_POWERPC_IPIC_MSI_H
+#define _ASM_POWERPC_IPIC_MSI_H
+
+#define NR_MSIR	8
+/* MSI interrupt register offset compared to MSI base address (0xc0) */
+#define IPIC_MSIR0	0x0	/* MSI Interrupt Reg 0 */
+#define IPIC_MSIR1	0x4	/* MSI Interrupt Reg 1 */
+#define IPIC_MSIR2	0x8	/* MSI Interrupt Reg 2 */
+#define IPIC_MSIR3	0xC	/* MSI Interrupt Reg 3 */
+#define IPIC_MSIR4	0x10	/* MSI Interrupt Reg 4 */
+#define IPIC_MSIR5	0x14	/* MSI Interrupt Reg 5 */
+#define IPIC_MSIR6	0x18	/* MSI Interrupt Reg 6 */
+#define IPIC_MSIR7	0x1C	/* MSI Interrupt Reg 7 */
+#define IPIC_MSIMR	0x30	/* MSI Interrupt Mask Reg */
+#define IPIC_MSISR	0x34	/* MSI Interrupt Status Reg */
+#define IPIC_MSIIR	0x38	/* MSI Interrupt Index Reg */
+
+struct ipic_msi {
+	volatile u32 __iomem *regs;
+
+	struct irq_host *irqhost;
+	struct irq_chip *hc_irq;
+
+	unsigned long cascade_irq;
+	unsigned int msir[8];
+
+	u32 msi_addr_lo;
+	u32 msi_addr_hi;
+
+	int nr_msir;
+	int int_per_msir;
+
+	void *ipic_msi_bitmap;
+};
+
+#ifdef CONFIG_IPIC_MSI
+extern void ipic_msi_cascade(unsigned int irq, struct irq_desc *desc);
+extern void __init ipic_msi_init(struct device_node *node,
+		void (*handler)(unsigned int irq, struct irq_desc *desc));
+#else
+void __inline__ ipic_msi_cascade(unsigned int irq, struct irq_desc *desc)
+{
+	return;
+}
+void __inline__ __init ipic_msi_init(struct device_node *node,
+		void (*handler)(unsigned int irq, struct irq_desc *desc))
+{
+	return;
+}
+#endif
+
+#endif /* _ASM_POWERPC_IPIC_MSI_H */
-- 
1.5.2