/* Copyright (c) 2006 Jon Sevy <jsevy@cs.drexel.edu>
 * 
 * Based on PXA interrupt controller,
 * Copyright (c) 2002  Genetec Corporation.  All rights reserved.
 * Written by Hiroyuki Bessho for Genetec Corporation.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *This product includes software developed for the NetBSD Project by
 *Genetec Corporation.
 * 4. The name of Genetec Corporation may not be used to endorse or 
 *    promote products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY GENETEC CORPORATION ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL GENETEC CORPORATION
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * IRQ handler for the Agere Vx115 processor.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: vx115_intr.c,v 1.5 2003/07/15 00:24:55 lukem Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>

#include <machine/bus.h>
#include <machine/intr.h>
#include <machine/lock.h>

#include <arm/vx115/vx115_reg.h>
#include <arm/vx115/vx115_var.h>
#include <arm/vx115/vx115_intr.h>


#define DEBUG_INTR

#ifdef DEBUG_INTR
#define DPRINTF(fmt...)  printf(fmt)

static void DPRINTF_MASKS(void)
{
    int i;
    
    printf("pri       hard mask       soft mask\n");
    for (i = 0; i < NIPL; i++)
        printf(" %i      0x%08x     0x%08x\n", i, vx115_pic_spl_mask[i], vx115_pic_spl_soft_mask[i]);
}

#else
#define DPRINTF(fmt...)  
#define DPRINTF_MASKS()   
#endif


struct vx115_pic_softc vx115_pic_init_sc;

struct vx115_pic_softc *vx115_pic_sc;


/* number of hardware IRQs */
#define NR_IRQS 32

/* Array mapping priorities to associated IRQ number                    */
/* The [IRQ_XXX_PRIORITY] values are defined in vx115_irq.h, and can    */
/* be changed without needing to change this array initialization       */
/* Note that these priority levels differ from the system priority      */
/* levels (SPLs), and are used just to prioritize between simultaneous  */
/* interrupts at the controller.                                        */
static u_int32_t irq_priorities[NR_IRQS] =
{
    0,
    [IRQ_TIMER1_PRIORITY]       = IRQ_TIMER1,
    [IRQ_DMA1_ERROR_PRIORITY]   = IRQ_DMA1_ERROR,
    [IRQ_DMA1_PRIORITY]         = IRQ_DMA1,
    [IRQ_DISPLAY_PRIORITY]      = IRQ_DISPLAY,
    [IRQ_DISP_SYNC_PRIORITY]    = IRQ_DISP_SYNC,
    [IRQ_SSP1_PRIORITY]         = IRQ_SSP1,
    [IRQ_SSP3_PRIORITY]         = IRQ_SSP3,
    [IRQ_UART0_WAKE_PRIORITY]   = IRQ_UART0_WAKE,
    [IRQ_UART0_PRIORITY]        = IRQ_UART0,
    [IRQ_SDMCC0_PRIORITY]       = IRQ_SDMCC0,
    [IRQ_SDMCC1_PRIORITY]       = IRQ_SDMCC1,
    [IRQ_I2C_PRIORITY]          = IRQ_I2C,
    [IRQ_KEYPAD_PRIORITY]       = IRQ_KEYPAD,
    [IRQ_GPIOA_PRIORITY]        = IRQ_GPIOA,
    [IRQ_GPIOB_PRIORITY]        = IRQ_GPIOB,
    [IRQ_USB_EXTINT_PRIORITY]   = IRQ_USB_EXTINT,
    [IRQ_USB_OTG_PRIORITY]      = IRQ_USB_OTG,
    [IRQ_DSP_PCU_PRIORITY]      = IRQ_DSP_PCU,
    [IRQ_ARM7_PCU_PRIORITY]     = IRQ_ARM7_PCU,
    [IRQ_SWI_PRIORITY]          = IRQ_SWI,
    [IRQ_CAMERA_PRIORITY]       = IRQ_CAMERA,
    [IRQ_EXT2_PRIORITY]         = IRQ_EXT2,
    [IRQ_EXT3_PRIORITY]         = IRQ_EXT3,
    [IRQ_AGPIOA_PRIORITY]       = IRQ_AGPIOA,
    [IRQ_AGPIOB_PRIORITY]       = IRQ_AGPIOB,
    [IRQ_EXT4_PRIORITY]         = IRQ_EXT4,
    [IRQ_EXT5_PRIORITY]         = IRQ_EXT5,
    [IRQ_EXT6_PRIORITY]         = IRQ_EXT6,
    [IRQ_EXT7_PRIORITY]         = IRQ_EXT7,
    [IRQ_RESERVED24_PRIORITY]   = IRQ_RESERVED24,
    [IRQ_RESERVED27_PRIORITY]   = IRQ_RESERVED27
};


