/*
** ints.c -- Amiga Linux interrupt handling code
**
** Copyright 1992 by Greg Harp
**
** 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.
**
** Created 10/2/92 by Greg Harp
** Last Modified 10/19/92 by Greg Harp
** Last Modified 02/11/93 by Hamish Macdonald
*/

#include <linux/types.h>
#include <machine/chipregs.h>
#include <machine/interrupt.h>

/*
** The below array is a temporary measure until there some mm code so
** that I can dynamically allocate memory for the structures.  It does
** not limit the function of the routines in any way except that there
** is a limit of 50 ISR nodes (changeable by modifying the constant).
** The functions alloc_ISR_node() and free_ISR_node() will also go away.
*/

#define NUM_STRUCTS 50

static struct ISR_node
    ISR_node_mem[NUM_STRUCTS];

int
    alloc_ISR_node(struct ISR_node **);

void
    free_ISR_node(struct ISR_node *);

static struct ISR_node
    *ISR_list[NUM_IRQS];	/* list is accessed 0-6 for IRQs 1-7 */

static struct ISR_mask
    all_mask;			/* all requested int bits */

static short intreqr_masks[] = {
    0, IRQ1_MASK, IRQ2_MASK, IRQ3_MASK,
    IRQ4_MASK, IRQ5_MASK, IRQ6_MASK, IRQ7_MASK
    };


int
    base_irq1_isr(struct ISR_mask *mask, ushort sr),
    base_irq2_isr(struct ISR_mask *mask, ushort sr),
    base_irq3_isr(struct ISR_mask *mask, ushort sr),
    base_irq4_isr(struct ISR_mask *mask, ushort sr),
    base_irq5_isr(struct ISR_mask *mask, ushort sr),
    base_irq6_isr(struct ISR_mask *mask, ushort sr),
    base_irq7_isr(struct ISR_mask *mask, ushort sr);

/*
** void set_PPL (int level)
**
** Parameters: The process priority level to set
**
** Returns:    Nothing
*/

void set_PPL (int level)
{
    __asm__ __volatile__ ("movew sr,d0\n\t"
			  "andiw #0xf8ff,d0\n\t"
			  "orw %0,d0\n\t"
			  "movew d0,sr" :: "g" ((level & 7) << 8) : "d0");
}

/*
** int get_PPL (void)
**
** Parameters: None
**
** Returns:    The current process priority level
*/

int get_PPL (void)
{
    int level;

    __asm__ __volatile__ ("movew sr,%0" : "=g" (level));

    return ((level >> 8) & 7);
}


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

void
    init_INTS(void)
    {
    int
	i;
    struct ISR_mask
	tmp_mask;		    /* temporary mask structure */

    /* initialize the storage - this will go away */
    for (i=0; i<NUM_STRUCTS; i++)
	ISR_node_mem[i].isr = (isrfunc)NULL;

    /* initialize the ISR list pointers */
    for (i=0; i<NUM_IRQS; i++)
	ISR_list[i] = (struct ISR_node *)NULL;

    /* create the default mask */
    tmp_mask.intreqr_mask = (ushort)0;
    tmp_mask.ciaa_mask = 0x0;
    tmp_mask.ciab_mask = 0x0;
    tmp_mask.always = 1;

    /* install the base handlers for all 7 IRQs */
    install_ISR(1, base_irq1_isr, -128, &tmp_mask);
    install_ISR(2, base_irq2_isr, -128, &tmp_mask);
    install_ISR(3, base_irq3_isr, -128, &tmp_mask);
    install_ISR(4, base_irq4_isr, -128, &tmp_mask);
    install_ISR(5, base_irq5_isr, -128, &tmp_mask);
    install_ISR(6, base_irq6_isr, -128, &tmp_mask);
    install_ISR(7, base_irq7_isr, -128, &tmp_mask);

    /* initialize mask containing requested bits */
    all_mask.intreqr_mask = (ushort)0;
    all_mask.ciaa_mask = 0x0;
    all_mask.ciab_mask = 0x0;
    all_mask.always = 1;

    /* set the PPL to accept all interrrupts, but only enable master int. */
    *intena = 0x7fff;
    *intena = 0xc000;
    *ciaa_icr = 0x7f;
    *ciab_icr = 0x7f;
    set_PPL(0);
    }

/*
** void process_int(int irq, ushort sr)
**
** Parameters:	irq - the interrupt level to process
**		sr  - the status register before the interrupt occurred
**
** Returns:	Nothing
**
** This function is meant to be called by the interrupt handlers
** themselves.	The handlers pass the IRQ number and previous status register.
*/

