davinci: INTC: add support for TI cp_intc

Add support for Texas Instuments Common Platform Interrupt Controller
(cp_intc) used on DA830/OMAP-L137.

Signed-off-by: Steve Chen <schen@mvista.com>
Signed-off-by: Mark Greer <mgreer@mvista.com>
Signed-off-by: Sergei Shtylyov <sshtylyov@ru.mvista.com>
Signed-off-by: Kevin Hilman <khilman@deeprootsystems.com>
diff --git a/arch/arm/mach-davinci/Kconfig b/arch/arm/mach-davinci/Kconfig
index a9c78bc..56cd03f 100644
--- a/arch/arm/mach-davinci/Kconfig
+++ b/arch/arm/mach-davinci/Kconfig
@@ -1,5 +1,8 @@
 if ARCH_DAVINCI
 
+config CP_INTC
+	bool
+
 menu "TI DaVinci Implementations"
 
 comment "DaVinci Core Type"
diff --git a/arch/arm/mach-davinci/Makefile b/arch/arm/mach-davinci/Makefile
index 1674661..9a696ae 100644
--- a/arch/arm/mach-davinci/Makefile
+++ b/arch/arm/mach-davinci/Makefile
@@ -8,6 +8,7 @@
 			   gpio.o devices.o dma.o usb.o
 
 obj-$(CONFIG_DAVINCI_MUX)		+= mux.o
+obj-$(CONFIG_CP_INTC)			+= cp_intc.o
 
 # Chip specific
 obj-$(CONFIG_ARCH_DAVINCI_DM644x)       += dm644x.o