/*
 * PIC autoconf glue
 */
static int vx115_pic_match(struct device *, struct cfdata *, void *);
static void vx115_pic_attach(struct device *, struct device *, void *);


CFATTACH_DECL(vx115_pic, sizeof(struct vx115_pic_softc), vx115_pic_match, vx115_pic_attach, NULL, NULL);

static int vx115_pic_attached;

static int stray_interrupt(void *);
static void vx115_pic_init_interrupt_masks(void);

/*
 * interrupt dispatch table. 
 */
#ifdef MULTIPLE_HANDLERS_ON_ONE_IRQ
struct intrhand {
    TAILQ_ENTRY(intrhand) ih_list;  /* link on intrq list */
    int (*ih_func)(void *);         /* handler */
    void *ih_arg;                   /* arg for handler */
};
#endif

static struct {
#ifdef MULTIPLE_HANDLERS_ON_ONE_IRQ
    TAILQ_HEAD(,intrhand) list;
#else
    vx115_irq_handler_t func;
#endif
    void *cookie;/* NULL for stackframe */
    /* struct evbnt ev; */
} handler[NR_IRQS];


__volatile int softint_pending;
__volatile int current_spl_level;


/* interrupt enable masks for each level; used for spl changes  */
/* we use separate masks for the hardware and software IRQs,    */
/* since the hardware IRQs use 32 bits                          */
int vx115_pic_spl_mask[NIPL];
int vx115_pic_spl_soft_mask[NIPL];

static int extirq_level[NR_IRQS];




static int
vx115_pic_match(struct device *parent, struct cfdata *cf, void *aux)
{
    struct vx115_attach_args *sa = aux;
    
    DPRINTF("vx115_pic_match\n");
    
    if (vx115_pic_attached || sa->sa_addr != PIC1_BASE_PHYS)
        return (0);
    
    return (1);
}

void
vx115_pic_attach(struct device *parent, struct device *self, void *aux)
{
    struct vx115_pic_softc     *sc = (struct vx115_pic_softc*)self;
    struct vx115_attach_args   *sa = (struct vx115_attach_args*)aux;
    int i;
    
    DPRINTF("vx115_pic_attach\n");
    
    /* get bus space and handle */
    sc->sc_iot = sa->sa_iot;
    
    if (vx115_pic_sc == NULL)
        vx115_pic_sc = sc;

    /* map bus space and get handle */
    if (bus_space_map(sc->sc_iot, sa->sa_addr, sa->sa_size, 0, &sc->sc_ioh) != 0)
        panic("%s: Cannot map registers", self->dv_xname);    
    
    vx115_pic_attached = 1;

    /* disable all interrupts */
    vx115_write_pic(PIC_ENABLE_CLEAR, INT_REQ_SRC_CLR_ALL);

    /* clear all interrupts */
    vx115_write_pic(PIC_SOURCE_CLEAR, INT_REQ_SRC_CLR_ALL);
    
    /* set up controller priority assignment registers */
    for (i = 1; i < NR_IRQS; i++)
    {
        /* set the priority control register to the associate IRQ number */
        vx115_write_pic(PIC_IPCR_1 + ((i-1)<<2), irq_priorities[i]);
    }
    
    /* enable all irq priority levels, but clear FRZ bit */
    vx115_write_pic(PIC_PRIORITY_ENABLE_SET, PIC_IPE_ALL);
    vx115_write_pic(PIC_PRIORITY_ENABLE_CLEAR, PIC_IPE_E0);    

    /* set all handlers initially to dummy handler */
    for(i = 0; i < sizeof handler / sizeof handler[0]; ++i)
    {
        handler[i].func = stray_interrupt;
        handler[i].cookie = (void *)(intptr_t) i;
        extirq_level[i] = IPL_SERIAL;
    }

    vx115_pic_init_interrupt_masks();
    
    _splraise(IPL_SERIAL);
    enable_interrupts(I32_bit);
}

