#include "xcall.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/queue.h>
#include <sys/malloc.h>
#include <sys/device.h>
#include <sys/evcount.h>
#include <machine/bus.h>
#include <machine/fdt.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/fdt.h>
#define INTC_PENDING_BANK0 0x00
#define INTC_PENDING_BANK1 0x04
#define INTC_PENDING_BANK2 0x08
#define INTC_FIQ_CONTROL 0x0C
#define INTC_ENABLE_BANK1 0x10
#define INTC_ENABLE_BANK2 0x14
#define INTC_ENABLE_BANK0 0x18
#define INTC_DISABLE_BANK1 0x1C
#define INTC_DISABLE_BANK2 0x20
#define INTC_DISABLE_BANK0 0x24
#define ARM_LOCAL_CONTROL 0x00
#define ARM_LOCAL_PRESCALER 0x08
#define PRESCALER_19_2 0x80000000
#define ARM_LOCAL_INT_TIMER(n) (0x40 + (n) * 4)
#define ARM_LOCAL_INT_MAILBOX(n) (0x50 + (n) * 4)
#define ARM_LOCAL_INT_PENDING(n) (0x60 + (n) * 4)
#define ARM_LOCAL_INT_PENDING_MASK 0x0f
#define ARM_LOCAL_INT_MAILBOX_SET(n) (0x80 + (n) * 16)
#define ARM_LOCAL_INT_MAILBOX_CLR(n) (0xc0 + (n) * 16)
#define BANK0_START 64
#define BANK0_END (BANK0_START + 32 - 1)
#define BANK1_START 0
#define BANK1_END (BANK1_START + 32 - 1)
#define BANK2_START 32
#define BANK2_END (BANK2_START + 32 - 1)
#define LOCAL_START 96
#define LOCAL_END (LOCAL_START + 32 - 1)
#define IS_IRQ_BANK0(n) (((n) >= BANK0_START) && ((n) <= BANK0_END))
#define IS_IRQ_BANK1(n) (((n) >= BANK1_START) && ((n) <= BANK1_END))
#define IS_IRQ_BANK2(n) (((n) >= BANK2_START) && ((n) <= BANK2_END))
#define IS_IRQ_LOCAL(n) (((n) >= LOCAL_START) && ((n) <= LOCAL_END))
#define IRQ_BANK0(n) ((n) - BANK0_START)
#define IRQ_BANK1(n) ((n) - BANK1_START)
#define IRQ_BANK2(n) ((n) - BANK2_START)
#define IRQ_LOCAL(n) ((n) - LOCAL_START)
#define ARM_LOCAL_IRQ_MAILBOX(n) (4 + (n))
#define INTC_NIRQ 128
#define INTC_NBANK 4
#define INTC_IRQ_TO_REG(i) (((i) >> 5) & 0x3)
#define INTC_IRQ_TO_REGi(i) ((i) & 0x1f)
struct intrhand {
TAILQ_ENTRY(intrhand) ih_list;
int (*ih_func)(void *);
void *ih_arg;
int ih_ipl;
int ih_flags;
int ih_irq;
struct evcount ih_count;
char *ih_name;
};
struct intrsource {
TAILQ_HEAD(, intrhand) is_list;
int is_irq;
};
struct bcm_intc_softc {
struct device sc_dev;
struct intrsource sc_handler[INTC_NIRQ];
uint32_t sc_imask[INTC_NBANK][NIPL];
int32_t sc_localcoremask[MAXCPUS];
bus_space_tag_t sc_iot;
bus_space_handle_t sc_ioh;
bus_space_handle_t sc_lioh;
struct interrupt_controller sc_intc;
struct interrupt_controller sc_l1_intc;
};
struct bcm_intc_softc *bcm_intc;
int bcm_intc_match(struct device *, void *, void *);
void bcm_intc_attach(struct device *, struct device *, void *);
void bcm_intc_splx(int new);
int bcm_intc_spllower(int new);
int bcm_intc_splraise(int new);
void bcm_intc_setipl(int new);
void bcm_intc_calc_mask(void);
void *bcm_intc_intr_establish(int, int, struct cpu_info *,
int (*)(void *), void *, char *);
void *bcm_intc_intr_establish_fdt(void *, int *, int, struct cpu_info *,
int (*)(void *), void *, char *);
void *l1_intc_intr_establish_fdt(void *, int *, int, struct cpu_info *,
int (*)(void *), void *, char *);
void bcm_intc_intr_disestablish(void *);
void bcm_intc_irq_handler(void *);
void bcm_intc_intr_route(void *, int , struct cpu_info *);
#ifdef MULTIPROCESSOR
void bcm_intc_handle_ipi(void);
void bcm_intc_send_ipi(struct cpu_info *, int);
#endif
const struct cfattach bcmintc_ca = {
sizeof (struct bcm_intc_softc), bcm_intc_match, bcm_intc_attach
};
struct cfdriver bcmintc_cd = {
NULL, "bcmintc", DV_DULL
};
int
bcm_intc_match(struct device *parent, void *cfdata, void *aux)
{
struct fdt_attach_args *faa = aux;
if (OF_is_compatible(faa->fa_node, "brcm,bcm2836-armctrl-ic"))
return 1;
return 0;
}
void
bcm_intc_attach(struct device *parent, struct device *self, void *aux)
{
struct bcm_intc_softc *sc = (struct bcm_intc_softc *)self;
struct fdt_attach_args *faa = aux;
uint32_t phandle, reg[2];
int node;
int i;
if (faa->fa_nreg < 1)
return;
bcm_intc = sc;
sc->sc_iot = faa->fa_iot;
if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
faa->fa_reg[0].size, 0, &sc->sc_ioh))
panic("%s: bus_space_map failed!", __func__);
phandle = OF_getpropint(faa->fa_node, "interrupt-parent", 0);
node = OF_getnodebyphandle(phandle);
if (node == 0)
panic("%s: can't find ARM control logic", __func__);
if (OF_getpropintarray(node, "reg", reg, sizeof(reg)) != sizeof(reg))
panic("%s: can't map ARM control logic", __func__);
if (bus_space_map(sc->sc_iot, reg[0], reg[1], 0, &sc->sc_lioh))
panic("%s: bus_space_map failed!", __func__);
printf("\n");
bus_space_write_4(sc->sc_iot, sc->sc_ioh, INTC_DISABLE_BANK0,
0xffffffff);
bus_space_write_4(sc->sc_iot, sc->sc_ioh, INTC_DISABLE_BANK1,
0xffffffff);
bus_space_write_4(sc->sc_iot, sc->sc_ioh, INTC_DISABLE_BANK2,
0xffffffff);
bus_space_write_4(sc->sc_iot, sc->sc_lioh, ARM_LOCAL_CONTROL, 0);
bus_space_write_4(sc->sc_iot, sc->sc_lioh, ARM_LOCAL_PRESCALER,
PRESCALER_19_2);
for (i = 0; i < 4; i++)
bus_space_write_4(sc->sc_iot, sc->sc_lioh,
ARM_LOCAL_INT_TIMER(i), 0);
for (i = 0; i < 4; i++)
bus_space_write_4(sc->sc_iot, sc->sc_lioh,
ARM_LOCAL_INT_MAILBOX(i), 0);
for (i = 0; i < INTC_NIRQ; i++) {
TAILQ_INIT(&sc->sc_handler[i].is_list);
}
bcm_intc_calc_mask();
arm_set_intr_handler(bcm_intc_splraise, bcm_intc_spllower,
bcm_intc_splx, bcm_intc_setipl, bcm_intc_irq_handler, NULL,
NULL, NULL);
sc->sc_intc.ic_node = faa->fa_node;
sc->sc_intc.ic_cookie = sc;
sc->sc_intc.ic_establish = bcm_intc_intr_establish_fdt;
sc->sc_intc.ic_disestablish = bcm_intc_intr_disestablish;
sc->sc_intc.ic_route = bcm_intc_intr_route;
arm_intr_register_fdt(&sc->sc_intc);
sc->sc_l1_intc.ic_node = node;
sc->sc_l1_intc.ic_cookie = sc;
sc->sc_l1_intc.ic_establish = l1_intc_intr_establish_fdt;
sc->sc_l1_intc.ic_disestablish = bcm_intc_intr_disestablish;
sc->sc_l1_intc.ic_route = bcm_intc_intr_route;
arm_intr_register_fdt(&sc->sc_l1_intc);
#ifdef MULTIPROCESSOR
intr_send_ipi_func = bcm_intc_send_ipi;
#endif
bcm_intc_setipl(IPL_HIGH);
intr_enable();
}
void
bcm_intc_intr_enable(int irq, int ipl)
{
struct bcm_intc_softc *sc = bcm_intc;
if (IS_IRQ_BANK0(irq))
sc->sc_imask[0][ipl] |= (1 << IRQ_BANK0(irq));
else if (IS_IRQ_BANK1(irq))
sc->sc_imask[1][ipl] |= (1 << IRQ_BANK1(irq));
else if (IS_IRQ_BANK2(irq))
sc->sc_imask[2][ipl] |= (1 << IRQ_BANK2(irq));
else if (IS_IRQ_LOCAL(irq))
sc->sc_imask[3][ipl] |= (1 << IRQ_LOCAL(irq));
else
printf("%s: invalid irq number: %d\n", __func__, irq);
}
void
bcm_intc_intr_disable(int irq, int ipl)
{
struct bcm_intc_softc *sc = bcm_intc;
if (IS_IRQ_BANK0(irq))
sc->sc_imask[0][ipl] &= ~(1 << IRQ_BANK0(irq));
else if (IS_IRQ_BANK1(irq))
sc->sc_imask[1][ipl] &= ~(1 << IRQ_BANK1(irq));
else if (IS_IRQ_BANK2(irq))
sc->sc_imask[2][ipl] &= ~(1 << IRQ_BANK2(irq));
else if (IS_IRQ_LOCAL(irq))
sc->sc_imask[3][ipl] &= ~(1 << IRQ_LOCAL(irq));
else
printf("%s: invalid irq number: %d\n", __func__, irq);
}
void
bcm_intc_calc_mask(void)
{
struct cpu_info *ci = curcpu();
struct bcm_intc_softc *sc = bcm_intc;
int irq;
struct intrhand *ih;
int i;
for (irq = 0; irq < INTC_NIRQ; irq++) {
int max = IPL_NONE;
int min = IPL_HIGH;
TAILQ_FOREACH(ih, &sc->sc_handler[irq].is_list, ih_list) {
if (ih->ih_ipl > max)
max = ih->ih_ipl;
if (ih->ih_ipl < min)
min = ih->ih_ipl;
}
sc->sc_handler[irq].is_irq = max;
if (max == IPL_NONE)
min = IPL_NONE;
#ifdef DEBUG_INTC
if (min != IPL_NONE) {
printf("irq %d to block at %d %d reg %d bit %d\n",
irq, max, min, INTC_IRQ_TO_REG(irq),
INTC_IRQ_TO_REGi(irq));
}
#endif
for (i = 0; i < min; i++)
bcm_intc_intr_enable(irq, i);
for (; i <= IPL_HIGH; i++)
bcm_intc_intr_disable(irq, i);
}
arm_init_smask();
bcm_intc_setipl(ci->ci_cpl);
}
void
bcm_intc_splx(int new)
{
struct cpu_info *ci = curcpu();
if (ci->ci_ipending & arm_smask[new])
arm_do_pending_intr(new);
bcm_intc_setipl(new);
}
int
bcm_intc_spllower(int new)
{
struct cpu_info *ci = curcpu();
int old = ci->ci_cpl;
bcm_intc_splx(new);
return (old);
}
int
bcm_intc_splraise(int new)
{
struct cpu_info *ci = curcpu();
int old;
old = ci->ci_cpl;
if (old > new)
new = old;
bcm_intc_setipl(new);
return (old);
}
void
bcm_intc_setipl(int new)
{
struct cpu_info *ci = curcpu();
struct bcm_intc_softc *sc = bcm_intc;
u_long psw;
psw = intr_disable();
ci->ci_cpl = new;
if (cpu_number() == 0) {
bus_space_write_4(sc->sc_iot, sc->sc_ioh, INTC_DISABLE_BANK0,
0xffffffff);
bus_space_write_4(sc->sc_iot, sc->sc_ioh, INTC_DISABLE_BANK1,
0xffffffff);
bus_space_write_4(sc->sc_iot, sc->sc_ioh, INTC_DISABLE_BANK2,
0xffffffff);
bus_space_write_4(sc->sc_iot, sc->sc_ioh, INTC_ENABLE_BANK0,
sc->sc_imask[0][new]);
bus_space_write_4(sc->sc_iot, sc->sc_ioh, INTC_ENABLE_BANK1,
sc->sc_imask[1][new]);
bus_space_write_4(sc->sc_iot, sc->sc_ioh, INTC_ENABLE_BANK2,
sc->sc_imask[2][new]);
}
bus_space_write_4(sc->sc_iot, sc->sc_lioh,
ARM_LOCAL_INT_TIMER(cpu_number()),
sc->sc_imask[3][ci->ci_cpl] &
sc->sc_localcoremask[cpu_number()]);
intr_restore(psw);
}
int
bcm_intc_get_next_irq(int last_irq)
{
struct bcm_intc_softc *sc = bcm_intc;
uint32_t pending;
int32_t irq = last_irq + 1;
if (irq < 0)
irq = 0;
if (IS_IRQ_BANK1(irq)) {
pending = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
INTC_PENDING_BANK1);
if (pending == 0) {
irq = BANK2_START;
} else do {
if (pending & (1 << IRQ_BANK1(irq)))
return irq;
irq++;
} while (IS_IRQ_BANK1(irq));
}
if (IS_IRQ_BANK2(irq)) {
pending = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
INTC_PENDING_BANK2);
if (pending == 0) {
irq = BANK0_START;
} else do {
if (pending & (1 << IRQ_BANK2(irq)))
return irq;
irq++;
} while (IS_IRQ_BANK2(irq));
}
if (IS_IRQ_BANK0(irq)) {
pending = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
INTC_PENDING_BANK0);
if (pending == 0) {
irq = LOCAL_START;
} else do {
if (pending & (1 << IRQ_BANK0(irq)))
return irq;
irq++;
} while (IS_IRQ_BANK0(irq));
}
if (IS_IRQ_LOCAL(irq)) {
pending = bus_space_read_4(sc->sc_iot, sc->sc_lioh,
ARM_LOCAL_INT_PENDING(cpu_number()));
pending &= ARM_LOCAL_INT_PENDING_MASK;
if (pending != 0) do {
if (pending & (1 << IRQ_LOCAL(irq)))
return irq;
irq++;
} while (IS_IRQ_LOCAL(irq));
}
return (-1);
}
void
bcm_intc_run_handler(struct intrhand *ih, void *frame, int s)
{
int handled;
void *arg;
#ifdef MULTIPROCESSOR
int need_lock;
if (ih->ih_flags & IPL_MPSAFE)
need_lock = 0;
else
need_lock = s < IPL_SCHED;
if (need_lock)
KERNEL_LOCK();
#endif
if (ih->ih_arg)
arg = ih->ih_arg;
else
arg = frame;
handled = ih->ih_func(arg);
if (handled)
ih->ih_count.ec_count++;
#ifdef MULTIPROCESSOR
if (need_lock)
KERNEL_UNLOCK();
#endif
}
void
bcm_intc_irq_handler(void *frame)
{
struct bcm_intc_softc *sc = bcm_intc;
struct intrhand *ih;
int irq, pri, s;
irq = (cpu_number() == 0 ? 0 : LOCAL_START) - 1;
while ((irq = bcm_intc_get_next_irq(irq)) != -1) {
#ifdef MULTIPROCESSOR
if (irq == ARM_LOCAL_IRQ_MAILBOX(cpu_number())) {
bcm_intc_handle_ipi();
continue;
}
#endif
pri = sc->sc_handler[irq].is_irq;
s = bcm_intc_splraise(pri);
TAILQ_FOREACH(ih, &sc->sc_handler[irq].is_list, ih_list) {
intr_enable();
bcm_intc_run_handler(ih, frame, s);
intr_disable();
}
bcm_intc_splx(s);
}
}
void *
bcm_intc_intr_establish_fdt(void *cookie, int *cell, int level,
struct cpu_info *ci, int (*func)(void *), void *arg, char *name)
{
struct bcm_intc_softc *sc = (struct bcm_intc_softc *)cookie;
int irq;
irq = cell[1];
if (cell[0] == 0)
irq += BANK0_START;
else if (cell[0] == 1)
irq += BANK1_START;
else if (cell[0] == 2)
irq += BANK2_START;
else if (cell[0] == 3)
irq += LOCAL_START;
else
panic("%s: bogus interrupt type", sc->sc_dev.dv_xname);
return bcm_intc_intr_establish(irq, level, ci, func, arg, name);
}
void *
l1_intc_intr_establish_fdt(void *cookie, int *cell, int level,
struct cpu_info *ci, int (*func)(void *), void *arg, char *name)
{
int irq;
irq = cell[0] + LOCAL_START;
return bcm_intc_intr_establish(irq, level, ci, func, arg, name);
}
void *
bcm_intc_intr_establish(int irqno, int level, struct cpu_info *ci,
int (*func)(void *), void *arg, char *name)
{
struct bcm_intc_softc *sc = bcm_intc;
struct intrhand *ih;
u_long psw;
if (irqno < 0 || irqno >= INTC_NIRQ)
panic("bcm_intc_intr_establish: bogus irqnumber %d: %s",
irqno, name);
if (ci != NULL && !CPU_IS_PRIMARY(ci))
return NULL;
psw = intr_disable();
ih = malloc(sizeof *ih, M_DEVBUF, M_WAITOK);
ih->ih_func = func;
ih->ih_arg = arg;
ih->ih_ipl = level & IPL_IRQMASK;
ih->ih_flags = level & IPL_FLAGMASK;
ih->ih_irq = irqno;
ih->ih_name = name;
if (IS_IRQ_LOCAL(irqno))
sc->sc_localcoremask[0] |= (1 << IRQ_LOCAL(irqno));
TAILQ_INSERT_TAIL(&sc->sc_handler[irqno].is_list, ih, ih_list);
if (name != NULL)
evcount_attach(&ih->ih_count, name, &ih->ih_irq);
#ifdef DEBUG_INTC
printf("%s irq %d level %d [%s]\n", __func__, irqno, level,
name);
#endif
bcm_intc_calc_mask();
intr_restore(psw);
return (ih);
}
void
bcm_intc_intr_disestablish(void *cookie)
{
struct bcm_intc_softc *sc = bcm_intc;
struct intrhand *ih = cookie;
int irqno = ih->ih_irq;
u_long psw;
psw = intr_disable();
TAILQ_REMOVE(&sc->sc_handler[irqno].is_list, ih, ih_list);
if (ih->ih_name != NULL)
evcount_detach(&ih->ih_count);
intr_restore(psw);
free(ih, M_DEVBUF, 0);
}
void
bcm_intc_intr_route(void *cookie, int enable, struct cpu_info *ci)
{
struct bcm_intc_softc *sc = bcm_intc;
struct intrhand *ih = cookie;
int lirq = IRQ_LOCAL(ih->ih_irq);
if (enable)
sc->sc_localcoremask[ci->ci_cpuid] |= (1 << lirq);
else
sc->sc_localcoremask[ci->ci_cpuid] &= ~(1 << lirq);
if (ci == curcpu()) {
bus_space_write_4(sc->sc_iot, sc->sc_lioh,
ARM_LOCAL_INT_TIMER(cpu_number()),
sc->sc_imask[3][ci->ci_cpl] &
sc->sc_localcoremask[cpu_number()]);
#ifdef MULTIPROCESSOR
bus_space_write_4(sc->sc_iot, sc->sc_lioh,
ARM_LOCAL_INT_MAILBOX(cpu_number()),
sc->sc_imask[3][ci->ci_cpl] &
sc->sc_localcoremask[cpu_number()]);
#endif
}
}
#ifdef MULTIPROCESSOR
void
bcm_intc_handle_ipi(void)
{
struct bcm_intc_softc *sc = bcm_intc;
int cpuno = cpu_number();
uint32_t mbox_val;
mbox_val = bus_space_read_4(sc->sc_iot, sc->sc_lioh,
ARM_LOCAL_INT_MAILBOX_CLR(cpuno));
bus_space_write_4(sc->sc_iot, sc->sc_lioh,
ARM_LOCAL_INT_MAILBOX_CLR(cpuno), mbox_val);
#ifdef DDB
if (ISSET(mbox_val, 1 << ARM_IPI_DDB)) {
db_enter();
}
#endif
#if NXCALL > 0
if (ISSET(mbox_val, 1 << ARM_IPI_XCALL))
arm_cpu_xcall_dispatch();
#endif
}
void
bcm_intc_send_ipi(struct cpu_info *ci, int id)
{
struct bcm_intc_softc *sc = bcm_intc;
__asm volatile("dsb sy");
bus_space_write_4(sc->sc_iot, sc->sc_lioh,
ARM_LOCAL_INT_MAILBOX_SET(ci->ci_cpuid), 1 << id);
}
#endif