diff --git a/arch/arm/mach-davinci/cp_intc.c b/arch/arm/mach-davinci/cp_intc.c
new file mode 100644
index 0000000..96c8e97
--- /dev/null
+++ b/arch/arm/mach-davinci/cp_intc.c
@@ -0,0 +1,161 @@
+/*
+ * TI Common Platform Interrupt Controller (cp_intc) driver
+ *
+ * Author: Steve Chen <schen@mvista.com>
+ * Copyright (C) 2008-2009, MontaVista Software, Inc. <source@mvista.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+
+#include <mach/cp_intc.h>
+
+static void __iomem *cp_intc_base;
+
+static inline unsigned int cp_intc_read(unsigned offset)
+{
+	return __raw_readl(cp_intc_base + offset);
+}
+
+static inline void cp_intc_write(unsigned long value, unsigned offset)
+{
+	__raw_writel(value, cp_intc_base + offset);
+}
+
+static void cp_intc_ack_irq(unsigned int irq)
+{
+	cp_intc_write(irq, CP_INTC_SYS_STAT_IDX_CLR);
+}
+
+/* Disable interrupt */
+static void cp_intc_mask_irq(unsigned int irq)
+{
+	/* XXX don't know why we need to disable nIRQ here... */
+	cp_intc_write(1, CP_INTC_HOST_ENABLE_IDX_CLR);
+	cp_intc_write(irq, CP_INTC_SYS_ENABLE_IDX_CLR);
+	cp_intc_write(1, CP_INTC_HOST_ENABLE_IDX_SET);
+}
+
+/* Enable interrupt */
+static void cp_intc_unmask_irq(unsigned int irq)
+{
+	cp_intc_write(irq, CP_INTC_SYS_ENABLE_IDX_SET);
+}
+
+static int cp_intc_set_irq_type(unsigned int irq, unsigned int flow_type)
+{
+	unsigned reg		= BIT_WORD(irq);
+	unsigned mask		= BIT_MASK(irq);
+	unsigned polarity	= cp_intc_read(CP_INTC_SYS_POLARITY(reg));
+	unsigned type		= cp_intc_read(CP_INTC_SYS_TYPE(reg));
+
+	switch (flow_type) {
+	case IRQ_TYPE_EDGE_RISING:
+		polarity |= mask;
+		type |= mask;
+		break;
+	case IRQ_TYPE_EDGE_FALLING:
+		polarity &= ~mask;
+		type |= mask;
+		break;
+	case IRQ_TYPE_LEVEL_HIGH:
+		polarity |= mask;
+		type &= ~mask;
+		break;
+	case IRQ_TYPE_LEVEL_LOW:
+		polarity &= ~mask;
+		type &= ~mask;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	cp_intc_write(polarity, CP_INTC_SYS_POLARITY(reg));
+	cp_intc_write(type, CP_INTC_SYS_TYPE(reg));
+
+	return 0;
+}
+
+static struct irq_chip cp_intc_irq_chip = {
+	.name		= "cp_intc",
+	.ack		= cp_intc_ack_irq,
+	.mask		= cp_intc_mask_irq,
+	.unmask		= cp_intc_unmask_irq,
+	.set_type	= cp_intc_set_irq_type,
+};
+
+void __init cp_intc_init(void __iomem *base, unsigned short num_irq,
+			 u8 *irq_prio)
+{
+	unsigned num_reg	= BITS_TO_LONGS(num_irq);
+	int i;
+
+	cp_intc_base = base;
+
+	cp_intc_write(0, CP_INTC_GLOBAL_ENABLE);
+
+	/* Disable all host interrupts */
+	cp_intc_write(0, CP_INTC_HOST_ENABLE(0));
+
+	/* Disable system interrupts */
+	for (i = 0; i < num_reg; i++)
+		cp_intc_write(~0, CP_INTC_SYS_ENABLE_CLR(i));
+
+	/* Set to normal mode, no nesting, no priority hold */
+	cp_intc_write(0, CP_INTC_CTRL);
+	cp_intc_write(0, CP_INTC_HOST_CTRL);
+
+	/* Clear system interrupt status */
+	for (i = 0; i < num_reg; i++)
+		cp_intc_write(~0, CP_INTC_SYS_STAT_CLR(i));
+
+	/* Enable nIRQ (what about nFIQ?) */
+	cp_intc_write(1, CP_INTC_HOST_ENABLE_IDX_SET);
+
+	/*
+	 * Priority is determined by host channel: lower channel number has
+	 * higher priority i.e. channel 0 has highest priority and channel 31
+	 * had the lowest priority.
+	 */
+	num_reg = (num_irq + 3) >> 2;	/* 4 channels per register */
+	if (irq_prio) {
+		unsigned j, k;
+		u32 val;
+
+		for (k = i = 0; i < num_reg; i++) {
+			for (val = j = 0; j < 4; j++, k++) {
+				val >>= 8;
+				if (k < num_irq)
+					val |= irq_prio[k] << 24;
+			}
+
+			cp_intc_write(val, CP_INTC_CHAN_MAP(i));
+		}
+	} else	{
+		/*
+		 * Default everything to channel 15 if priority not specified.
+		 * Note that channel 0-1 are mapped to nFIQ and channels 2-31
+		 * are mapped to nIRQ.
+		 */
+		for (i = 0; i < num_reg; i++)
+			cp_intc_write(0x0f0f0f0f, CP_INTC_CHAN_MAP(i));
+	}
+
+	/* Set up genirq dispatching for cp_intc */
+	for (i = 0; i < num_irq; i++) {
+		set_irq_chip(i, &cp_intc_irq_chip);
+		set_irq_flags(i, IRQF_VALID | IRQF_PROBE);
+		set_irq_handler(i, handle_edge_irq);
+	}
+
+	/* Enable global interrupt */
+	cp_intc_write(1, CP_INTC_GLOBAL_ENABLE);
+}
diff --git a/arch/arm/mach-davinci/include/mach/cp_intc.h b/arch/arm/mach-davinci/include/mach/cp_intc.h
new file mode 100644
index 0000000..c4d27ee
--- /dev/null
+++ b/arch/arm/mach-davinci/include/mach/cp_intc.h
@@ -0,0 +1,57 @@
+/*
+ * TI Common Platform Interrupt Controller (cp_intc) definitions
+ *
+ * Author: Steve Chen <schen@mvista.com>
+ * Copyright (C) 2008-2009, MontaVista Software, Inc. <source@mvista.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+#ifndef __ASM_HARDWARE_CP_INTC_H
+#define __ASM_HARDWARE_CP_INTC_H
+
+#define CP_INTC_REV			0x00
+#define CP_INTC_CTRL			0x04
+#define CP_INTC_HOST_CTRL		0x0C
+#define CP_INTC_GLOBAL_ENABLE		0x10
+#define CP_INTC_GLOBAL_NESTING_LEVEL	0x1C
+#define CP_INTC_SYS_STAT_IDX_SET	0x20
+#define CP_INTC_SYS_STAT_IDX_CLR	0x24
+#define CP_INTC_SYS_ENABLE_IDX_SET	0x28
+#define CP_INTC_SYS_ENABLE_IDX_CLR	0x2C
+#define CP_INTC_GLOBAL_WAKEUP_ENABLE	0x30
+#define CP_INTC_HOST_ENABLE_IDX_SET	0x34
+#define CP_INTC_HOST_ENABLE_IDX_CLR	0x38
+#define CP_INTC_PACING_PRESCALE 	0x40
+#define CP_INTC_VECTOR_BASE		0x50
+#define CP_INTC_VECTOR_SIZE		0x54
+#define CP_INTC_VECTOR_NULL		0x58
+#define CP_INTC_PRIO_IDX		0x80
+#define CP_INTC_PRIO_VECTOR		0x84
+#define CP_INTC_SECURE_ENABLE		0x90
+#define CP_INTC_SECURE_PRIO_IDX 	0x94
+#define CP_INTC_PACING_PARAM(n) 	(0x0100 + (n << 4))
+#define CP_INTC_PACING_DEC(n)		(0x0104 + (n << 4))
+#define CP_INTC_PACING_MAP(n)		(0x0108 + (n << 4))
+#define CP_INTC_SYS_RAW_STAT(n) 	(0x0200 + (n << 2))
+#define CP_INTC_SYS_STAT_CLR(n) 	(0x0280 + (n << 2))
+#define CP_INTC_SYS_ENABLE_SET(n)	(0x0300 + (n << 2))
+#define CP_INTC_SYS_ENABLE_CLR(n)	(0x0380 + (n << 2))
+#define CP_INTC_CHAN_MAP(n)		(0x0400 + (n << 2))
+#define CP_INTC_HOST_MAP(n)		(0x0800 + (n << 2))
+#define CP_INTC_HOST_PRIO_IDX(n)	(0x0900 + (n << 2))
+#define CP_INTC_SYS_POLARITY(n) 	(0x0D00 + (n << 2))
+#define CP_INTC_SYS_TYPE(n)		(0x0D80 + (n << 2))
+#define CP_INTC_WAKEUP_ENABLE(n)	(0x0E00 + (n << 2))
+#define CP_INTC_DEBUG_SELECT(n) 	(0x0F00 + (n << 2))
+#define CP_INTC_SYS_SECURE_ENABLE(n)	(0x1000 + (n << 2))
+#define CP_INTC_HOST_NESTING_LEVEL(n)	(0x1100 + (n << 2))
+#define CP_INTC_HOST_ENABLE(n)		(0x1500 + (n << 2))
+#define CP_INTC_HOST_PRIO_VECTOR(n)	(0x1600 + (n << 2))
+#define CP_INTC_VECTOR_ADDR(n)		(0x2000 + (n << 2))
+
+void __init cp_intc_init(void __iomem *base, unsigned short num_irq,
+			 u8 *irq_prio);
+
+#endif	/* __ASM_HARDWARE_CP_INTC_H */