From 9a70f2dcb24a5aab29386373c86ba035acba4891 Mon Sep 17 00:00:00 2001
From: Axel Gembe <ago@bastart.eu.org>
Date: Sun, 18 May 2008 12:07:21 +0200
Subject: [PATCH] bcm963xx: rewrite irq handling code

This patch adds interrupt handling as on AR7. The old code was very messy and
didn't work too well.

Signed-off-by: Axel Gembe <ago@bastart.eu.org>
---
 arch/mips/bcm963xx/irq.c                  |  308 ++++++++++-------------------
 drivers/serial/bcm63xx_cons.c             |   13 +-
 include/asm-mips/mach-bcm963xx/bcm_intr.h |   18 +--
 3 files changed, 119 insertions(+), 220 deletions(-)

--- a/arch/mips/bcm963xx/irq.c
+++ b/arch/mips/bcm963xx/irq.c
@@ -1,259 +1,159 @@
 /*
-<:copyright-gpl 
- Copyright 2002 Broadcom Corp. All Rights Reserved. 
- 
- This program is free software; you can distribute it and/or modify it 
- under the terms of the GNU General Public License (Version 2) as 
- published by the Free Software Foundation. 
- 
- This program is distributed in the hope it will be useful, but WITHOUT 
- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
- FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License 
- for more details. 
- 
- You should have received a copy of the GNU General Public License along 
- with this program; if not, write to the Free Software Foundation, Inc., 
- 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. 
-:>
-*/
-/*
- * Interrupt control functions for Broadcom 963xx MIPS boards
+ * Copyright (C) 2006,2007 Felix Fietkau <nbd@openwrt.org>
+ * Copyright (C) 2006,2007 Eugene Konev <ejka@openwrt.org>
+ * Copyright (C) 2008 Axel Gembe <ago@bastart.eu.org>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
-#include <asm/atomic.h>
-
-#include <linux/delay.h>
-#include <linux/init.h>
-#include <linux/ioport.h>
-#include <linux/irq.h>
 #include <linux/interrupt.h>
-#include <linux/kernel.h>
-#include <linux/slab.h>
-#include <linux/module.h>
+#include <linux/io.h>
 
-#include <asm/irq.h>
+#include <asm/irq_cpu.h>
 #include <asm/mipsregs.h>
-#include <asm/addrspace.h>
-#include <asm/signal.h>
+
 #include <6348_map_part.h>
 #include <6348_intr.h>
 #include <bcm_map_part.h>
 #include <bcm_intr.h>
 
-static void irq_dispatch_int(void)
-{
-	unsigned int pendingIrqs;
-	static unsigned int irqBit;
-	static unsigned int isrNumber = 31;
-
-	pendingIrqs = PERF->IrqStatus & PERF->IrqMask;
-	if (!pendingIrqs) {
-		return;
-	}
+static int bcm963xx_irq_base;
 
-	while (1) {
-	irqBit <<= 1;
-	isrNumber++;
-	if (isrNumber == 32) {
-		isrNumber = 0;
-		irqBit = 0x1;
-	}
-	if (pendingIrqs & irqBit) {
-			PERF->IrqMask &= ~irqBit; // mask
-			do_IRQ(isrNumber + INTERNAL_ISR_TABLE_OFFSET);
-		break;
-	}
-	}
+void bcm963xx_unmask_irq(unsigned int irq)
+{
+	PERF->IrqMask |= (1 << (irq - bcm963xx_irq_base));
 }
 
-static void irq_dispatch_ext(uint32 irq)
+void bcm963xx_mask_irq(unsigned int irq)
 {
-	if (!(PERF->ExtIrqCfg & (1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_MASK_SHFT)))) {
-	printk("**** Ext IRQ mask. Should not dispatch ****\n");
-	}
-	/* disable and clear interrupt in the controller */
-	PERF->ExtIrqCfg |= (1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_CLEAR_SHFT));
-	PERF->ExtIrqCfg &= ~(1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_MASK_SHFT));
-	do_IRQ(irq);
+	PERF->IrqMask &= ~(1 << (irq - bcm963xx_irq_base));
 }
 