/*
 * Invoked very early on from the board-specific initarm(), in order to
 * allow us to set up softc pointer, which is needed for spl functions.
 */
void
vx115_intr_bootstrap(bus_addr_t addr, bus_size_t size)
{
    /* set up inital softc struct; this will be used only until device formally attached */
    /* assign bus tag: standard vx115 bus ops */
    vx115_pic_init_sc.sc_iot = &vx115_bs_tag;
    
    /* map bus space and set handle */
    if (bus_space_map(vx115_pic_init_sc.sc_iot, addr, size, 0, &vx115_pic_init_sc.sc_ioh) != 0)
        panic("%s: Cannot map initial interrupt softc region", "vx115_intr_bootstrap");
        
    /* assign softc pointer to init softc struct */
    vx115_pic_sc = & vx115_pic_init_sc;
}

static __inline void
__raise(int ipl)
{
    if (current_spl_level < ipl)
        vx115_setipl(ipl);
}


/*
 * Map a software interrupt queue to an interrupt priority level.
 */
static const int si_to_ipl[SI_NQUEUES] = {
    IPL_SOFT,          /* SI_SOFT */
    IPL_SOFTCLOCK,     /* SI_SOFTCLOCK */
    IPL_SOFTNET,       /* SI_SOFTNET */
    IPL_SOFTSERIAL,    /* SI_SOFTSERIAL */
};

/*
 * called from irq_entry.
 */
void
vx115_irq_dispatcher(void *arg)
{
    struct clockframe *frame = arg;
    uint32_t irqbits;
    int irqno;
    int saved_spl_level;
    
    saved_spl_level = current_spl_level;
    
    while ((irqbits = vx115_read_pic(PIC_STATUS)) != 0) 
    {
        /* FOR LATER: handle IRQs in priority order - the PIC does this for us */
        irqno = find_first_bit(irqbits);
        
        /* raise spl if necessary to stop interrupts of lower priorities */
        if (saved_spl_level < extirq_level[irqno])
            vx115_setipl(extirq_level[irqno]);
        
        (*handler[irqno].func)( (handler[irqno].cookie == 0) ? frame : handler[irqno].cookie );

        /* restore spl if it was changed */
        if (saved_spl_level < extirq_level[irqno])
            vx115_setipl(saved_spl_level);
        
        /* clear interrupt */
        vx115_write_pic(PIC_SOURCE_CLEAR, (1<<irqno));
        
    }
    
    /* do any pending soft interrutps */
    if(softint_pending & vx115_pic_spl_soft_mask[current_spl_level])
        vx115_do_pending();
}

static int
stray_interrupt(void *cookie)
{
    int irqno = (int)cookie;
    printf("stray interrupt %d\n", irqno);
    
    if (irqno < NR_IRQS) 
    {
        /* disable this interrupt */
        int save = disable_interrupts(I32_bit);
        vx115_write_pic(PIC_ENABLE_CLEAR, ~(1U<<irqno));
        restore_interrupts(save);
    }
    
    return 0;
}




/*
 * Update priority masks for new interrupt at specified priority level.
 */

