/* $NetBSD: omap_intr.c,v 1.11 2014/03/26 08:52:00 christos Exp $ */
/*
* Based on arch/arm/xscale/pxa2x0_intr.c
*
* 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 Texas Instruments OMAP processors. The OMAPs
* have two cascaded interrupt controllers. They have similarities, but
* are not identical.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: omap_intr.c,v 1.11 2014/03/26 08:52:00 christos Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/device.h>
#include <sys/bus.h>
#include <machine/intr.h>
#include <machine/lock.h>
#include <arm/omap/omap_reg.h>
#include <arm/omap/omap_tipb.h>
/*
* Array of arrays to give us the per bank masks for each level. The OMAP
* interrupt controller blocks an interrupt when the corresponding bit is set
* in the mask register. Initialize our masks to have all interrupts blocked
* for all levels.
*/
uint32_t omap_spl_masks[NIPL][OMAP_NBANKS] =
{ [ 0 ... NIPL-1 ] = { [ 0 ... OMAP_NBANKS-1 ] = ~0 } };
/*
* And OR in the following global interrupt masks at all levels. This is
* used to hold off interrupts that omap_irq_handler will soon service,
* since servicing is performed in random order wrt spl levels.
*/
uint32_t omap_global_masks[OMAP_NBANKS];
static int stray_interrupt(void *);
static void init_interrupt_masks(void);
static void omap_update_intr_masks(int, int);
static void omapintc_set_name(int, const char *, int);
typedef int (* omap_irq_handler_t)(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 irq_handler {
struct evcnt ev;
char irq_num_str[8];
const char *name;
#ifdef MULTIPLE_HANDLERS_ON_ONE_IRQ
TAILQ_HEAD(,intrhand) list;
#else
omap_irq_handler_t func;
#endif
void *cookie; /* NULL for stack frame */
/* struct evbnt ev; */
} handler[OMAP_NIRQ];
static int extirq_level[OMAP_NIRQ];
int
omapintc_match(device_t parent, cfdata_t cf, void *aux)
{
return (1);
}
void
omapintc_attach(device_t parent, device_t self, void *args)
{
int i;
aprint_normal(": Interrupt Controller\n");
aprint_naive("\n");
/* Make sure we have interrupts globally off. */
disable_interrupts(I32_bit);
/*
* Initialize the controller hardware.
*/
/* Turn off the first level's global mask. */
write_icu(OMAP_INT_L1_BASE, OMAP_INTL1_GMR, 0);
/* Turn off the second level's global mask. */
write_icu(OMAP_INT_L2_BASE, OMAP_INTL2_CONTROL, 0);
/* Allow the second level to automatically idle. */
write_icu(OMAP_INT_L2_BASE, OMAP_INTL2_OCP_CFG,
OMAP_INTL2_OCP_CFG_SMART_IDLE | OMAP_INTL2_OCP_CFG_AUTOIDLE);
/*
* Now set all of the Interrupt Level Registers. Set the triggering
* as appropriate for that interrupt, but otherwise, set them all to
* low priority and to be an IRQ (not a FIQ).
*/
static const uint32_t ilr_def
= ((OMAP_INTB_ILR_PRIO_LOWEST << OMAP_INTB_ILR_PRIO_SHIFT)
| OMAP_INTB_ILR_IRQ);
for (i=0; i<OMAP_NIRQ; i++) {
const omap_intr_info_t *inf = &omap_intr_info[i];
volatile uint32_t *ILR = (volatile uint32_t *)inf->ILR;
switch (inf->trig) {
case INVALID:
break;
case TRIG_LEVEL:
*ILR = ilr_def | OMAP_INTB_ILR_LEVEL;
break;
case TRIG_EDGE:
case TRIG_LEVEL_OR_EDGE:
*ILR = ilr_def | OMAP_INTB_ILR_EDGE;
break;
default:
panic("Bad trigger (%d) on irq %d\n", inf->trig, i);
break;
}
}
/* Set all interrupts to be stray and to have event counters. */
omapintc_set_name(OMAP_INT_L2_IRQ, "IRQ from L2", false);
omapintc_set_name(OMAP_INT_L2_FIQ, "FIQ from L2", false);
#ifdef __HAVE_FAST_SOFTINTS
omapintc_set_name(omap_si_to_irq[SI_SOFTCLOCK], "SOFTCLOCK", false);
omapintc_set_name(omap_si_to_irq[SI_SOFTBIO], "SOFTBIO", false);
omapintc_set_name(omap_si_to_irq[SI_SOFTNET], "SOFTNET", false);
omapintc_set_name(omap_si_to_irq[SI_SOFTSERIAL], "SOFTSERIAL", false);
#endif
for(i = 0; i < __arraycount(handler); ++i) {
handler[i].func = stray_interrupt;
handler[i].cookie = (void *)(intptr_t) i;
extirq_level[i] = IPL_SERIAL;
snprintf(handler[i].irq_num_str,
sizeof(handler[i].irq_num_str), "#%d", i);
if (handler[i].name == NULL)
omapintc_set_name(i, handler[i].irq_num_str, false);
}
/* Initialize our table of masks. */
init_interrupt_masks();
/*
* Now that we have the masks for all the levels set up, write the
* masks to the hardware.
*/
omap_splx(IPL_SERIAL);
/* Everything is all set. Enable the interrupts. */
enable_interrupts(I32_bit);
}
static void
dispatch_irq(int irqno, struct clockframe *frame)
{
if (extirq_level[irqno] != curcpl())
splx(extirq_level[irqno]);
#ifndef MULTIPLE_HANDLERS_ON_ONE_IRQ
(* handler[irqno].func)(handler[irqno].cookie == 0
? frame : handler[irqno].cookie );
#else
/* process all handlers for this interrupt. */
#error Having multiple handlers per IRQ is not yet supported.
#endif
}
/*
* called from irq_entry.
*/
void
omap_irq_handler(void *arg)
{
struct clockframe *frame = arg;
int saved_spl_level;
int unmaskedpend[OMAP_NBANKS];
int bank;
int level2 = 0;
saved_spl_level = curcpl();
for (bank = 0; bank < OMAP_NBANKS; bank++) {
int masked = read_icu(omap_intr_bank_bases[bank],
OMAP_INTB_MIR);
int pend = read_icu(omap_intr_bank_bases[bank],
OMAP_INTB_ITR);
/*
* Save away pending unmasked interrupts for handling in
* a moment. Mask those interrupts, will unmask after
* serviced.
*/
unmaskedpend[bank] = pend & ~masked;
write_icu(omap_intr_bank_bases[bank], OMAP_INTB_ITR,
~unmaskedpend[bank]);
write_icu(omap_intr_bank_bases[bank], OMAP_INTB_MIR,
masked | unmaskedpend[bank]);
omap_global_masks[bank] |= unmaskedpend[bank];
/*
* If any interrupt is pending for the Level-2 controller
* then we need a Level-2 new IRQ agreement to receive
* another one.
*/
if (bank && pend)
level2 = 1;
}
/* Let the interrupt controllers process the next interrupt.
Acknowledge Level-1 IRQs. */
write_icu(OMAP_INT_L1_BASE, OMAP_INTL1_CONTROL,
OMAP_INTL1_CONTROL_NEW_IRQ_AGR);
if (level2) {
/* Acknowledge Level-2 IRQs. */
write_icu(OMAP_INT_L2_BASE, OMAP_INTL2_CONTROL,
OMAP_INTL2_CONTROL_NEW_IRQ_AGR);
}
/*
* Invoke handlers for interrupts found pending and unmasked
* above. Unmask the interrupts for each bank after servicing.
*/
for (bank = 0; bank < OMAP_NBANKS; bank++) {
int irqno;
int oldirqstate;
int pendtodo = unmaskedpend[bank];
if (!unmaskedpend[bank])
continue;
while ((irqno = ffs(pendtodo) - 1) != -1) {
irqno += bank * OMAP_BANK_WIDTH;
if (omap_intr_info[irqno].trig == INVALID)
printf("Bad IRQ %d.\n", irqno);
handler[irqno].ev.ev_count++;
if (irqno != OMAP_INT_L2_IRQ) {
oldirqstate = enable_interrupts(I32_bit);
dispatch_irq(irqno, frame);
restore_interrupts(oldirqstate);
}
pendtodo &= ~omap_intr_info[irqno].mask;
}
omap_global_masks[bank] &= ~unmaskedpend[bank];
write_icu(omap_intr_bank_bases[bank], OMAP_INTB_MIR,
read_icu(omap_intr_bank_bases[bank],
OMAP_INTB_MIR) &
~unmaskedpend[bank]);
}
/* Restore spl to what it was when this interrupt happened. */
splx(saved_spl_level);
}
static int
stray_interrupt(void *cookie)
{
int irqno = (int)cookie;
printf("stray interrupt number %d: \"%s\".\n",
irqno, handler[irqno].name);
if (OMAP_IRQ_MIN <= irqno && irqno < OMAP_NIRQ){
/* Keep it from happening again. */
omap_update_intr_masks(irqno, IPL_NONE);
}
return 0;
}
static inline void
level_block_irq(int lvl, const omap_intr_info_t *inf)
{
omap_spl_masks[lvl][inf->bank_num] |= inf->mask;
}
static inline void
level_allow_irq(int lvl, const omap_intr_info_t *inf)
{
omap_spl_masks[lvl][inf->bank_num] &= ~inf->mask;
}
static inline void
level_mask_level(int dst_level, int src_level)
{
int i;
for (i = 0; i < OMAP_NBANKS; i++) {
omap_spl_masks[dst_level][i]
|= omap_spl_masks[src_level][i];
}
}
/*
* Interrupt Mask Handling
*/
static void
omap_update_intr_masks(int irqno, int level)
{
const omap_intr_info_t *irq_inf =&omap_intr_info[irqno];
int i;
int psw = disable_interrupts(I32_bit);
for(i = 0; i < level; ++i)
/* Enable interrupt at lower level */
level_allow_irq(i, irq_inf);
for( ; i < NIPL-1; ++i)
/* Disable interrupt at upper level */
level_block_irq(i, irq_inf);
/*
* There is no way for interrupts to be enabled at upper levels, but
* not at lower levels and vice-versa. This means that this function
* doesn't have to enforce it.
*/
/* Refresh the hardware's masks in case the current level's changed. */
omap_splx(curcpl());
restore_interrupts(psw);
}
static void
init_interrupt_masks(void)
{
const omap_intr_info_t
#ifdef __HAVE_FAST_SOFTINTS
*softclock_inf =&omap_intr_info[omap_si_to_irq[SI_SOFTCLOCK]],
*softbio_inf =&omap_intr_info[omap_si_to_irq[SI_SOFTBIO]],
*softnet_inf =&omap_intr_info[omap_si_to_irq[SI_SOFTNET]],
*softserial_inf=&omap_intr_info[omap_si_to_irq[SI_SOFTSERIAL]],
#endif
*l2_inf =&omap_intr_info[OMAP_INT_L2_IRQ];
int i;
#ifdef __HAVE_FAST_SOFTINTS
/*
* We just blocked all the interrupts in all the masks. Now we just
* go through and modify the masks to allow the software interrupts as
* documented in the spl(9) man page.
*/
for (i = IPL_NONE; i < IPL_SOFTCLOCK; ++i)
level_allow_irq(i, softclock_inf);
for (i = IPL_NONE; i < IPL_SOFTBIO; ++i)
level_allow_irq(i, softbio_inf);
for (i = IPL_NONE; i < IPL_SOFTNET; ++i)
level_allow_irq(i, softnet_inf);
for (i = IPL_NONE; i < IPL_SOFTSERIAL; ++i)
level_allow_irq(i, softserial_inf);
#endif
/*
* We block level 2 interrupts down in the level 2 controller, so we
* can allow the level 1 interrupt controller to service the level 2
* interrupts at all levels.
*/
for (i = IPL_NONE; i < IPL_SERIAL; ++i)
level_allow_irq(i, l2_inf);
}
#undef splx
void
splx(int ipl)
{
omap_splx(ipl);
}
#undef _splraise
int
_splraise(int ipl)
{
return omap_splraise(ipl);
}
#undef _spllower
int
_spllower(int ipl)
{
return omap_spllower(ipl);
}
void *
omap_intr_establish(int irqno, int level, const char *name,
int (*func)(void *), void *cookie)
{
const omap_intr_info_t *info;
int psw;
if (irqno < OMAP_IRQ_MIN || irqno >= OMAP_NIRQ
|| irqno == OMAP_INT_L2_IRQ
|| omap_intr_info[irqno].trig == INVALID)
panic("%s(): bogus irq number %d", __func__, irqno);
#ifndef MULTIPLE_HANDLERS_ON_ONE_IRQ
if (handler[irqno].func != stray_interrupt)
panic("Shared interrupts are not supported (irqno=%d)\n",
irqno);
#endif
psw = disable_interrupts(I32_bit);
/*
* Name the evcnt what they passed us. Note this will zero the
* count.
*/
omapintc_set_name(irqno, name, true);
/* Clear any existing interrupt by writing a zero into its ITR bit. */
info = &omap_intr_info[irqno];
write_icu(info->bank_base, OMAP_INTB_ITR, ~info->mask);
handler[irqno].cookie = cookie;
handler[irqno].func = func;
extirq_level[irqno] = level;
omap_update_intr_masks(irqno, level);
restore_interrupts(psw);
return (&handler[irqno]);
}
void
omap_intr_disestablish(void *v)
{
struct irq_handler *irq_handler = v;
int irqno = ((unsigned)v - (unsigned)&handler[0])/sizeof(handler[0]);
if (irqno < OMAP_IRQ_MIN || irqno >= OMAP_NIRQ
|| irqno == OMAP_INT_L2_IRQ
|| omap_intr_info[irqno].trig == INVALID)
panic("%s(): bogus irq number %d", __func__, irqno);
int psw = disable_interrupts(I32_bit);
/*
* Put the evcnt name back to our number string. Note this will zero
* the count.
*/
omapintc_set_name(irqno, handler[irqno].irq_num_str, true);
irq_handler->cookie = (void *)(intptr_t) irqno;
irq_handler->func = stray_interrupt;
extirq_level[irqno] = IPL_SERIAL;
omap_update_intr_masks(irqno, IPL_NONE);
restore_interrupts(psw);
}
static void
omapintc_set_name(int irqno, const char *name, int detach_first)
{
const char *group;
if (irqno < OMAP_INT_L1_NIRQ)
group = "omap_intr L1";
else
group = "omap_intr L2";
if (detach_first)
evcnt_detach(&handler[irqno].ev);
handler[irqno].name = name;
evcnt_attach_dynamic(&handler[irqno].ev, EVCNT_TYPE_INTR, NULL,
group, handler[irqno].name);
}