-
-//extern void brcm_timer_interrupt(struct pt_regs *regs);
-
-asmlinkage void plat_irq_dispatch(void)
+void bcm963xx_ack_irq(unsigned int irq)
 {
-	unsigned long cause;
-
-	cause = read_c0_status() & read_c0_cause() & ST0_IM;
-	if (cause & CAUSEF_IP7)
-		do_IRQ(7);
-	else if (cause & CAUSEF_IP2)
-		irq_dispatch_int();
-	else if (cause & CAUSEF_IP3)
-		irq_dispatch_ext(INTERRUPT_ID_EXTERNAL_0);
-	else if (cause & CAUSEF_IP4)
-		irq_dispatch_ext(INTERRUPT_ID_EXTERNAL_1);
-	else if (cause & CAUSEF_IP5)
-		irq_dispatch_ext(INTERRUPT_ID_EXTERNAL_2);
-	else if (cause & CAUSEF_IP6) {
-		irq_dispatch_ext(INTERRUPT_ID_EXTERNAL_3);
-		local_irq_disable();
-	}
+	PERF->IrqStatus &= ~(1 << (irq - bcm963xx_irq_base));
 }
 
-
-void enable_brcm_irq(unsigned int irq)
+void bcm963xx_unmask_ext_irq(unsigned int irq)
 {
-	unsigned long flags;
-
-	local_irq_save(flags);
-	if( irq >= INTERNAL_ISR_TABLE_OFFSET ) {
-	PERF->IrqMask |= (1 << (irq - INTERNAL_ISR_TABLE_OFFSET));
-	}
-	else if (irq >= INTERRUPT_ID_EXTERNAL_0 && irq <= INTERRUPT_ID_EXTERNAL_3) {
-	/* enable and clear interrupt in the controller */
-	PERF->ExtIrqCfg |= (1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_CLEAR_SHFT));
 	PERF->ExtIrqCfg |= (1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_MASK_SHFT));
-	}
-	local_irq_restore(flags);
 }
 
-void disable_brcm_irq(unsigned int irq)
+void bcm963xx_mask_ext_irq(unsigned int irq)
 {
-	unsigned long flags;
-
-	local_irq_save(flags);
-	if( irq >= INTERNAL_ISR_TABLE_OFFSET ) {
-	PERF->IrqMask &= ~(1 << (irq - INTERNAL_ISR_TABLE_OFFSET));
-	}
-	else if (irq >= INTERRUPT_ID_EXTERNAL_0 && irq <= INTERRUPT_ID_EXTERNAL_3) {
-	/* disable interrupt in the controller */
 	PERF->ExtIrqCfg &= ~(1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_MASK_SHFT));
-	}
-	local_irq_restore(flags);
 }
 
-void ack_brcm_irq(unsigned int irq)
+void bcm963xx_ack_ext_irq(unsigned int irq)
 {
-	/* Already done in brcm_irq_dispatch */
+	PERF->ExtIrqCfg |= (1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_CLEAR_SHFT));
 }
 
-unsigned int startup_brcm_irq(unsigned int irq)
+static void bcm963xx_dispatch_ext_irq(unsigned int irq)
 {
-	enable_brcm_irq(irq);
-
-	return 0; /* never anything pending */
+	bcm963xx_ack_ext_irq(irq);
+	bcm963xx_mask_ext_irq(irq);
+	do_IRQ(irq);
 }
 
-unsigned int startup_brcm_none(unsigned int irq)
+static void bcm963xx_cascade(void)
 {
-	return 0;
-}
+	uint32_t pending, bit, irq;
 
-void end_brcm_irq(unsigned int irq)
-{
-	if (!(irq_desc[irq].status & (IRQ_DISABLED|IRQ_INPROGRESS)))
-		enable_brcm_irq(irq);
-}
+	if (!(pending = PERF->IrqStatus & PERF->IrqMask))
+		return;
 
-void end_brcm_none(unsigned int irq)
-{
-}
+	for (irq = 0, bit = 1; irq < 32; irq++, bit <<= 1) {
+		if (pending & bit) {
+			bcm963xx_ack_irq(irq + bcm963xx_irq_base);
+			bcm963xx_mask_irq(irq + bcm963xx_irq_base);
+			do_IRQ(irq + bcm963xx_irq_base);
+			return;
+		}
+	}
+
+	spurious_interrupt();
+}
+
+static struct irq_chip bcm963xx_irq_type = {
+	.name = "bcm963xx",
+	.unmask = bcm963xx_unmask_irq,
+	.mask = bcm963xx_mask_irq,
+	.ack = bcm963xx_ack_irq
+};
 
