/* $OpenBSD: sxiintc.c,v 1.6 2019/10/05 15:44:57 kettenis Exp $ */ /* * Copyright (c) 2007,2009 Dale Rahn * Copyright (c) 2013 Artturi Alm * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DEBUG_INTC #define DPRINTF(x) do { if (sxiintcdebug) printf x; } while (0) #define DPRINTFN(n,x) do { if (sxiintcdebug>(n)) printf x; } while (0) int sxiintcdebug = 10; char *ipl_strtbl[NIPL] = { "IPL_NONE", "IPL_SOFT", "IPL_SOFTCLOCK", "IPL_SOFTNET", "IPL_SOFTTTY", "IPL_BIO|IPL_USB", "IPL_NET", "IPL_TTY", "IPL_VM", "IPL_AUDIO", "IPL_CLOCK", "IPL_STATCLOCK", "IPL_SCHED|IPL_HIGH" }; #else #define DPRINTF(x) #define DPRINTFN(n,x) #endif #define NIRQ 96 #define NBANKS 3 #define NIRQPRIOREGS 5 /* registers */ #define INTC_VECTOR_REG 0x00 #define INTC_BASE_ADR_REG 0x04 #define INTC_PROTECTION_REG 0x08 #define INTC_NMI_CTRL_REG 0x0c #define INTC_IRQ_PENDING_REG0 0x10 #define INTC_IRQ_PENDING_REG1 0x14 #define INTC_IRQ_PENDING_REG2 0x18 #define INTC_SELECT_REG0 0x30 #define INTC_SELECT_REG1 0x34 #define INTC_SELECT_REG2 0x38 #define INTC_ENABLE_REG0 0x40 #define INTC_ENABLE_REG1 0x44 #define INTC_ENABLE_REG2 0x48 #define INTC_MASK_REG0 0x50 #define INTC_MASK_REG1 0x54 #define INTC_MASK_REG2 0x58 #define INTC_RESP_REG0 0x60 #define INTC_RESP_REG1 0x64 #define INTC_RESP_REG2 0x68 #define INTC_PRIO_REG0 0x80 #define INTC_PRIO_REG1 0x84 #define INTC_PRIO_REG2 0x88 #define INTC_PRIO_REG3 0x8c #define INTC_PRIO_REG4 0x90 #define INTC_IRQ_PENDING_REG(_b) (0x10 + ((_b) * 4)) #define INTC_FIQ_PENDING_REG(_b) (0x20 + ((_b) * 4)) #define INTC_SELECT_REG(_b) (0x30 + ((_b) * 4)) #define INTC_ENABLE_REG(_b) (0x40 + ((_b) * 4)) #define INTC_MASK_REG(_b) (0x50 + ((_b) * 4)) #define INTC_RESP_REG(_b) (0x60 + ((_b) * 4)) #define INTC_PRIO_REG(_b) (0x80 + ((_b) * 4)) #define IRQ2REG32(i) (((i) >> 5) & 0x3) #define IRQ2BIT32(i) ((i) & 0x1f) #define IRQ2REG16(i) (((i) >> 4) & 0x5) #define IRQ2BIT16(i) (((i) & 0x0f) * 2) #define INTC_IRQ_HIPRIO 0x3 #define INTC_IRQ_ENABLED 0x2 #define INTC_IRQ_DISABLED 0x1 #define INTC_IRQ_LOWPRIO 0x0 #define INTC_PRIOCLEAR(i) (~(INTC_IRQ_HIPRIO << IRQ2BIT16((i)))) #define INTC_PRIOENABLE(i) (INTC_IRQ_ENABLED << IRQ2BIT16((i))) #define INTC_PRIOHI(i) (INTC_IRQ_HIPRIO << IRQ2BIT16((i))) struct intrhand { TAILQ_ENTRY(intrhand) ih_list; /* link on intrq list */ int (*ih_func)(void *); /* handler */ void *ih_arg; /* arg for handler */ int ih_ipl; /* IPL_* */ int ih_irq; /* IRQ number */ struct evcount ih_count; char *ih_name; }; struct intrq { TAILQ_HEAD(, intrhand) iq_list; /* handler list */ int iq_irq; /* IRQ to mask while handling */ int iq_levels; /* IPL_*'s this IRQ has */ int iq_ist; /* share type */ }; volatile int a1xsoftint_pending; struct intrq sxiintc_handler[NIRQ]; u_int32_t sxiintc_smask[NIPL]; u_int32_t sxiintc_imask[NBANKS][NIPL]; struct interrupt_controller sxiintc_ic; bus_space_tag_t sxiintc_iot; bus_space_handle_t sxiintc_ioh; int sxiintc_nirq; int sxiintc_match(struct device *, void *, void *); void sxiintc_attach(struct device *, struct device *, void *); int sxiintc_spllower(int); int sxiintc_splraise(int); void sxiintc_setipl(int); void sxiintc_calc_masks(void); void *sxiintc_intr_establish_fdt(void *, int *, int, int (*)(void *), void *, char *); struct cfattach sxiintc_ca = { sizeof (struct device), sxiintc_match, sxiintc_attach }; struct cfdriver sxiintc_cd = { NULL, "sxiintc", DV_DULL }; int sxiintc_attached = 0; int sxiintc_match(struct device *parent, void *match, void *aux) { struct fdt_attach_args *faa = aux; return OF_is_compatible(faa->fa_node, "allwinner,sun4i-a10-ic"); } void sxiintc_attach(struct device *parent, struct device *self, void *aux) { struct fdt_attach_args *faa = aux; int i, j; sxiintc_iot = faa->fa_iot; if (bus_space_map(sxiintc_iot, faa->fa_reg[0].addr, faa->fa_reg[0].size, 0, &sxiintc_ioh)) panic("sxiintc_attach: bus_space_map failed!"); /* disable/mask/clear all interrupts */ for (i = 0; i < NBANKS; i++) { bus_space_write_4(sxiintc_iot, sxiintc_ioh, INTC_ENABLE_REG(i), 0); bus_space_write_4(sxiintc_iot, sxiintc_ioh, INTC_MASK_REG(i), 0); bus_space_write_4(sxiintc_iot, sxiintc_ioh, INTC_IRQ_PENDING_REG(i), 0xffffffff); for (j = 0; j < NIPL; j++) sxiintc_imask[i][j] = 0; } /* XXX */ bus_space_write_4(sxiintc_iot, sxiintc_ioh, INTC_PROTECTION_REG, 1); bus_space_write_4(sxiintc_iot, sxiintc_ioh, INTC_NMI_CTRL_REG, 0); for (i = 0; i < NIRQ; i++) TAILQ_INIT(&sxiintc_handler[i].iq_list); sxiintc_calc_masks(); arm_init_smask(); sxiintc_attached = 1; /* insert self as interrupt handler */ arm_set_intr_handler(sxiintc_splraise, sxiintc_spllower, sxiintc_splx, sxiintc_setipl, sxiintc_intr_establish, sxiintc_intr_disestablish, sxiintc_intr_string, sxiintc_irq_handler); sxiintc_setipl(IPL_HIGH); /* XXX ??? */ enable_interrupts(PSR_I); printf("\n"); sxiintc_ic.ic_node = faa->fa_node; sxiintc_ic.ic_establish = sxiintc_intr_establish_fdt; arm_intr_register_fdt(&sxiintc_ic); } void sxiintc_calc_masks(void) { struct cpu_info *ci = curcpu(); int irq; struct intrhand *ih; int i; for (irq = 0; irq < NIRQ; irq++) { int max = IPL_NONE; int min = IPL_HIGH; TAILQ_FOREACH(ih, &sxiintc_handler[irq].iq_list, ih_list) { if (ih->ih_ipl > max) max = ih->ih_ipl; if (ih->ih_ipl < min) min = ih->ih_ipl; } sxiintc_handler[irq].iq_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, IRQ2REG32(irq), IRQ2BIT32(irq)); } #endif /* Enable interrupts at lower levels, clear -> enable */ for (i = 0; i < min; i++) sxiintc_imask[IRQ2REG32(irq)][i] &= ~(1 << IRQ2BIT32(irq)); for (; i < NIPL; i++) sxiintc_imask[IRQ2REG32(irq)][i] |= (1 << IRQ2BIT32(irq)); /* XXX - set enable/disable, priority */ } sxiintc_setipl(ci->ci_cpl); } void sxiintc_splx(int new) { struct cpu_info *ci = curcpu(); sxiintc_setipl(new); if (ci->ci_ipending & arm_smask[ci->ci_cpl]) arm_do_pending_intr(ci->ci_cpl); } int sxiintc_spllower(int new) { struct cpu_info *ci = curcpu(); int old = ci->ci_cpl; sxiintc_splx(new); return (old); } int sxiintc_splraise(int new) { struct cpu_info *ci = curcpu(); int old; old = ci->ci_cpl; /* * setipl must always be called because there is a race window * where the variable is updated before the mask is set * an interrupt occurs in that window without the mask always * being set, the hardware might not get updated on the next * splraise completely messing up spl protection. */ if (old > new) new = old; sxiintc_setipl(new); return (old); } void sxiintc_setipl(int new) { struct cpu_info *ci = curcpu(); int i, psw; #if 1 /* * XXX not needed, because all interrupts are disabled * by default, so touching maskregs has no effect, i hope. */ if (sxiintc_attached == 0) { ci->ci_cpl = new; return; } #endif psw = disable_interrupts(PSR_I); ci->ci_cpl = new; for (i = 0; i < NBANKS; i++) bus_space_write_4(sxiintc_iot, sxiintc_ioh, INTC_MASK_REG(i), sxiintc_imask[i][new]); restore_interrupts(psw); } void sxiintc_irq_handler(void *frame) { struct intrhand *ih; void *arg; uint32_t pr; int irq, prio, s; irq = bus_space_read_4(sxiintc_iot, sxiintc_ioh, INTC_VECTOR_REG) >> 2; if (irq == 0) return; prio = sxiintc_handler[irq].iq_irq; s = sxiintc_splraise(prio); splassert(prio); pr = bus_space_read_4(sxiintc_iot, sxiintc_ioh, INTC_ENABLE_REG(IRQ2REG32(irq))); bus_space_write_4(sxiintc_iot, sxiintc_ioh, INTC_ENABLE_REG(IRQ2REG32(irq)), pr & ~(1 << IRQ2BIT32(irq))); /* clear pending */ pr = bus_space_read_4(sxiintc_iot, sxiintc_ioh, INTC_IRQ_PENDING_REG(IRQ2REG32(irq))); bus_space_write_4(sxiintc_iot, sxiintc_ioh, INTC_IRQ_PENDING_REG(IRQ2REG32(irq)), pr | (1 << IRQ2BIT32(irq))); pr = bus_space_read_4(sxiintc_iot, sxiintc_ioh, INTC_ENABLE_REG(IRQ2REG32(irq))); bus_space_write_4(sxiintc_iot, sxiintc_ioh, INTC_ENABLE_REG(IRQ2REG32(irq)), pr | (1 << IRQ2BIT32(irq))); TAILQ_FOREACH(ih, &sxiintc_handler[irq].iq_list, ih_list) { if (ih->ih_arg != 0) arg = ih->ih_arg; else arg = frame; if (ih->ih_func(arg)) ih->ih_count.ec_count++; } sxiintc_splx(s); } void * sxiintc_intr_establish(int irq, int level, int (*func)(void *), void *arg, char *name) { int psw; struct intrhand *ih; uint32_t er; if (irq <= 0 || irq >= NIRQ) panic("intr_establish: bogus irq %d %s\n", irq, name); DPRINTF(("intr_establish: irq %d level %d [%s]\n", irq, level, name != NULL ? name : "NULL")); psw = disable_interrupts(PSR_I); ih = malloc(sizeof(*ih), M_DEVBUF, M_WAITOK); ih->ih_func = func; ih->ih_arg = arg; ih->ih_ipl = level & IPL_IRQMASK; ih->ih_irq = irq; ih->ih_name = name; TAILQ_INSERT_TAIL(&sxiintc_handler[irq].iq_list, ih, ih_list); if (name != NULL) evcount_attach(&ih->ih_count, name, &ih->ih_irq); er = bus_space_read_4(sxiintc_iot, sxiintc_ioh, INTC_ENABLE_REG(IRQ2REG32(irq))); bus_space_write_4(sxiintc_iot, sxiintc_ioh, INTC_ENABLE_REG(IRQ2REG32(irq)), er | (1 << IRQ2BIT32(irq))); sxiintc_calc_masks(); restore_interrupts(psw); return (ih); } void * sxiintc_intr_establish_fdt(void *cookie, int *cell, int level, int (*func)(void *), void *arg, char *name) { return sxiintc_intr_establish(cell[0], level, func, arg, name); } void sxiintc_intr_disestablish(void *cookie) { struct intrhand *ih = cookie; int irq = ih->ih_irq; int psw; uint32_t er; psw = disable_interrupts(PSR_I); TAILQ_REMOVE(&sxiintc_handler[irq].iq_list, ih, ih_list); if (ih->ih_name != NULL) evcount_detach(&ih->ih_count); free(ih, M_DEVBUF, 0); er = bus_space_read_4(sxiintc_iot, sxiintc_ioh, INTC_ENABLE_REG(IRQ2REG32(irq))); bus_space_write_4(sxiintc_iot, sxiintc_ioh, INTC_ENABLE_REG(IRQ2REG32(irq)), er & ~(1 << IRQ2BIT32(irq))); sxiintc_calc_masks(); restore_interrupts(psw); } const char * sxiintc_intr_string(void *cookie) { return "asd?"; }