void
vx115_update_intr_masks(int irqno, int level)
{
    int mask;
    int *mask_array;
    int psw = disable_interrupts(I32_bit);
    int i;
    
    /* determine whether hard or soft interrupt, and set mask    */
    /* and array affected accordingly (hard irq's are < 32)      */
    if (irqno < 32)
    {
        /* hard interrupt */
        mask_array = vx115_pic_spl_mask;
        mask = 1U << irqno;
    }
    else
    {
        mask_array = vx115_pic_spl_soft_mask;
        mask = 1U << (irqno - 32);
    }

    /* Enable interrupt when at lower priority level */
    for(i = 0; i < level; ++i)
        mask_array[i] |= mask;

    /* Disable interrupt when at supplied or higher priority level */
    for( ; i < NIPL-1; ++i)
        mask_array[i] &= ~mask;

    /*
     * Enforce a heirarchy that gives "slow" device (or devices with
     * limited input buffer space/"real-time" requirements) a better
     * chance at not dropping data.
     */
    mask_array[IPL_BIO]        &= mask_array[IPL_SOFTNET];
    mask_array[IPL_NET]        &= mask_array[IPL_BIO];
    mask_array[IPL_SOFTSERIAL] &= mask_array[IPL_NET];
    mask_array[IPL_TTY]        &= mask_array[IPL_SOFTSERIAL];
    
    /*
     * splvm() blocks all interrupts that use the kernel memory
     * allocation facilities.
     */
    mask_array[IPL_VM] &= mask_array[IPL_TTY];
    
    /*
     * Audio devices are not allowed to perform memory allocation
     * in their interrupt routines, and they have fairly "real-time"
     * requirements, so give them a high interrupt priority.
     */
    mask_array[IPL_AUDIO] &= mask_array[IPL_VM];
    
    /*
     * splclock() must block anything that uses the scheduler.
     */
    mask_array[IPL_CLOCK] &= mask_array[IPL_AUDIO];
    
    /*
     * splhigh() must block "everything".
     */
    mask_array[IPL_HIGH] &= mask_array[IPL_STATCLOCK];
    
    /*
     * XXX We need serial drivers to run at the absolute highest priority
     * in order to avoid overruns, so serial > high.
     */
    mask_array[IPL_SERIAL] &= mask_array[IPL_HIGH];
    
    /* enable hardware interrupts appropriate to current spl level */
    vx115_write_pic(PIC_ENABLE_SET, vx115_pic_spl_mask[current_spl_level]);
    
    /* disable hardware interrupts appropriate to current spl level */
    vx115_write_pic(PIC_ENABLE_CLEAR, ~vx115_pic_spl_mask[current_spl_level]);
    
    restore_interrupts(psw);
    
    DPRINTF("vx115_update_intr_masks: irqno %i, level %i\n", irqno, level);
    DPRINTF_MASKS();
}


static void
vx115_pic_init_interrupt_masks(void)
{

    DPRINTF("vx115_pic_init_intr_masks\n");
    
    
    memset(vx115_pic_spl_mask, 0, sizeof(vx115_pic_spl_mask));
    memset(vx115_pic_spl_soft_mask, 0, sizeof(vx115_pic_spl_soft_mask));
    
    /*
     * IPL_NONE has soft interrupts enabled only, at least until
     * hardware handlers are installed.
     */
    vx115_pic_spl_soft_mask[IPL_NONE] =
        SI_TO_IRQBIT(SI_SOFT) |
        SI_TO_IRQBIT(SI_SOFTCLOCK) |
        SI_TO_IRQBIT(SI_SOFTNET) |
        SI_TO_IRQBIT(SI_SOFTSERIAL);

    /*
     * Initialize the soft interrupt masks to block themselves.
     */
    vx115_pic_spl_soft_mask[IPL_SOFT]       = ~SI_TO_IRQBIT(SI_SOFT);
    vx115_pic_spl_soft_mask[IPL_SOFTCLOCK]  = ~SI_TO_IRQBIT(SI_SOFTCLOCK);
    vx115_pic_spl_soft_mask[IPL_SOFTNET]    = ~SI_TO_IRQBIT(SI_SOFTNET);
    vx115_pic_spl_soft_mask[IPL_SOFTSERIAL] = ~SI_TO_IRQBIT(SI_SOFTSERIAL);
    
    vx115_pic_spl_soft_mask[IPL_SOFT] &= vx115_pic_spl_soft_mask[IPL_NONE];
    
    /*
     * splsoftclock() is the only interface that users of the
     * generic software interrupt facility have to block their
     * soft intrs, so splsoftclock() must also block IPL_SOFT.
     */
    vx115_pic_spl_soft_mask[IPL_SOFTCLOCK] &= vx115_pic_spl_soft_mask[IPL_SOFT];
    
    /*
     * splsoftnet() must also block splsoftclock(), since we don't
     * want timer-driven network events to occur while we're
     * processing incoming packets.
     */
    vx115_pic_spl_soft_mask[IPL_SOFTNET] &= vx115_pic_spl_soft_mask[IPL_SOFTCLOCK];
    
    DPRINTF_MASKS();
}


