/*
 *  arch/ppc/kernel/irq.c
 *
 *  TiVo Customer Device version
 *    Copyright (C) 1999 TiVo Inc. (www.teleworld.com)
 *
 *  Power Macintosh version
 *    Copyright (C) 1996 Paul Mackerras (paulus@cs.anu.edu.au)
 *
 *  Derived from the PowerPC/PReP version
 *    Copyright (C) 1995-1996 Gary Thomas (gdt@linuxppc.org)
 *
 *  Derived from arch/i386/kernel/irq.c
 *    Copyright (C) 1992 Linus Torvalds
 *
 * This file contains the code used by various IRQ handling routines:
 * asking for different IRQ's should be done through these routines
 * instead of just grabbing them. Thus setups with different IRQ numbers
 * shouldn't result in any weird surprises, and installing new handlers
 * should be easier.
 */

/*
 * IRQ's are in fact implemented a bit like signal handlers for the kernel.
 * Naturally it's not a 1:1 relation, but there are similarities.
 */

/*
 * This modules is __not__ SMP capable.
 */

#include <linux/config.h>
#include <linux/ptrace.h>
#include <linux/errno.h>
#include <linux/kernel_stat.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/timex.h>
#include <linux/init.h>

#include <asm/system.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/bitops.h>
#include <asm/ppcops.h>

#undef	REPORT_IRQ

unsigned int ms_base_addr = 0;
unsigned int ppc_local_bh_count[NR_CPUS];
unsigned int ppc_local_irq_count[NR_CPUS];

void disable_irq(unsigned int irq_nr)
{
	int s;
	save_and_cli(s);

	if (irq_nr < 32) {
		int ov = mfdcr(SPRN_EXIER);
		ov &= ~(1 << (31 - irq_nr));
		mtdcr(SPRN_EXIER, ov);
	}
	else if (irq_nr < 64) {
		unsigned int enabled = inl(MEDIASWITCH_IER);
		enabled &= ~(1 << (63 - irq_nr));
		outl(enabled, MEDIASWITCH_IER);
	}
	else if (irq_nr < 96) {
		unsigned int enabled = inl(MEDIASWITCH_EIER);
		enabled &= ~(1 << (95 - irq_nr));
		outl(enabled, MEDIASWITCH_EIER);
	}
	else if ((irq_nr < 128) && ms2probe()) {
		unsigned int enabled = inl(MEDIASWITCH2_IER);
		enabled &= ~(1 << (127 - irq_nr));
		outl(enabled, MEDIASWITCH2_IER);
	} else if ((irq_nr < 160) && ms2probe()) {
		unsigned int enabled = inl(MEDIASWITCH2_EIER);
		enabled &= ~(1 << (159 - irq_nr));
		outl(enabled, MEDIASWITCH2_EIER);
	}
	else
		printk("disable requested for invalid IRQ %d\n", irq_nr);
#ifdef REPORT_IRQ
	printk("IRQ %d disabled!\n", irq_nr);
#endif
	restore_flags(s);
}

void disable_irq_nosync(unsigned int irq_nr)
{
	// Currently, we don't support SMP on 4xx anyway
	disable_irq(irq_nr);
}

void enable_irq(unsigned int irq_nr)
{
    int s;
	save_and_cli(s);

	if (irq_nr < 32) {
		int ov = mfdcr(SPRN_EXIER);
		ov |= (1 << (31 - irq_nr));
		mtdcr(SPRN_EXIER, ov);
	}
	else if (irq_nr < 64) {
		unsigned int enabled = inl(MEDIASWITCH_IER);
		enabled |= (1 << (63 - irq_nr));
		outl(enabled, MEDIASWITCH_IER);
	}
	else if (irq_nr < 96) {
		unsigned int enabled = inl(MEDIASWITCH_EIER);
		enabled |= (1 << (95 - irq_nr));
		outl(enabled, MEDIASWITCH_EIER);
	}
	else if ((irq_nr < 128) && ms2probe()) {
		unsigned int enabled = inl(MEDIASWITCH2_IER);
		enabled |= (1 << (127 - irq_nr));
		outl(enabled, MEDIASWITCH2_IER);
	} else if ((irq_nr < 160) && ms2probe()) {
		unsigned int enabled = inl(MEDIASWITCH2_EIER);
		enabled |= (1 << (159 - irq_nr));
		outl(enabled, MEDIASWITCH2_EIER);
	}
	else
		printk("enable requested for invalid IRQ %d\n", irq_nr);
#ifdef REPORT_IRQ
	printk("IRQ %d enabled!\n", irq_nr);
#endif
	restore_flags(s);
}

