sh: intc: IRQ auto-distribution support.

This implements support for hardware-managed IRQ balancing as implemented
by SH-X3 cores (presently only hooked up for SH7786, but can probably be
carried over to other SH-X3 cores, too).

CPUs need to specify their distribution register along with the mask
definitions, as these follow the same format. Peripheral IRQs that don't
opt out of balancing will be automatically distributed at the whim of the
hardware block, while each CPU needs to verify whether it is handling the
IRQ or not, especially before clearing the mask.

Signed-off-by: Paul Mundt <lethal@linux-sh.org>
diff --git a/arch/sh/include/asm/irq.h b/arch/sh/include/asm/irq.h
index df8e150..99c593b 100644
--- a/arch/sh/include/asm/irq.h
+++ b/arch/sh/include/asm/irq.h
@@ -12,6 +12,14 @@
 #define NR_IRQS_LEGACY		8	/* Legacy external IRQ0-7 */
 
 /*
+ * This is a special IRQ number for indicating that no IRQ has been
+ * triggered and to simply ignore the IRQ dispatch. This is a special
+ * case that can happen with IRQ auto-distribution when multiple CPUs
+ * are woken up and signalled in parallel.
+ */
+#define NO_IRQ_IGNORE		((unsigned int)-1)
+
+/*
  * Convert back and forth between INTEVT and IRQ values.
  */
 #ifdef CONFIG_CPU_HAS_INTEVT
@@ -53,6 +61,14 @@
 # define irq_ctx_exit(cpu) do { } while (0)
 #endif
 
+#ifdef CONFIG_INTC_BALANCING
+extern unsigned int irq_lookup(unsigned int irq);
+extern void irq_finish(unsigned int irq);
+#else
+#define irq_lookup(irq)		(irq)
+#define irq_finish(irq)		do { } while (0)
+#endif
+
 #include <asm-generic/irq.h>
 #ifdef CONFIG_CPU_SH5
 #include <cpu/irq.h>
diff --git a/arch/sh/kernel/cpu/sh4a/setup-sh7786.c b/arch/sh/kernel/cpu/sh4a/setup-sh7786.c
index 235edf8..d733603 100644
--- a/arch/sh/kernel/cpu/sh4a/setup-sh7786.c
+++ b/arch/sh/kernel/cpu/sh4a/setup-sh7786.c
@@ -573,7 +573,6 @@
 	&usb_ohci_device,
 };
 
-
 /*
  * Please call this function if your platform board
  * use external clock for USB
@@ -581,6 +580,7 @@
 #define USBCTL0		0xffe70858
 #define CLOCK_MODE_MASK 0xffffff7f
 #define EXT_CLOCK_MODE  0x00000080
+
 void __init sh7786_usb_use_exclock(void)
 {
 	u32 val = __raw_readl(USBCTL0) & CLOCK_MODE_MASK;
@@ -598,6 +598,7 @@
 #define PLL_ENB		0x00000002
 #define PHY_RST		0x00000004
 #define ACT_PLL_STATUS	0xc0000000
+
 static void __init sh7786_usb_setup(void)
 {
 	int i = 1000000;
@@ -753,9 +754,19 @@
 #define INTMSK2		0xfe410068
 #define INTMSKCLR2	0xfe41006c
 
+#define INTDISTCR0	0xfe4100b0
+#define INTDISTCR1	0xfe4100b4
+#define INTACK		0xfe4100b8
+#define INTACKCLR	0xfe4100bc
+#define INT2DISTCR0	0xfe410900
+#define INT2DISTCR1	0xfe410904
+#define INT2DISTCR2	0xfe410908
+#define INT2DISTCR3	0xfe41090c
+
 static struct intc_mask_reg mask_registers[] __initdata = {
 	{ CnINTMSK0, CnINTMSKCLR0, 32,
-	  { IRQ0, IRQ1, IRQ2, IRQ3, IRQ4, IRQ5, IRQ6, IRQ7 } },
+	  { IRQ0, IRQ1, IRQ2, IRQ3, IRQ4, IRQ5, IRQ6, IRQ7 },
+	    INTC_SMP_BALANCING(INTDISTCR0) },
 	{ INTMSK2, INTMSKCLR2, 32,
 	  { IRL0_LLLL, IRL0_LLLH, IRL0_LLHL, IRL0_LLHH,
 	    IRL0_LHLL, IRL0_LHLH, IRL0_LHHL, IRL0_LHHH,
@@ -767,7 +778,8 @@
 	    IRL4_HHLL, IRL4_HHLH, IRL4_HHHL, 0, } },
 	{ CnINT2MSKR0, CnINT2MSKCR0 , 32,
 	  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-	    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, WDT } },
+	    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, WDT },
+	    INTC_SMP_BALANCING(INT2DISTCR0) },
 	{ CnINT2MSKR1, CnINT2MSKCR1, 32,
 	  { TMU0_0, TMU0_1, TMU0_2, TMU0_3, TMU1_0, TMU1_1, TMU1_2, 0,
 	    DMAC0_0, DMAC0_1, DMAC0_2, DMAC0_3, DMAC0_4, DMAC0_5, DMAC0_6,
@@ -776,14 +788,14 @@
 	    HPB_0, HPB_1, HPB_2,
 	    SCIF0_0, SCIF0_1, SCIF0_2, SCIF0_3,
 	    SCIF1,
-	    TMU2, TMU3, 0, } },
+	    TMU2, TMU3, 0, }, INTC_SMP_BALANCING(INT2DISTCR1) },
 	{ CnINT2MSKR2, CnINT2MSKCR2, 32,
 	  { 0, 0, SCIF2, SCIF3, SCIF4, SCIF5,
 	    Eth_0, Eth_1,
 	    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 	    PCIeC0_0, PCIeC0_1, PCIeC0_2,
 	    PCIeC1_0, PCIeC1_1, PCIeC1_2,
-	    USB, 0, 0 } },
+	    USB, 0, 0 }, INTC_SMP_BALANCING(INT2DISTCR2) },
 	{ CnINT2MSKR3, CnINT2MSKCR3, 32,
 	  { 0, 0, 0, 0, 0, 0,
 	    I2C0, I2C1,
@@ -792,7 +804,7 @@
 	    HAC0, HAC1,
 	    FLCTL, 0,
 	    HSPI, GPIO0, GPIO1, Thermal,
-	    0, 0, 0, 0, 0, 0, 0, 0 } },
+	    0, 0, 0, 0, 0, 0, 0, 0 }, INTC_SMP_BALANCING(INT2DISTCR3) },
 };
 
 static struct intc_prio_reg prio_registers[] __initdata = {
@@ -910,6 +922,18 @@
 #define INTC_INTMSKCLR2	INTMSKCLR2
 #define INTC_USERIMASK	0xfe411000
 
+#ifdef CONFIG_INTC_BALANCING
+unsigned int irq_lookup(unsigned int irq)
+{
+	return __raw_readl(INTACK) & 1 ? irq : NO_IRQ_IGNORE;
+}
+
+void irq_finish(unsigned int irq)
+{
+	__raw_writel(irq2evt(irq), INTACKCLR);
+}
+#endif
+
 void __init plat_irq_setup(void)
 {
 	/* disable IRQ3-0 + IRQ7-4 */
