/*
 * ints.c -- 680x0 Linux general interrupt handling code
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file README.legal in the main directory of this archive
 * for more details.
 */

#include <linux/types.h>
#include <linux/config.h>
#include <linux/sched.h>
#include <linux/kernel_stat.h>
#include <linux/errno.h>

#include <asm/system.h>
#include <asm/segment.h>
#include <asm/irq.h>
#include <asm/page.h>

/* list is accessed 0-6 for IRQs 1-7 */
static isr_node_t *isr_list[7];

/* The number of spurious interrupts */
volatile unsigned long num_spurious;

#ifdef MASTER_STACK
unsigned long interrupt_stack[PAGE_SIZE/sizeof(long)];
#endif

/*
 * void init_IRQ(void)
 *
 * Parameters:	None
 *
 * Returns:	Nothing
 *
 * This function should be called during kernel startup to initialize
 * the IRQ handling routines.
 */

void init_IRQ(void)
{
#ifdef MASTER_STACK
    /* Setup interrupt stack pointer */
    asm ("movec %0,%/isp"
	 : : "r" (interrupt_stack + sizeof (interrupt_stack) / sizeof (long)));
#endif
	
    mach_init_INTS ();
}

void insert_isr (isr_node_t **listp, isr_node_t *node)
{
    unsigned long spl;
    isr_node_t *cur;

    save_flags(spl);
    cli();

    cur = *listp;

    while (cur && cur->pri <= node->pri)
    {
	listp = &cur->next;
	cur = cur->next;
    }

    node->next = cur;
    *listp = node;

    restore_flags(spl);
}

void delete_isr (isr_node_t **listp, isrfunc isr)
{
    unsigned long flags;
    isr_node_t *np;

    save_flags(flags);
    cli();
    for (np = *listp; np; listp = &np->next, np = *listp) {
	if (np->isr == isr) {
	    *listp = np->next;
	    /* Mark it as free. */
	    np->isr = NULL;
	    restore_flags(flags);
	    return;
	}
    }
    restore_flags(flags);
    printk ("delete_isr: isr %p not found on list!\n", isr);
}

#define NUM_ISR_NODES 100
static isr_node_t nodes[NUM_ISR_NODES];

isr_node_t *new_isr_node(void)
{
    isr_node_t *np;

    for (np = nodes; np < &nodes[NUM_ISR_NODES]; np++)
	if (np->isr == NULL)
	    return np;

    printk ("new_isr_node: out of nodes");
    return NULL;
}

int add_isr (unsigned long source, isrfunc isr, int pri, void *data,
	     char *name)
{
    isr_node_t *p;

    if (source & IRQ_MACHSPEC)
    {
	return mach_add_isr (source, isr, pri, data, name);
    }

    if (source < IRQ1 || source > IRQ7)
	panic ("add_isr: Incorrect IRQ source %ld from %s\n",source,name);

    p = new_isr_node();
    if (p == NULL)
	return 0;
    p->isr = isr;
    p->pri = pri;
    p->data = data;
    p->name = name;
    p->next = NULL;

    insert_isr (&isr_list[source-1], p);

    return 1;
}

int remove_isr (unsigned long source, isrfunc isr)
{
    if (source & IRQ_MACHSPEC)
	return mach_remove_isr (source, isr);

    if (source < IRQ1 || source > IRQ7) {
	printk ("remove_isr: Incorrect IRQ source %ld\n", source);
	return 0;
    }

    delete_isr (&isr_list[source - 1], isr);
    return 1;
}

void call_isr_list(int irq, isr_node_t *p, struct intframe *fp)
{
    while (p) {
	p->isr (irq, fp, p->data);
	p = p->next;
    }
}

asmlinkage void process_int(int level, struct intframe *fp)
{
	kstat.interrupts[level]++;  	
	call_isr_list (level, isr_list[level-1], fp);
}

int request_irq(unsigned int irq, void (*handler)(int, struct pt_regs *),
	unsigned long flags, const char * devname)
{
	if (irq == 0) { /* TIMER_IRQ */
		mach_sched_init((isrfunc)handler);
		return 0;
	}
		
	return -EINVAL;
}

void free_irq(unsigned int irq)
{
}

void enable_irq(unsigned int irq_nr)
{
	if ((irq_nr & IRQ_MACHSPEC) && mach_enable_irq)
		mach_enable_irq( irq_nr );
}

void disable_irq(unsigned int irq_nr)
{
	if ((irq_nr & IRQ_MACHSPEC) && mach_disable_irq)
		mach_disable_irq( irq_nr );
}

int get_irq_list( char *buf )
{
    int i, len = 0;
    isr_node_t *p;
    
    /* autovector interrupts */
    for( i = IRQ1; i <= IRQ7; ++i ) {
	if (!isr_list[i-1])
	    continue;
	len += sprintf( buf+len, "auto %2d: %8d ", i, kstat.interrupts[i] );
	for( p = isr_list[i-1]; p; p = p->next ) {
	    len += sprintf( buf+len, "%s\n", p->name );
	    if (p->next)
		len += sprintf( buf+len, "                  " );
	}
    }

    len = mach_get_irq_list( buf, len );
    return( len );
}