/*
 * Irq handlers.
 */
struct irq_action {
	void (*handler)(int, void *dev, struct pt_regs *);
	unsigned long flags;
	unsigned long mask;
	const char *name;
	void *dev_id;
};

static struct irq_action irq_action[NR_IRQS];

int get_irq_list(char *buf)
{
	int i, len = 0;
	struct irq_action * action = irq_action;

	for (i = 0; i < NR_IRQS; i++, action++) {
		if (!action->handler)
			continue;
		len += sprintf(buf+len, "%2d: %8d %c %s\n",
			i, kstat.irqs[0][i],
			(action->flags & SA_INTERRUPT) ? '+' : ' ',
			action->name);
	}
	return len;
}

asmlinkage void handle_IRQ(struct pt_regs *regs)
{
	int irq;
	unsigned int oexier;
	unsigned long bits;
	unsigned long mask = (1 << 31);
	struct irq_action *action;
#ifdef CONFIG_XMON
	int cbuf;
	extern int intr_enter(int);
	extern void intr_leave(int);
#endif

	ppc_local_irq_count[0]++;

	bits = mfdcr(SPRN_EXISR);
	oexier = mfdcr(SPRN_EXIER);

	/*
	 * We need to turn interrupts back on, so that the
	 * clock will run; turn off all interrupt sources
	 * and turn on the MSR[EE] bit. Yucko.
	 *
	 * On the bright side, this allows critical exceptions
	 * to happen.
	 */
     	mtdcr(SPRN_EXIER, 0x80000000);
     	mtmsr(mfmsr() | MSR_EE);

	bits &= oexier;
	for (irq = 0; irq < 32; irq++, mask >>= 1) {
		if (bits & mask) {
#ifdef CONFIG_XMON
			cbuf = intr_enter(irq);
#endif
			action = irq_action + irq;
			kstat.irqs[0][irq]++;
			if (!action->handler) {
			    printk("Unexpected interrupt #%d, PC: %lx\n", irq, regs->nip);
			    disable_irq(irq);
			}
			else {
				action->handler(irq, action->dev_id, regs);
				if (!(mfmsr() & MSR_EE)) {
					mtmsr(mfmsr() | MSR_EE);
				}
			}
			mtdcr(SPRN_EXISR, (1 << (31 - irq)));
#ifdef CONFIG_XMON
			intr_leave(cbuf);
#endif
		}
	}

	/* restore interrupt state */
	mtmsr(mfmsr() & ~MSR_EE);
	mtdcr(SPRN_EXIER, oexier);

	ppc_local_irq_count[0]--;
}

unsigned long irq_ms_g;
unsigned long ms_isr_cleared_g;

void handle_MSE(int irq, void *dev, struct pt_regs *regs) {
	unsigned long bits;
	unsigned long enabled;
	unsigned long mask = (1 << 31);
	struct irq_action *action;
#ifdef CONFIG_XMON
	int cbuf;
	extern int intr_enter(int);
	extern void intr_leave(int);
#endif

	bits = inl(MEDIASWITCH_EISR);
	bits &= 0xfff;
	if (!bits)
	    return;

	enabled = inl(MEDIASWITCH_EIER);
	bits &= enabled;
	if (!bits)
	    return;

	for (irq = 0; irq < 32; irq++, mask >>= 1) {
		if (bits & mask) {
			int rirq = irq + MEDIASWITCH_EIBASE;
#ifdef CONFIG_XMON
			cbuf = intr_enter(rirq);
#endif
			action = irq_action + rirq;
			kstat.irqs[0][rirq]++;
			if (!action->handler) {
			    printk("Unexpected Mediaswitch error interrupt #%d, PC: %lx\n",
				    rirq, regs->nip);
			    disable_irq(rirq);
			}
			else
			    action->handler(rirq, action->dev_id, regs);
			outl((1 << (31 - irq)), MEDIASWITCH_EISR);
#ifdef CONFIG_XMON
			intr_leave(cbuf);
#endif
		}
	}
}