void
vx115_do_pending(void)
{
    static __cpu_simple_lock_t processing = __SIMPLELOCK_UNLOCKED;
    int oldirqstate, spl_save;
    
    if (__cpu_simple_lock_try(&processing) == 0)
        return;
    
    spl_save = current_spl_level;
    
    oldirqstate = disable_interrupts(I32_bit);
        
#define DO_SOFTINT(si,ipl)  \
        if ((softint_pending & vx115_pic_spl_soft_mask[current_spl_level]) & SI_TO_IRQBIT(si)) { \
            softint_pending &= ~SI_TO_IRQBIT(si);       \
            __raise(ipl);                               \
            restore_interrupts(oldirqstate);            \
            softintr_dispatch(si);                      \
            oldirqstate = disable_interrupts(I32_bit);  \
            vx115_setipl(spl_save);                     \
        }
    
    do 
    {
        DO_SOFTINT(SI_SOFTSERIAL,IPL_SOFTSERIAL);
        DO_SOFTINT(SI_SOFTNET, IPL_SOFTNET);
        DO_SOFTINT(SI_SOFTCLOCK, IPL_SOFTCLOCK);
        DO_SOFTINT(SI_SOFT, IPL_SOFT);
    } while( softint_pending & vx115_pic_spl_soft_mask[current_spl_level] );

    __cpu_simple_unlock(&processing);
    
    restore_interrupts(oldirqstate);
}


#undef splx
void
splx(int ipl)
{
    vx115_splx(ipl);
}

#undef _splraise
int
_splraise(int ipl)
{
    return vx115_splraise(ipl);
}

#undef _spllower
int
_spllower(int ipl)
{
    return vx115_spllower(ipl);
}

#undef _setsoftintr
void
_setsoftintr(int si)
{
    return vx115_setsoftintr(si);
}


void *
vx115_intr_establish(int irqno, int level, int (*func)(void *), void *cookie)
{
    int psw;
    
    if (irqno < VX115_IRQ_MIN || irqno >= VX115_IRQ_MAX)
        panic("intr_establish: bogus irq number %d", irqno);
    
    psw = disable_interrupts(I32_bit);
    
    handler[irqno].cookie = cookie;
    handler[irqno].func = func;
    extirq_level[irqno] = level;
        
    vx115_update_intr_masks(irqno, level);
    
    restore_interrupts(psw);
    
    return (&handler[irqno]);
}


/* Configure the irq's sense (level or edge) and polarity (low-high or high-low) */
int vx115_configure_irq(unsigned int irq, unsigned int sense, unsigned int polarity)
{
    unsigned int irq_mask;
    unsigned int irq_enabled;
    unsigned int offset;

    /* world's most annoying register offset calculation */
    offset = irq << 2;

    if ((1 <= irq) && (irq <= 7))
    {
        offset += 0xA4;
    }
    else if ((8 <= irq) && (irq <= 26))
    {
        offset += 0xE0;
    }
    else if ((27 <= irq) && (irq <= 28))
    {
        offset += 0x58;
    }
    else if ((29 <= irq) && (irq <= 30))
    {
        offset += 0x64;
    }
    else 
    {
        /* bad irq number supplied */
        return -1;
    }

    /* create mask for this irq */
    irq_mask = 1 << irq;

    /* get current interrupt enabled state */
    irq_enabled = vx115_read_pic(PIC_ENABLE_SET) & irq_mask;

    /* assign supplied parameters */
    vx115_write_pic(offset, sense | polarity | VX115_INT_ENABLED);

    /* clear the interrupt in case got a false one */
    vx115_write_pic(PIC_SOURCE_CLEAR, irq_mask);

    /* reenable the irq if enabled on entry */
    vx115_write_pic(PIC_ENABLE_SET, irq_enabled);

    return 0;
}



void vx115_enable_irq(unsigned int irq)
{
    vx115_write_pic(PIC_ENABLE_SET, 1 << irq);
}


void vx115_disable_irq(unsigned int irq)
{
    vx115_write_pic(PIC_ENABLE_CLEAR, 1 << irq);
}


