#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: sysasic.c,v 1.14 2008/04/28 20:23:16 martin Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/syslog.h>
#include <sh3/exception.h>
#include <machine/intr.h>
#include <machine/sysasicvar.h>
#define SYSASIC_INTR_ST 0xa05f6900
#define SYSASIC_INTR_EN(level) (0xa05f6910 + ((level) << 4))
#define SYSASIC_IRQ_LEVEL_13 0
#define SYSASIC_IRQ_LEVEL_11 1
#define SYSASIC_IRQ_LEVEL_9 2
#define SYSASIC_IRQ_LEVEL_MAX 2
#define SYSASIC_IRQ_INDEX_TO_IRQ(i) (13 - 2 * (i))
struct sysasic_intrhand {
#define SYSASIC_EVENT_NMAP ((SYSASIC_EVENT_MAX + 1 + (32 - 1)) / 32)
#define SYSASIC_EVENT_INTR_MAP(ev) ((ev) >> 5)
#define SYSASIC_EVENT_INTR_BIT(ev) ((unsigned) 1 << ((ev) & 31))
unsigned syh_events[SYSASIC_EVENT_NMAP];
unsigned syh_hndmap[SYSASIC_EVENT_NMAP];
void *syh_intc;
int syh_idx;
} sysasic_intrhand[SYSASIC_IRQ_LEVEL_MAX + 1];
struct syh_eventhand {
int (*hnd_fn)(void *);
void *hnd_arg;
struct sysasic_intrhand *hnd_syh;
} sysasic_eventhand[SYSASIC_EVENT_MAX + 1];
int sysasic_intr(void *);
const char * __pure
sysasic_intr_string(int irl)
{
switch (irl) {
default:
#ifdef DEBUG
panic("sysasic_intr_string: unknown IRL%d", irl);
#endif
case SYSASIC_IRL9:
return "SH4 IRL 9";
case SYSASIC_IRL11:
return "SH4 IRL 11";
case SYSASIC_IRL13:
return "SH4 IRL 13";
}
}
void *
sysasic_intr_establish(int event, int ipl, int irl, int (*ih_fun)(void *),
void *ih_arg)
{
struct sysasic_intrhand *syh;
struct syh_eventhand *hnd;
int idx;
static const int idx2evt[3] = {
SH_INTEVT_IRL13, SH_INTEVT_IRL11, SH_INTEVT_IRL9
};
#ifdef DEBUG
int i;
#endif
KDASSERT(event >= 0 && event <= SYSASIC_EVENT_MAX);
KDASSERT(ih_fun);
switch (irl) {
default:
#ifdef DEBUG
panic("sysasic_intr_establish: unknown IRL %d", irl);
#endif
case SYSASIC_IRL9:
idx = SYSASIC_IRQ_LEVEL_9;
break;
case SYSASIC_IRL11:
idx = SYSASIC_IRQ_LEVEL_11;
break;
case SYSASIC_IRL13:
idx = SYSASIC_IRQ_LEVEL_13;
break;
}
syh = &sysasic_intrhand[idx];
if (syh->syh_intc == NULL) {
syh->syh_idx = idx;
syh->syh_intc = intc_intr_establish(idx2evt[idx], IST_LEVEL,
irl, sysasic_intr, syh);
}
#ifdef DEBUG
for (i = 0; i <= SYSASIC_IRQ_LEVEL_MAX; i++)
if ((sysasic_intrhand[i].syh_hndmap[SYSASIC_EVENT_INTR_MAP(event)] &
SYSASIC_EVENT_INTR_BIT(event)) != 0)
panic("sysasic_intr_establish: event %d already installed irq %d",
event, SYSASIC_IRQ_INDEX_TO_IRQ(i));
#endif
syh->syh_hndmap[SYSASIC_EVENT_INTR_MAP(event)] |=
SYSASIC_EVENT_INTR_BIT(event);
hnd = &sysasic_eventhand[event];
hnd->hnd_fn = ih_fun;
hnd->hnd_arg = ih_arg;
hnd->hnd_syh = syh;
sysasic_intr_enable(hnd, 1);
return (void *)hnd;
}
void
sysasic_intr_disestablish(void *arg)
{
struct syh_eventhand *hnd = arg;
struct sysasic_intrhand *syh;
int event;
int i;
event = hnd - sysasic_eventhand;
KDASSERT(event >= 0 && event <= SYSASIC_EVENT_MAX);
syh = hnd->hnd_syh;
#ifdef DIAGNOSTIC
if ((syh->syh_hndmap[SYSASIC_EVENT_INTR_MAP(event)] &
SYSASIC_EVENT_INTR_BIT(event)) == 0)
panic("sysasic_intr_disestablish: event %d not installed for irq %d",
event, SYSASIC_IRQ_INDEX_TO_IRQ(syh->syh_idx));
#endif
sysasic_intr_enable(hnd, 0);
hnd->hnd_fn = 0;
hnd->hnd_arg = 0;
hnd->hnd_syh = 0;
syh->syh_hndmap[SYSASIC_EVENT_INTR_MAP(event)] &=
~SYSASIC_EVENT_INTR_BIT(event);
for (i = 0; i < SYSASIC_EVENT_NMAP; i++)
if (syh->syh_hndmap[i])
return;
intc_intr_disestablish(syh->syh_intc);
syh->syh_intc = 0;
}
void
sysasic_intr_enable(void *arg, int on)
{
struct syh_eventhand *hnd = arg;
struct sysasic_intrhand *syh;
int event;
volatile uint32_t *masks, *stats;
int evmap;
unsigned evbit;
event = hnd - sysasic_eventhand;
KDASSERT(event >= 0 && event <= SYSASIC_EVENT_MAX);
syh = hnd->hnd_syh;
#ifdef DIAGNOSTIC
if ((syh->syh_hndmap[SYSASIC_EVENT_INTR_MAP(event)] &
SYSASIC_EVENT_INTR_BIT(event)) == 0)
panic("sysasic_intr_enable: event %d not installed for irq %d",
event, SYSASIC_IRQ_INDEX_TO_IRQ(syh->syh_idx));
#endif
masks = (volatile uint32_t *) SYSASIC_INTR_EN(syh->syh_idx);
stats = (volatile uint32_t *) SYSASIC_INTR_ST;
evmap = SYSASIC_EVENT_INTR_MAP(event);
evbit = SYSASIC_EVENT_INTR_BIT(event);
if (on) {
stats[evmap] = evbit;
syh->syh_events[evmap] |= evbit;
masks[evmap] = syh->syh_events[evmap];
} else {
masks[evmap] = syh->syh_events[evmap] & ~evbit;
stats[evmap] = evbit;
syh->syh_events[evmap] &= ~evbit;
}
}
int
sysasic_intr(void *arg)
{
struct sysasic_intrhand *syh = arg;
unsigned ev;
int n, pos;
uint32_t *evwatched;
volatile uint32_t *evmap;
struct syh_eventhand *evh;
#ifdef DEBUG
int handled = 0;
static int reportcnt = 10;
#endif
evwatched = syh->syh_events;
evmap = (volatile uint32_t *) SYSASIC_INTR_ST;
for (n = 0; n < (SYSASIC_EVENT_NMAP << 5); n |= 31, n++, evmap++) {
if ((ev = *evwatched++) && (ev &= *evmap)) {
*evmap = ev;
n--;
do {
pos = ffs(ev);
n += pos;
#ifdef __OPTIMIZE__
__asm("shld %2,%0"
: "=r" (ev) : "0" (ev), "r" (-pos));
#else
if (pos >= 32)
ev = 0;
else
ev >>= pos;
#endif
evh = &sysasic_eventhand[n];
#ifdef DEBUG
KDASSERT(evh->hnd_fn);
if ((*evh->hnd_fn)(evh->hnd_arg))
handled = 1;
#else
(*evh->hnd_fn)(evh->hnd_arg);
#endif
} while (ev);
}
}
#ifdef DEBUG
if (!handled && reportcnt > 0) {
reportcnt--;
log(LOG_ERR, "sysasic_intr: stray irq%d interrupt%s\n",
SYSASIC_IRQ_INDEX_TO_IRQ(syh->syh_idx),
reportcnt == 0 ? "; stopped logging" : "");
}
#endif
return 0;
}