#include <sys/param.h>
#include <sys/device.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/atomic.h>
#include <uvm/uvm_extern.h>
#include <ddb/db_var.h>
#include <machine/autoconf.h>
#include <machine/intr.h>
#include <machine/psl.h>
#include <machine/pio.h>
#include <dev/ofw/openfirm.h>
#define ICU_LEN 64
#define LEGAL_IRQ(x) ((x >= 0) && (x < ICU_LEN))
int macintr_ienable_l[IPL_NUM], macintr_ienable_h[IPL_NUM];
int macintr_pri_share[IPL_NUM];
struct intrq macintr_handler[ICU_LEN];
void macintr_calc_mask(void);
void macintr_eoi(int irq);
int macintr_read_irq(void);
extern u_int32_t *heathrow_FCR;
#define INT_STATE_REG0 (interrupt_reg + 0x20)
#define INT_ENABLE_REG0 (interrupt_reg + 0x24)
#define INT_CLEAR_REG0 (interrupt_reg + 0x28)
#define INT_LEVEL_REG0 (interrupt_reg + 0x2c)
#define INT_STATE_REG1 (INT_STATE_REG0 - 0x10)
#define INT_ENABLE_REG1 (INT_ENABLE_REG0 - 0x10)
#define INT_CLEAR_REG1 (INT_CLEAR_REG0 - 0x10)
#define INT_LEVEL_REG1 (INT_LEVEL_REG0 - 0x10)
struct macintr_softc {
struct device sc_dev;
};
int macintr_match(struct device *parent, void *cf, void *aux);
void macintr_attach(struct device *, struct device *, void *);
void mac_ext_intr(void);
void macintr_collect_preconf_intr(void);
void macintr_setipl(int ipl);
const struct cfattach macintr_ca = {
sizeof(struct macintr_softc),
macintr_match,
macintr_attach
};
struct cfdriver macintr_cd = {
NULL, "macintr", DV_DULL
};
int
macintr_match(struct device *parent, void *cf, void *aux)
{
struct confargs *ca = aux;
char type[40];
if (strcmp(ca->ca_name, "interrupt-controller") == 0 ) {
OF_getprop(ca->ca_node, "device_type", type, sizeof(type));
if (strcmp(type, "interrupt-controller") == 0)
return 1;
}
if (strcmp(ca->ca_name, "legacy-interrupt-controller") == 0 )
return 1;
return 0;
}
u_int8_t *interrupt_reg;
typedef void (void_f) (void);
int macintr_prog_button (void *arg);
intr_establish_t macintr_establish;
intr_disestablish_t macintr_disestablish;
ppc_splraise_t macintr_splraise;
ppc_spllower_t macintr_spllower;
ppc_splx_t macintr_splx;
int
macintr_splraise(int newcpl)
{
struct cpu_info *ci = curcpu();
int ocpl = ci->ci_cpl;
int s;
newcpl = macintr_pri_share[newcpl];
if (ocpl > newcpl)
newcpl = ocpl;
s = ppc_intr_disable();
macintr_setipl(newcpl);
ppc_intr_enable(s);
return ocpl;
}
int
macintr_spllower(int newcpl)
{
struct cpu_info *ci = curcpu();
int ocpl = ci->ci_cpl;
macintr_splx(newcpl);
return ocpl;
}
void
macintr_splx(int newcpl)
{
struct cpu_info *ci = curcpu();
int intr, s;
intr = ppc_intr_disable();
macintr_setipl(newcpl);
if (ci->ci_dec_deferred && newcpl < IPL_CLOCK) {
ppc_mtdec(0);
ppc_mtdec(UINT32_MAX);
}
if ((newcpl < IPL_SOFTTTY && ci->ci_ipending & ppc_smask[newcpl])) {
s = splsofttty();
dosoftint(newcpl);
macintr_setipl(s);
}
ppc_intr_enable(intr);
}
void
macintr_attach(struct device *parent, struct device *self, void *aux)
{
struct cpu_info *ci = curcpu();
struct confargs *ca = aux;
extern intr_establish_t *intr_establish_func;
extern intr_disestablish_t *intr_disestablish_func;
struct intrq *iq;
int i;
interrupt_reg = (void *)mapiodev(ca->ca_baseaddr,0x100);
for (i = 0; i < ICU_LEN; i++) {
iq = &macintr_handler[i];
TAILQ_INIT(&iq->iq_list);
}
ppc_smask_init();
install_extint(mac_ext_intr);
intr_establish_func = macintr_establish;
intr_disestablish_func = macintr_disestablish;
ppc_intr_func.raise = macintr_splraise;
ppc_intr_func.lower = macintr_spllower;
ppc_intr_func.x = macintr_splx;
ci->ci_flags = 0;
macintr_collect_preconf_intr();
mac_intr_establish(parent, 0x14, IST_LEVEL, IPL_HIGH,
macintr_prog_button, (void *)0x14, "progbutton");
ppc_intr_enable(1);
printf("\n");
}
void
macintr_collect_preconf_intr(void)
{
int i;
for (i = 0; i < ppc_configed_intr_cnt; i++) {
#ifdef DEBUG
printf("\n\t%s irq %d level %d fun %p arg %p",
ppc_configed_intr[i].ih_what,
ppc_configed_intr[i].ih_irq,
ppc_configed_intr[i].ih_level,
ppc_configed_intr[i].ih_fun,
ppc_configed_intr[i].ih_arg
);
#endif
macintr_establish(NULL,
ppc_configed_intr[i].ih_irq,
IST_LEVEL,
ppc_configed_intr[i].ih_level,
ppc_configed_intr[i].ih_fun,
ppc_configed_intr[i].ih_arg,
ppc_configed_intr[i].ih_what);
}
}
int
macintr_prog_button (void *arg)
{
#ifdef DDB
if (db_console)
db_enter();
#else
printf("programmer button pressed, debugger not available\n");
#endif
return 1;
}
void
macintr_setipl(int ipl)
{
struct cpu_info *ci = curcpu();
ci->ci_cpl = ipl;
if (heathrow_FCR)
out32rb(INT_ENABLE_REG1,
macintr_ienable_h[macintr_pri_share[ipl]]);
out32rb(INT_ENABLE_REG0, macintr_ienable_l[macintr_pri_share[ipl]]);
}
void *
macintr_establish(void * lcv, int irq, int type, int level,
int (*ih_fun)(void *), void *ih_arg, const char *name)
{
struct cpu_info *ci = curcpu();
struct intrq *iq;
struct intrhand *ih;
int s, flags;
if (!LEGAL_IRQ(irq) || type == IST_NONE) {
printf("%s: bogus irq %d or type %d", __func__, irq, type);
return (NULL);
}
ih = malloc(sizeof *ih, M_DEVBUF, cold ? M_NOWAIT : M_WAITOK);
if (ih == NULL)
panic("intr_establish: can't malloc handler info");
iq = &macintr_handler[irq];
switch (iq->iq_ist) {
case IST_NONE:
iq->iq_ist = type;
break;
case IST_EDGE:
intr_shared_edge = 1;
case IST_LEVEL:
if (type == iq->iq_ist)
break;
case IST_PULSE:
if (type != IST_NONE)
panic("intr_establish: can't share %s with %s",
ppc_intr_typename(iq->iq_ist),
ppc_intr_typename(type));
break;
}
flags = level & IPL_MPSAFE;
level &= ~IPL_MPSAFE;
KASSERT(level <= IPL_TTY || level >= IPL_CLOCK || flags & IPL_MPSAFE);
ih->ih_fun = ih_fun;
ih->ih_arg = ih_arg;
ih->ih_level = level;
ih->ih_flags = flags;
ih->ih_irq = irq;
evcount_attach(&ih->ih_count, name, &ih->ih_irq);
s = ppc_intr_disable();
TAILQ_INSERT_TAIL(&iq->iq_list, ih, ih_list);
macintr_calc_mask();
macintr_setipl(ci->ci_cpl);
ppc_intr_enable(s);
return (ih);
}
void
macintr_disestablish(void *lcp, void *arg)
{
struct cpu_info *ci = curcpu();
struct intrhand *ih = arg;
int irq = ih->ih_irq;
int s;
struct intrq *iq;
if (!LEGAL_IRQ(irq)) {
printf("%s: bogus irq %d", __func__, irq);
return;
}
iq = &macintr_handler[irq];
s = ppc_intr_disable();
TAILQ_REMOVE(&iq->iq_list, ih, ih_list);
macintr_calc_mask();
macintr_setipl(ci->ci_cpl);
ppc_intr_enable(s);
evcount_detach(&ih->ih_count);
free(ih, M_DEVBUF, sizeof *ih);
if (TAILQ_EMPTY(&iq->iq_list))
iq->iq_ist = IST_NONE;
}
void
macintr_calc_mask(void)
{
int irq;
struct intrhand *ih;
int i;
for (i = IPL_NONE; i < IPL_NUM; i++) {
macintr_pri_share[i] = i;
}
for (irq = 0; irq < ICU_LEN; irq++) {
int maxipl = IPL_NONE;
int minipl = IPL_HIGH;
struct intrq *iq = &macintr_handler[irq];
TAILQ_FOREACH(ih, &iq->iq_list, ih_list) {
if (ih->ih_level > maxipl)
maxipl = ih->ih_level;
if (ih->ih_level < minipl)
minipl = ih->ih_level;
}
iq->iq_ipl = maxipl;
if (maxipl == IPL_NONE) {
minipl = IPL_NONE;
} else {
for (i = minipl; i < maxipl; i++)
macintr_pri_share[i] =
macintr_pri_share[maxipl];
}
if (irq < 32) {
for (i = IPL_NONE; i < minipl; i++)
macintr_ienable_l[i] |= (1 << irq);
for (; i <= IPL_HIGH; i++)
macintr_ienable_l[i] &= ~(1 << irq);
} else {
for (i = IPL_NONE; i < minipl; i++)
macintr_ienable_h[i] |= (1 << (irq-32));
for (; i <= IPL_HIGH; i++)
macintr_ienable_h[i] &= ~(1 << (irq-32));
}
}
#if 0
for (i = 0; i < IPL_NUM; i++)
printf("imask[%d] %x %x\n", i, macintr_ienable_l[i],
macintr_ienable_h[i]);
#endif
}
void
mac_ext_intr(void)
{
int irq = 0;
int pcpl, ret;
struct cpu_info *ci = curcpu();
struct intrq *iq;
struct intrhand *ih;
pcpl = ci->ci_cpl;
irq = macintr_read_irq();
while (irq != 255) {
iq = &macintr_handler[irq];
macintr_setipl(iq->iq_ipl);
TAILQ_FOREACH(ih, &iq->iq_list, ih_list) {
ppc_intr_enable(1);
ret = ((*ih->ih_fun)(ih->ih_arg));
if (ret) {
ih->ih_count.ec_count++;
if (intr_shared_edge == 0 && ret == 1)
break;
}
(void)ppc_intr_disable();
}
macintr_eoi(irq);
macintr_setipl(pcpl);
atomic_inc_int(&uvmexp.intrs);
irq = macintr_read_irq();
}
macintr_splx(pcpl);
}
void
macintr_eoi(int irq)
{
u_int32_t state0, state1;
if (irq < 32) {
state0 = 1 << irq;
out32rb(INT_CLEAR_REG0, state0);
} else {
if (heathrow_FCR) {
state1 = 1 << (irq - 32);
out32rb(INT_CLEAR_REG1, state1);
}
}
}
int
macintr_read_irq(void)
{
struct cpu_info *ci = curcpu();
u_int32_t state0, state1, irq_mask;
int ipl, irq;
state0 = in32rb(INT_STATE_REG0);
if (heathrow_FCR)
state1 = in32rb(INT_STATE_REG1);
else
state1 = 0;
for (ipl = IPL_HIGH; ipl >= ci->ci_cpl; ipl --) {
irq_mask = state0 & macintr_ienable_l[ipl];
if (irq_mask) {
irq = ffs(irq_mask) - 1;
return irq;
}
irq_mask = state1 & macintr_ienable_h[ipl];
if (irq_mask) {
irq = ffs(irq_mask) + 31;
return irq;
}
}
return 255;
}