-static struct hw_interrupt_type brcm_irq_type = {
-	.typename	= "MIPS",
-	.startup	= startup_brcm_irq,
-	.shutdown	= disable_brcm_irq,
-	.enable	= enable_brcm_irq,
-	.disable	= disable_brcm_irq,
-	.ack	= ack_brcm_irq,
-	.end	= end_brcm_irq,
-	.set_affinity = NULL
+static struct irq_chip bcm963xx_ext_irq_type = {
+	.name = "bcm963xx_ext",
+	.unmask = bcm963xx_unmask_ext_irq,
+	.mask = bcm963xx_mask_ext_irq,
+	.ack = bcm963xx_ack_ext_irq,
 };
 
-static struct hw_interrupt_type brcm_irq_no_end_type = {
-	.typename	= "MIPS",
-	.startup	= startup_brcm_none,
-	.shutdown	= disable_brcm_irq,
-	.enable	= enable_brcm_irq,
-	.disable	= disable_brcm_irq,
-	.ack	= ack_brcm_irq,
-	.end	= end_brcm_none,
-	.set_affinity = NULL
+static struct irqaction bcm963xx_cascade_action = {
+	.handler = no_action,
+	.name = "BCM963xx cascade interrupt"
 };
 
-void __init arch_init_irq(void)
+static void __init bcm963xx_irq_init(int base)
 {
 	int i;
 
-	clear_c0_status(ST0_BEV);
-	change_c0_status(ST0_IM, (IE_IRQ0 | IE_IRQ1 | IE_IRQ2 | IE_IRQ3 | IE_IRQ4));
+	bcm963xx_irq_base = base;
 
-	for (i = 0; i < NR_IRQS; i++) {
-		irq_desc[i].status = IRQ_DISABLED;
-		irq_desc[i].action = 0;
-		irq_desc[i].depth = 1;
-		irq_desc[i].chip = &brcm_irq_type;
-	}
+	/* External IRQs */
+	set_irq_chip_and_handler(INTERRUPT_ID_EXTERNAL_0, &bcm963xx_ext_irq_type,
+				 handle_level_irq);
+	set_irq_chip_and_handler(INTERRUPT_ID_EXTERNAL_1, &bcm963xx_ext_irq_type,
+				 handle_level_irq);
+	set_irq_chip_and_handler(INTERRUPT_ID_EXTERNAL_2, &bcm963xx_ext_irq_type,
+				 handle_level_irq);
+	set_irq_chip_and_handler(INTERRUPT_ID_EXTERNAL_3, &bcm963xx_ext_irq_type,
+				 handle_level_irq);
+
+	for (i = 0; i < 32; i++) {
+		set_irq_chip_and_handler(base + i, &bcm963xx_irq_type,
+					 handle_level_irq);
+	}
+
+	setup_irq(2, &bcm963xx_cascade_action);
+	setup_irq(bcm963xx_irq_base, &bcm963xx_cascade_action);
+	set_c0_status(IE_IRQ0);
 }
 
-int request_external_irq(unsigned int irq, 
-	FN_HANDLER handler,
-		unsigned long irqflags, 
-		const char * devname,
-		void *dev_id)
+asmlinkage void plat_irq_dispatch(void)
 {
-	unsigned long flags;
-
-	local_irq_save(flags);
+	unsigned int pending = read_c0_status() & read_c0_cause() & ST0_IM;
 
-	PERF->ExtIrqCfg |= (1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_CLEAR_SHFT));      // Clear
-	PERF->ExtIrqCfg &= ~(1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_MASK_SHFT));      // Mask
-	PERF->ExtIrqCfg &= ~(1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_INSENS_SHFT));    // Edge insesnsitive
-	PERF->ExtIrqCfg |= (1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_LEVEL_SHFT));      // Level triggered
-	PERF->ExtIrqCfg &= ~(1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_SENSE_SHFT));     // Low level
-
-	local_irq_restore(flags);
-
-	return( request_irq(irq, handler, irqflags, devname, dev_id) );
+	if (pending & STATUSF_IP7)		/* cpu timer */
+		do_IRQ(7);
+	else if (pending & STATUSF_IP2)		/* internal interrupt cascade */
+		bcm963xx_cascade();
+	else if (pending & STATUSF_IP3)
+		bcm963xx_dispatch_ext_irq(INTERRUPT_ID_EXTERNAL_0);
+	else if (pending & STATUSF_IP4)
+		bcm963xx_dispatch_ext_irq(INTERRUPT_ID_EXTERNAL_1);
+	else if (pending & STATUSF_IP5)
+		bcm963xx_dispatch_ext_irq(INTERRUPT_ID_EXTERNAL_2);
+	else if (pending & STATUSF_IP6)
+		bcm963xx_dispatch_ext_irq(INTERRUPT_ID_EXTERNAL_3);
+	else
+		spurious_interrupt();
 }
 