void
    process_int(int irq, ushort sr)
    {
    struct ISR_node
	*cur;			/* pointer to current ISR_node */

    int
	done = 0;

    struct ISR_mask
        cur_mask;		/* current contents of registers */

    /* check the IRQ range */
    if ((irq < 1) || (irq > 7))
	return;

    /* get the asserted interrupts for this level */
    cur_mask.intreqr_mask = *intreqr & intreqr_masks[irq];

    /* only read ciaa icr if we have a PORTS interrupt */
    if (cur_mask.intreqr_mask & IF_PORTS)
	cur_mask.ciaa_mask = *ciaa_icr & 0x7f;
    else
	cur_mask.ciaa_mask = 0;

    /* only read ciaa b icr if we have an EXTER interrupt */
    if (cur_mask.intreqr_mask & IF_EXTER)
	cur_mask.ciab_mask = *ciab_icr & 0x7f;
    else
	cur_mask.ciab_mask = 0;

    /* traverse the list for this IRQ */
    for (cur = ISR_list[irq-1];
	 cur && !done;
	 cur = cur->next)
	/* if the conditions are right */
	if (cur->mask.always ||
	    (cur_mask.intreqr_mask & cur->mask.intreqr_mask) ||
	    (cur_mask.ciaa_mask & cur->mask.ciaa_mask) ||
	    (cur_mask.ciab_mask & cur->mask.ciab_mask))
	    /* call the ISR */
	    done = (cur->isr(&cur_mask, sr)) ? 0 : 1;

    /* if done was set (i.e. we didn't complete the list),
       find the last entry (our base entry) and call it */
    if (done && cur)
	{
	/* find the last entry */
	while (cur->next)
	    cur = cur->next;

	/* call it */
	cur->isr(&cur_mask, sr);
	}
    }

/*
** int install_ISR(int irq, isrfunc isr, signed char pri,
**		   struct ISR_mask *mask);
**
** Parameters:	irq - IRQ number (0 to 7)
**		isr - pointer to the ISR function
**		pri - priority (-128 to 127)
**		mask - pointer to the mask structure
**
** Returns:	0 - success
**		1 - IRQ out of range
**		2 - ISR function already in list
**		3 - Allocation of new ISR_node failed
*/

int
    install_ISR(int irq, isrfunc isr, signed char pri, struct ISR_mask *mask)
    {
    struct ISR_node
	*cur,
	*new;

    int
	PPL_tmp;

    /* check the IRQ */
    if ((irq < 1) || (irq > 7))
	/* return "IRQ out of range" */
	return(1);

    /* see if the ISR is already in the list */
    for (cur = ISR_list[irq-1];
	 cur != (struct ISR_node *)NULL;
	 cur = cur->next)
	/* if this ISR is already installed */
	if (cur->isr == isr)
	    /* give the proper error code */
	    return(2);

    /* allocate a new node for this ISR */
    if (alloc_ISR_node(&new))
	/* if failed, give the proper error code */
	return(3);

    /* insert the info into the new node (would use memcpy if I had it) */
    new->isr = isr;
    new->pri = pri;
    new->mask.intreqr_mask = mask->intreqr_mask;
    new->mask.ciaa_mask = mask->ciaa_mask;
    new->mask.ciab_mask = mask->ciab_mask;
    new->mask.always = mask->always;
    new->next = (struct ISR_node *)NULL;

    /* put the ISR in the list according to priority */
    cur = ISR_list[irq-1];

    /* see if there are any entries */
    if (!cur)
	{
	/* this is the first entry for this IRQ */
	ISR_list[irq-1] = new;
	}

    /* see if the new ISR's pri is >= than the first entry */
    else if (pri >= cur->pri)
	{
	/* disable interrupts of this level and lower */
	PPL_tmp = get_PPL();
	if (PPL_tmp < irq)
	    set_PPL(irq);

	/* put this ISR first in the list */
	new->next = cur;
	ISR_list[irq-1] = new;

	/* restore the PPL */
	set_PPL(PPL_tmp);
	}

    /* find the appropriate place down the list for this ISR */
    else
	{
	/* traverse the list until the end or we find a lower pri */
	while (cur->next && (pri < cur->next->pri))
	    cur = cur->next;

	/* disable interrupts of this level and lower */
	PPL_tmp = get_PPL();
	if (PPL_tmp < irq)
	    set_PPL(irq);

	/* insert the new ISR node */
	new->next = cur->next;
	cur->next = new;

	/* restore the PPL */
	set_PPL(PPL_tmp);
	}

    /* remember the requested interrupts */
    all_mask.intreqr_mask |= mask->intreqr_mask;
    all_mask.ciaa_mask |= mask->ciaa_mask;
    all_mask.ciab_mask |= mask->ciab_mask;

    /* enable the requested interrupts */
    *intena = 0xc000 | all_mask.intreqr_mask;
    *ciaa_icr = 0x80 | all_mask.ciaa_mask;
    *ciab_icr = 0x80 | all_mask.ciab_mask;

    /* return success */
    return(0);
    }

