summaryrefslogtreecommitdiff
path: root/sys/arch/armv7/sunxi/sxiintc.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/arch/armv7/sunxi/sxiintc.c')
-rw-r--r--sys/arch/armv7/sunxi/sxiintc.c458
1 files changed, 458 insertions, 0 deletions
diff --git a/sys/arch/armv7/sunxi/sxiintc.c b/sys/arch/armv7/sunxi/sxiintc.c
new file mode 100644
index 00000000000..452361ecd76
--- /dev/null
+++ b/sys/arch/armv7/sunxi/sxiintc.c
@@ -0,0 +1,458 @@
+/* $OpenBSD: sxiintc.c,v 1.1 2016/08/05 20:38:17 kettenis Exp $ */
+/*
+ * Copyright (c) 2007,2009 Dale Rahn <drahn@openbsd.org>
+ * 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 <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 <armv7/armv7/armv7var.h>
+#include <armv7/sunxi/sunxireg.h>
+#include <armv7/sunxi/sxiintc.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/fdt.h>
+
+#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);
+
+ /* no point in sleeping unless someone can free memory. */
+ ih = (struct intrhand *)malloc (sizeof *ih, M_DEVBUF,
+ cold ? M_NOWAIT : M_WAITOK);
+ if (ih == NULL)
+ panic("intr_establish: can't malloc handler info\n");
+ ih->ih_func = func;
+ ih->ih_arg = arg;
+ ih->ih_ipl = level;
+ 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?";
+}