diff --git a/arch/sh/kernel/irq.c b/arch/sh/kernel/irq.c
index d2d41d0..f6a9319 100644
--- a/arch/sh/kernel/irq.c
+++ b/arch/sh/kernel/irq.c
@@ -113,19 +113,14 @@
 
 static union irq_ctx *hardirq_ctx[NR_CPUS] __read_mostly;
 static union irq_ctx *softirq_ctx[NR_CPUS] __read_mostly;
-#endif
 
-asmlinkage __irq_entry int do_IRQ(unsigned int irq, struct pt_regs *regs)
+static char softirq_stack[NR_CPUS * THREAD_SIZE] __page_aligned_bss;
+static char hardirq_stack[NR_CPUS * THREAD_SIZE] __page_aligned_bss;
+
+static inline void handle_one_irq(unsigned int irq)
 {
-	struct pt_regs *old_regs = set_irq_regs(regs);
-#ifdef CONFIG_IRQSTACKS
 	union irq_ctx *curctx, *irqctx;
-#endif
 
-	irq_enter();
-	irq = irq_demux(irq);
-
-#ifdef CONFIG_IRQSTACKS
 	curctx = (union irq_ctx *)current_thread_info();
 	irqctx = hardirq_ctx[smp_processor_id()];
 
@@ -164,20 +159,9 @@
 			  "r5", "r6", "r7", "r8", "t", "pr"
 		);
 	} else
-#endif
 		generic_handle_irq(irq);
-
-	irq_exit();
-
-	set_irq_regs(old_regs);
-	return 1;
 }
 
-#ifdef CONFIG_IRQSTACKS
-static char softirq_stack[NR_CPUS * THREAD_SIZE] __page_aligned_bss;
-
-static char hardirq_stack[NR_CPUS * THREAD_SIZE] __page_aligned_bss;
-
 /*
  * allocate per-cpu stacks for hardirq and for softirq processing
  */
@@ -257,8 +241,33 @@
 
 	local_irq_restore(flags);
 }
+#else
+static inline void handle_one_irq(unsigned int irq)
+{
+	generic_handle_irq(irq);
+}
 #endif
 
+asmlinkage __irq_entry int do_IRQ(unsigned int irq, struct pt_regs *regs)
+{
+	struct pt_regs *old_regs = set_irq_regs(regs);
+
+	irq_enter();
+
+	irq = irq_demux(irq_lookup(irq));
+
+	if (irq != NO_IRQ_IGNORE) {
+		handle_one_irq(irq);
+		irq_finish(irq);
+	}
+
+	irq_exit();
+
+	set_irq_regs(old_regs);
+
+	return IRQ_HANDLED;
+}
+
 void __init init_IRQ(void)
 {
 	plat_irq_setup();