/*
** int get_ISR_pri(int irq, isrfunc isr, signed char *pri);
**
** Parameters:	irq - IRQ number (0 to 7)
**		isr - pointer to the ISR function
**		pri - pointer to char (current pri returned here)
**
** Returns:	0 - success
**		1 - IRQ out of range
**		2 - ISR function not found in list
*/

int
    get_ISR_pri(int irq, isrfunc isr, signed char *pri)
    {
    struct ISR_node
	*cur;

    /* check the IRQ */
    if ((irq < 1) || (irq > 7))
	/* return "IRQ out of range" */
	return(1);

    /* see if the ISR is in the list */
    for (cur = ISR_list[irq-1];
	 cur != (struct ISR_node *)NULL;
	 cur = cur->next)
	/* if this ISR is installed */
	if (cur->isr == isr)
	    {
	    /* we found it, so copy the priority */
	    *pri = cur->pri;

	    /* return success */
	    return(0);
	    }

    /* return ISR not found */
    return(2);
    }

/*
** int get_ISR_mask(int irq, isrfunc isr, struct ISR_mask *mask);
**
** Parameters:	irq - IRQ number (0 to 7)
**		isr - pointer the ISR function
**		mask - pointer to a mask structure (returned here)
**
** Returns:	0 - success
**		1 - IRQ out of range
**		2 - ISR function not found in list
*/

int
    get_ISR_mask(int irq, isrfunc isr, struct ISR_mask *mask)
    {
    struct ISR_node
	*cur;

    /* check the IRQ */
    if ((irq < 1) || (irq > 7))
	/* return "IRQ out of range" */
	return(1);

    /* see if the ISR is in the list */
    for (cur = ISR_list[irq-1];
	 cur != (struct ISR_node *)NULL;
	 cur = cur->next)
	/* if this ISR is installed */
	if (cur->isr == isr)
	    {
	    /* we found it, so copy the mask */
	    mask->intreqr_mask = cur->mask.intreqr_mask;
	    mask->ciaa_mask = cur->mask.ciaa_mask;
	    mask->ciab_mask = cur->mask.ciab_mask;
	    mask->always = cur->mask.always;

	    /* return success */
	    return(0);
	    }

    /* return ISR not found */
    return(2);
    }

/*
** int set_ISR_pri(int irq, isrfunc isr, signed char pri);
**
** Parameters:	irq - IRQ number (0 to 7)
**		isr - pointer to the ISR function
**		pri - new priority (-128 to 127)
**
** Returns:	0 - success
**		1 - IRQ out of range
**		2 - ISR function not found in list
*/

int
    set_ISR_pri(int irq, isrfunc isr, signed char pri)
    {
    struct ISR_node
	*cur;

    /* check the IRQ */
    if ((irq < 1) || (irq > 7))
	/* return "IRQ out of range" */
	return(1);

    /* see if the ISR is in the list */
    for (cur = ISR_list[irq-1];
	 cur != (struct ISR_node *)NULL;
	 cur = cur->next)
	/* if this ISR is installed */
	if (cur->isr == isr)
	    {
	    /* we found it, so copy the priority */
	    cur->pri = pri;

	    /* return success */
	    return(0);
	    }

    /* return ISR not found */
    return(2);
    }

/*
** int set_ISR_mask(int irq, isrfunc isr, struct ISR_mask *mask);
**
** Parameters:	irq - IRQ number (0 to 7)
**		isr - pointer the ISR function
**		mask - pointer to the mask structure
**
** Returns:	0 - success
**		1 - IRQ out of range
**		2 - ISR function not found in list
*/

int
    set_ISR_mask(int irq, isrfunc isr, struct ISR_mask *mask)
    {
    struct ISR_node
	*cur;

    int
	PPL_tmp;

    /* check the IRQ */
    if ((irq < 1) || (irq > 7))
	/* return "IRQ out of range" */
	return(1);

    /* see if the ISR is in the list */
    for (cur = ISR_list[irq-1];
	 cur != (struct ISR_node *)NULL;
	 cur = cur->next)
	/* if this ISR is installed */
	if (cur->isr == isr)
	    {
	    /* disable interrupts of this level and lower */
	    PPL_tmp = get_PPL();
	    if (PPL_tmp < irq)
		set_PPL(irq);

	    /* we found it, so copy the mask */
	    cur->mask.intreqr_mask = mask->intreqr_mask;
	    cur->mask.ciaa_mask = mask->ciaa_mask;
	    cur->mask.ciab_mask = mask->ciab_mask;
	    cur->mask.always = mask->always;

	    /* restore the PPL */
	    set_PPL(PPL_tmp);

	    /* return success */
	    return(0);
	    }

    /* return ISR not found */
    return(2);
    }