void handle_MS2E(int irq, void *dev, struct pt_regs *regs) {
	unsigned long bits;
	unsigned long enabled;
	unsigned long mask = (1 << 31);
	struct irq_action *action;
#ifdef CONFIG_XMON
	int cbuf;
	extern int intr_enter(int);
	extern void intr_leave(int);
#endif

	bits = inl(MEDIASWITCH2_EISR);
	bits &= 0xfff;
	if (!bits)
	    return;

	enabled = inl(MEDIASWITCH2_EIER);
	bits &= enabled;
	if (!bits)
	    return;

	for (irq = 0; irq < 32; irq++, mask >>= 1) {
		if (bits & mask) {
			int rirq = irq + MEDIASWITCH2_EIBASE;
#ifdef CONFIG_XMON
			cbuf = intr_enter(rirq);
#endif
			action = irq_action + rirq;
			kstat.irqs[0][rirq]++;
			if (!action->handler) {
			    printk("Unexpected Mediaswitch2 error interrupt #%d, PC: %lx\n",
				    rirq, regs->nip);
			    disable_irq(rirq);
			}
			else
			    action->handler(rirq, action->dev_id, regs);
			outl((1 << (31 - irq)), MEDIASWITCH2_EISR);
#ifdef CONFIG_XMON
			intr_leave(cbuf);
#endif
		}
	}
}

void handle_MS(int irq, void *dev, struct pt_regs *regs) {
	unsigned long bits;
	unsigned long enabled;
	unsigned long mask = (1 << 31);
	struct irq_action *action;
#ifdef CONFIG_XMON
	int cbuf;
	extern int intr_enter(int);
	extern void intr_leave(int);
#endif


	if (ms2probe()) {
	  /* 
	   * If MS2_MS1 is set, first process MS2 events then MS1.
	   * If MS2_MS1 is not set, only process MS2 events.
	   */
	  handle_MS2E(0, dev, regs);
	  bits = inl(MEDIASWITCH2_ISR);
	  enabled = inl(MEDIASWITCH2_IER);
//printk("MediaSwitch2(0x%x) ISR %x IMR %x\n",MEDIASWITCH2_ISR,bits,enabled);
	  bits &= (enabled);
	  /*	
	  printk("MediaSwitch2 ISR %x IMR %x\n",bits,enabled);
	  */	
	  /* NOTE: skip 1 since it is MS2_MS1... handled later. */
	  mask = (1 << 30);
	  for (irq_ms_g = 1; irq_ms_g < 32; irq_ms_g++, mask >>= 1) {
	    int rirq = irq_ms_g + MEDIASWITCH2_IBASE;
	    ms_isr_cleared_g = 0;		
	    if (bits & mask) {
//printk("MediaSwitch2(0x%x) ISR %x IMR %x\n",(int)MEDIASWITCH2_ISR,bits,enabled);
#ifdef CONFIG_XMON
            cbuf = intr_enter(rirq);
#endif
	    action = irq_action + rirq;
	    kstat.irqs[0][rirq]++;
	    if (!action->handler) {
	      printk("Unexpected Mediaswitch2 interrupt #%d, PC: %lx, ISR: 0x%x, IER=0x%x\n",
		     rirq, regs->nip, inl(MEDIASWITCH2_ISR), inl(MEDIASWITCH2_IER));
	      disable_irq(rirq);
	    }
	    else {
//printk("MS2 handler=0x%x\n", (int)action->handler);
	      action->handler(rirq, action->dev_id, regs);
//printk("MS2 handler returns\n");
	      if (!ms_isr_cleared_g) {
//printk("MS2 handler reutnrs, cleared=0x%x, output 0x%x\n", ms_isr_cleared_g, (1 << (31 - irq_ms_g)));
		outl((1 << (31 - irq_ms_g)), MEDIASWITCH2_ISR);
	      }
	    }
#ifdef CONFIG_XMON
            intr_leave(cbuf);
#endif
	  }
	}
	if (!(inl(MEDIASWITCH2_ISR)&MEDIASWITCH2_MS1INTR)) {
//printk("No MS1 events\n");
		/* printk("No MS1 events\n"); */
		return;
	}
	}
	/* handle error interrupts first */
	handle_MSE(0, dev, regs);

	bits = inl(MEDIASWITCH_ISR);
	enabled = inl(MEDIASWITCH_IER);
//printk("MediaSwitch ISR %x IMR %x\n",bits,enabled);
	bits &= enabled;
	mask = (1 << 31);
	for (irq_ms_g = 0; irq_ms_g < 32; irq_ms_g++, mask >>= 1) {
	  int rirq = irq_ms_g + MEDIASWITCH_IBASE;
	  ms_isr_cleared_g = 0;		
	  if (bits & mask) {
#ifdef CONFIG_XMON
          cbuf = intr_enter(rirq);
#endif
	    action = irq_action + rirq;
	    kstat.irqs[0][rirq]++;
//printk("MS, rirq=%d, handler=0x%x\n", rirq, action->handler);
	    if (!action->handler) {
	      printk("Unexpected Mediaswitch interrupt #%d, PC: %lx\n",
		     rirq, regs->nip);
	      disable_irq(rirq);
	    }
	    else {
	      action->handler(rirq, action->dev_id, regs);
	      if (!ms_isr_cleared_g) {
		outl((1 << (31 - irq_ms_g)), MEDIASWITCH_ISR);
	      }
	    }
#ifdef CONFIG_XMON
        intr_leave(cbuf);
#endif
	  }
	}
	/*	
	printk("MediaSwitch ISR END\n");
	*/	
}