-/* VxWorks compatibility function(s). */
-
-unsigned int BcmHalMapInterrupt(FN_HANDLER pfunc, unsigned int param,
-	unsigned int interruptId)
+void __init arch_init_irq(void)
 {
-	int nRet = -1;
-	char *devname;
-
-	devname = kmalloc(16, GFP_KERNEL);
-	if (devname)
-		sprintf( devname, "brcm_%d", interruptId );
-
-	/* Set the IRQ description to not automatically enable the interrupt at
-	 * the end of an ISR.  The driver that handles the interrupt must
-	 * explicitly call BcmHalInterruptEnable or enable_brcm_irq.  This behavior
-	 * is consistent with interrupt handling on VxWorks.
-	 */
-	irq_desc[interruptId].chip = &brcm_irq_no_end_type;
-
-	if( interruptId >= INTERNAL_ISR_TABLE_OFFSET )
-	{	
-		printk("BcmHalMapInterrupt : internal IRQ\n");
-		nRet = request_irq( interruptId, pfunc, IRQF_DISABLED, devname, (void *) param );
-	}
-	else if (interruptId >= INTERRUPT_ID_EXTERNAL_0 && interruptId <= INTERRUPT_ID_EXTERNAL_3)
-	{
-		printk("BcmHalMapInterrupt : external IRQ\n");
-		nRet = request_external_irq( interruptId, pfunc, IRQF_DISABLED, devname, (void *) param );
-	}
-
-	return( nRet );
+	mips_cpu_irq_init();
+	bcm963xx_irq_init(INTERNAL_ISR_TABLE_OFFSET);
 }
-
-
-EXPORT_SYMBOL(enable_brcm_irq);
-EXPORT_SYMBOL(disable_brcm_irq);
-EXPORT_SYMBOL(request_external_irq);
-EXPORT_SYMBOL(BcmHalMapInterrupt);
-
--- a/drivers/serial/bcm63xx_cons.c
+++ b/drivers/serial/bcm63xx_cons.c
@@ -267,7 +267,7 @@
 	}
 
 	// Clear the interrupt
-	enable_brcm_irq(INTERRUPT_ID_UART);
+//	bcm963xx_unmask_irq(INTERRUPT_ID_UART);
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
 	return IRQ_HANDLED;
 #endif
@@ -880,7 +880,7 @@
 	info->count++;
 	tty->driver_data = info;
 	info->tty = tty;
-	enable_brcm_irq(INTERRUPT_ID_UART);
+	bcm963xx_unmask_irq(INTERRUPT_ID_UART);
 
 	// Start up serial port
 	retval = startup(info);
@@ -927,7 +927,7 @@
 -------------------------------------------------------------------------- */
 static int __init bcm63xx_serialinit(void)
 {
-	int i, flags;
+	int i, flags, res;
 	struct bcm_serial *info;
 
 	// Print the driver version information
@@ -981,7 +981,12 @@
 		 */
 		if (!info->port)
 			return 0;
-		BcmHalMapInterrupt(bcm_interrupt, 0, INTERRUPT_ID_UART);
+
+		res = request_irq(INTERRUPT_ID_UART, bcm_interrupt, 0, "bcm-uart", NULL);
+		if (res) {
+			spin_unlock_irqrestore(&bcm963xx_serial_lock, flags);
+			return res;
+		}
 	}
 
 	/* order matters here... the trick is that flags
--- a/include/asm-mips/mach-bcm963xx/bcm_intr.h
+++ b/include/asm-mips/mach-bcm963xx/bcm_intr.h
@@ -39,18 +39,12 @@
 typedef int (*FN_HANDLER) (int, void *);
 
 /* prototypes */
-extern void enable_brcm_irq(unsigned int irq);
-extern void disable_brcm_irq(unsigned int irq);
-extern int request_external_irq(unsigned int irq,
-    FN_HANDLER handler, unsigned long irqflags, 
-    const char * devname, void *dev_id);
-extern unsigned int BcmHalMapInterrupt(FN_HANDLER isr, unsigned int param,
-    unsigned int interruptId);
-extern void dump_intr_regs(void);
-
-/* compatibility definitions */
-#define BcmHalInterruptEnable(irq)      enable_brcm_irq( irq )
-#define BcmHalInterruptDisable(irq)     disable_brcm_irq( irq )
+extern void bcm963xx_unmask_irq(unsigned int irq);
+extern void bcm963xx_mask_irq(unsigned int irq);
+extern void bcm963xx_ack_irq(unsigned int irq);
+extern void bcm963xx_unmask_ext_irq(unsigned int irq);
+extern void bcm963xx_mask_ext_irq(unsigned int irq);
+extern void bcm963xx_ack_ext_irq(unsigned int irq);
 
 #ifdef __cplusplus
     }