/*
** int remove_ISR(int irq, isrfunc isr);
**
** Parameters:	irq - IRQ number (0 to 7)
**		isr - pointer the ISR function
**
** Returns:	0 - success
**		1 - IRQ out of range
**		2 - ISR function not found in list
*/

int
    remove_ISR(int irq, isrfunc isr)
    {
    struct ISR_node
	*cur,
	*prev;

    /*@ We might want to disable interrupts no longer requested.  However
	that would require an exhaustive search for all requested bits in
	the ISR lists.	I've left it out for now... */

    /* check the IRQ */
    if ((irq < 1) || (irq > 7))
	/* return "IRQ out of range" */
	return(1);

    /* see if the ISR is in the list */
    for (cur = ISR_list[irq-1], prev = NULL;
	 cur != (struct ISR_node *)NULL;
	 prev = cur, cur = cur->next)
	/* if this ISR is installed */
	if (cur->isr == isr)
	    {
	    /* remove the ISR */
	    if (prev)
		/* this entry was somewhere in the middle of the list */
		prev->next = cur->next;
	    else
		/* this entry was first in the list, so move up the 2nd */
		ISR_list[irq-1] = cur->next;

	    /* free the node */
	    free_ISR_node(cur);

	    /* return success */
	    return(0);
	    }

    /* return ISR not found */
    return(2);
    }

/*
** Base Interrupt Service Routines
*/

int
    base_irq1_isr(struct ISR_mask *mask, ushort sr)
    {
    /* clear the INTREQR bits for this IRQ level */
    *intreq = IRQ1_MASK;

    /* we're last, but return 'done' anyway */
    return(0);
    }

int
    base_irq2_isr(struct ISR_mask *mask, ushort sr)
    {
    /* clear the INTREQR bits for this IRQ level */
    *intreq = IRQ2_MASK;

    /* we're last, but return 'done' anyway */
    return(0);
    }

int
    base_irq3_isr(struct ISR_mask *mask, ushort sr)
    {
    /* clear the INTREQR bits for this IRQ level */
    *intreq = IRQ3_MASK;

    /* we're last, but return 'done' anyway */
    return(0);
    }

int
    base_irq4_isr(struct ISR_mask *mask, ushort sr)
    {
    /* clear the INTREQR bits for this IRQ level */
    *intreq = IRQ4_MASK;

    /* we're last, but return 'done' anyway */
    return(0);
    }

int
    base_irq5_isr(struct ISR_mask *mask, ushort sr)
    {
    /* clear the INTREQR bits for this IRQ level */
    *intreq = IRQ5_MASK;

    /* we're last, but return 'done' anyway */
    return(0);
    }

int
    base_irq6_isr(struct ISR_mask *mask, ushort sr)
    {
    /* clear the INTREQR bits for this IRQ level */
    *intreq = IRQ6_MASK;

    /* we're last, but return 'done' anyway */
    return(0);
    }

int
    base_irq7_isr(struct ISR_mask *mask, ushort sr)
    {
    /* clear the INTREQR bits for this IRQ level */
    *intreq = IRQ7_MASK;

    /* we're last, but return 'done' anyway */
    return(0);
    }

/*
** temporary functions -- will be gone when mm code is in place
*/

int
    alloc_ISR_node(struct ISR_node **node)
    {
    int
	i=0;

    /* set the node pointer to NULL */
    *node = (struct ISR_node *)NULL;

    /* traverse the ISR storage array until we find a free entry */
    do
	{
	/* this this entry doesn't point to a function (i.e. isn't allocated) */
	if (!(ISR_node_mem[i].isr))
	    {
	    /* point our function to it */
	    *node = &ISR_node_mem[i];

	    /* and just for safety's sake, make it look allocated */
	    (*node)->isr = base_irq1_isr;
	    }

	/* move to next node space */
	i++;
	}
    while (!(*node) && (i < NUM_STRUCTS));

    /* if we found space */
    if (*node)
	/* return success */
	return(0);

    /* else, return failure */
    return(1);
    }

void
    free_ISR_node(struct ISR_node *node)
    {
    int
	i=0;

    /* make sure the node points to a list member */
    for (i=0; (node != &ISR_node_mem[i]) && (i < NUM_STRUCTS); i++)
	;

    /* if we found the entry */
    if (i < NUM_STRUCTS)
	/* clear its function pointer to free it */
	node->isr = (isrfunc)NULL;
    }