int request_irq(unsigned int irq, void (*handler)(int, void *, struct pt_regs *),
	unsigned long irqflags, const char * devname, void *dev_id)
{
	struct irq_action * action;
	unsigned long flags;

	if (irq >= NR_IRQS)
		return -EINVAL;
	action = irq + irq_action;
	if (action->handler)
		return -EBUSY;
	if (!handler)
		return -EINVAL;
	save_and_cli(flags);
	action->handler = handler;
	action->flags = irqflags;
	action->mask = 0;
	action->name = devname;
	action->dev_id = dev_id;
	enable_irq(irq);
	restore_flags(flags);
#ifdef REPORT_IRQ
	printk("IRQ %d registered handler at 0x%p\n", irq, handler);
#endif
	return 0;
}

void free_irq(unsigned int irq, void *dev_id)
{
	struct irq_action * action = irq + irq_action;
	unsigned long flags;

	if (irq >= NR_IRQS) {
		printk("Trying to free non-existent IRQ%d\n",irq);
		return;
	}
	if (!action->handler) {
		printk("Trying to free IRQ%d, already free!\n",irq);
		return;
	}
	disable_irq(irq);
	save_and_cli(flags);
	action->handler = NULL;
	action->flags = 0;
	action->mask = 0;
	action->name = NULL;
	action->dev_id = NULL;
	restore_flags(flags);
#ifdef REPORT_IRQ
	printk("IRQ %d deleted handler\n", irq);
#endif
}

unsigned long probe_irq_on (void)
{
	panic("probe_irq_on");
	return 0;
}

int probe_irq_off (unsigned long irqs)
{
	panic("probe_irq_off");
	return 0;
}

void __init init_IRQ(void)
{
	/* ignore all interrupts until expliclty requested */
	mtdcr(SPRN_EXIER, 0);

	/* ignore all Mediaswitch interrupts */
	outl(0, MEDIASWITCH_IER);
	request_irq(IRQ_MEDIASWITCH, handle_MS, 0, "msw0", 0);

	/* ignore all Mediaswitch error interrupts */
	outl(0, MEDIASWITCH_EIER);
	if (ms2probe()) {
		outl(0, MEDIASWITCH2_IER);
		outl(0, MEDIASWITCH2_EIER);
	}
}
