sxiintc.c revision 1.10
1/*	$OpenBSD: sxiintc.c,v 1.10 2021/10/24 17:52:28 mpi Exp $	*/
2/*
3 * Copyright (c) 2007,2009 Dale Rahn <drahn@openbsd.org>
4 * Copyright (c) 2013 Artturi Alm
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/param.h>
20#include <sys/systm.h>
21#include <sys/queue.h>
22#include <sys/malloc.h>
23#include <sys/device.h>
24#include <sys/evcount.h>
25
26#include <machine/bus.h>
27#include <machine/fdt.h>
28
29#include <armv7/sunxi/sxiintc.h>
30
31#include <dev/ofw/openfirm.h>
32#include <dev/ofw/fdt.h>
33
34#ifdef DEBUG_INTC
35#define DPRINTF(x)	do { if (sxiintcdebug) printf x; } while (0)
36#define DPRINTFN(n,x)	do { if (sxiintcdebug>(n)) printf x; } while (0)
37int	sxiintcdebug = 10;
38char *ipl_strtbl[NIPL] = {
39	"IPL_NONE",
40	"IPL_SOFT",
41	"IPL_SOFTCLOCK",
42	"IPL_SOFTNET",
43	"IPL_SOFTTTY",
44	"IPL_BIO|IPL_USB",
45	"IPL_NET",
46	"IPL_TTY",
47	"IPL_VM",
48	"IPL_AUDIO",
49	"IPL_CLOCK",
50	"IPL_STATCLOCK",
51	"IPL_SCHED|IPL_HIGH"
52};
53#else
54#define DPRINTF(x)
55#define DPRINTFN(n,x)
56#endif
57
58#define NIRQ			96
59#define NBANKS			3
60#define NIRQPRIOREGS		5
61
62/* registers */
63#define INTC_VECTOR_REG		0x00
64#define INTC_BASE_ADR_REG	0x04
65#define INTC_PROTECTION_REG	0x08
66#define INTC_NMI_CTRL_REG	0x0c
67
68#define INTC_IRQ_PENDING_REG0	0x10
69#define INTC_IRQ_PENDING_REG1	0x14
70#define INTC_IRQ_PENDING_REG2	0x18
71
72#define INTC_SELECT_REG0	0x30
73#define INTC_SELECT_REG1	0x34
74#define INTC_SELECT_REG2	0x38
75
76#define INTC_ENABLE_REG0	0x40
77#define INTC_ENABLE_REG1	0x44
78#define INTC_ENABLE_REG2	0x48
79
80#define INTC_MASK_REG0		0x50
81#define INTC_MASK_REG1		0x54
82#define INTC_MASK_REG2		0x58
83
84#define INTC_RESP_REG0		0x60
85#define INTC_RESP_REG1		0x64
86#define INTC_RESP_REG2		0x68
87
88#define INTC_PRIO_REG0		0x80
89#define INTC_PRIO_REG1		0x84
90#define INTC_PRIO_REG2		0x88
91#define INTC_PRIO_REG3		0x8c
92#define INTC_PRIO_REG4		0x90
93
94#define INTC_IRQ_PENDING_REG(_b)	(0x10 + ((_b) * 4))
95#define INTC_FIQ_PENDING_REG(_b)	(0x20 + ((_b) * 4))
96#define INTC_SELECT_REG(_b)		(0x30 + ((_b) * 4))
97#define INTC_ENABLE_REG(_b)		(0x40 + ((_b) * 4))
98#define INTC_MASK_REG(_b)		(0x50 + ((_b) * 4))
99#define INTC_RESP_REG(_b)		(0x60 + ((_b) * 4))
100#define INTC_PRIO_REG(_b)		(0x80 + ((_b) * 4))
101
102#define IRQ2REG32(i)		(((i) >> 5) & 0x3)
103#define IRQ2BIT32(i)		((i) & 0x1f)
104
105#define IRQ2REG16(i)		(((i) >> 4) & 0x5)
106#define IRQ2BIT16(i)		(((i) & 0x0f) * 2)
107
108#define INTC_IRQ_HIPRIO		0x3
109#define INTC_IRQ_ENABLED	0x2
110#define INTC_IRQ_DISABLED	0x1
111#define INTC_IRQ_LOWPRIO	0x0
112#define INTC_PRIOCLEAR(i)	(~(INTC_IRQ_HIPRIO << IRQ2BIT16((i))))
113#define INTC_PRIOENABLE(i)	(INTC_IRQ_ENABLED << IRQ2BIT16((i)))
114#define INTC_PRIOHI(i)		(INTC_IRQ_HIPRIO << IRQ2BIT16((i)))
115
116
117struct intrhand {
118	TAILQ_ENTRY(intrhand) ih_list;	/* link on intrq list */
119	int (*ih_func)(void *);		/* handler */
120	void *ih_arg;			/* arg for handler */
121	int ih_ipl;			/* IPL_* */
122	int ih_irq;			/* IRQ number */
123	struct evcount	ih_count;
124	char *ih_name;
125};
126
127struct intrq {
128	TAILQ_HEAD(, intrhand) iq_list;	/* handler list */
129	int iq_irq;			/* IRQ to mask while handling */
130	int iq_levels;			/* IPL_*'s this IRQ has */
131	int iq_ist;			/* share type */
132};
133
134volatile int a1xsoftint_pending;
135
136struct intrq sxiintc_handler[NIRQ];
137u_int32_t sxiintc_smask[NIPL];
138u_int32_t sxiintc_imask[NBANKS][NIPL];
139struct interrupt_controller sxiintc_ic;
140
141bus_space_tag_t		sxiintc_iot;
142bus_space_handle_t	sxiintc_ioh;
143int			sxiintc_nirq;
144
145int	sxiintc_match(struct device *, void *, void *);
146void	sxiintc_attach(struct device *, struct device *, void *);
147int	sxiintc_spllower(int);
148int	sxiintc_splraise(int);
149void	sxiintc_setipl(int);
150void	sxiintc_calc_masks(void);
151void	*sxiintc_intr_establish_fdt(void *, int *, int, struct cpu_info *,
152	    int (*)(void *), void *, char *);
153
154const struct cfattach	sxiintc_ca = {
155	sizeof (struct device), sxiintc_match, sxiintc_attach
156};
157
158struct cfdriver sxiintc_cd = {
159	NULL, "sxiintc", DV_DULL
160};
161
162int sxiintc_attached = 0;
163
164int
165sxiintc_match(struct device *parent, void *match, void *aux)
166{
167	struct fdt_attach_args *faa = aux;
168
169	return OF_is_compatible(faa->fa_node, "allwinner,sun4i-a10-ic");
170}
171
172void
173sxiintc_attach(struct device *parent, struct device *self, void *aux)
174{
175	struct fdt_attach_args *faa = aux;
176	int i, j;
177
178	sxiintc_iot = faa->fa_iot;
179	if (bus_space_map(sxiintc_iot, faa->fa_reg[0].addr,
180	    faa->fa_reg[0].size, 0, &sxiintc_ioh))
181		panic("sxiintc_attach: bus_space_map failed!");
182
183	/* disable/mask/clear all interrupts */
184	for (i = 0; i < NBANKS; i++) {
185		bus_space_write_4(sxiintc_iot, sxiintc_ioh, INTC_ENABLE_REG(i), 0);
186		bus_space_write_4(sxiintc_iot, sxiintc_ioh, INTC_MASK_REG(i), 0);
187		bus_space_write_4(sxiintc_iot, sxiintc_ioh, INTC_IRQ_PENDING_REG(i),
188		    0xffffffff);
189		for (j = 0; j < NIPL; j++)
190			sxiintc_imask[i][j] = 0;
191	}
192
193	/* XXX */
194	bus_space_write_4(sxiintc_iot, sxiintc_ioh, INTC_PROTECTION_REG, 1);
195	bus_space_write_4(sxiintc_iot, sxiintc_ioh, INTC_NMI_CTRL_REG, 0);
196
197	for (i = 0; i < NIRQ; i++)
198		TAILQ_INIT(&sxiintc_handler[i].iq_list);
199
200	sxiintc_calc_masks();
201
202	arm_init_smask();
203	sxiintc_attached = 1;
204
205	/* insert self as interrupt handler */
206	arm_set_intr_handler(sxiintc_splraise, sxiintc_spllower, sxiintc_splx,
207	    sxiintc_setipl,
208	    sxiintc_intr_establish, sxiintc_intr_disestablish, sxiintc_intr_string,
209	    sxiintc_irq_handler);
210	sxiintc_setipl(IPL_HIGH);  /* XXX ??? */
211	enable_interrupts(PSR_I);
212	printf("\n");
213
214	sxiintc_ic.ic_node = faa->fa_node;
215	sxiintc_ic.ic_establish = sxiintc_intr_establish_fdt;
216	arm_intr_register_fdt(&sxiintc_ic);
217}
218
219void
220sxiintc_calc_masks(void)
221{
222	struct cpu_info *ci = curcpu();
223	int irq;
224	struct intrhand *ih;
225	int i;
226
227	for (irq = 0; irq < NIRQ; irq++) {
228		int max = IPL_NONE;
229		int min = IPL_HIGH;
230		TAILQ_FOREACH(ih, &sxiintc_handler[irq].iq_list, ih_list) {
231			if (ih->ih_ipl > max)
232				max = ih->ih_ipl;
233			if (ih->ih_ipl < min)
234				min = ih->ih_ipl;
235		}
236
237		sxiintc_handler[irq].iq_irq = max;
238
239		if (max == IPL_NONE)
240			min = IPL_NONE;
241
242#ifdef DEBUG_INTC
243		if (min != IPL_NONE) {
244			printf("irq %d to block at %d %d reg %d bit %d\n",
245			    irq, max, min, IRQ2REG32(irq),
246			    IRQ2BIT32(irq));
247		}
248#endif
249		/* Enable interrupts at lower levels, clear -> enable */
250		for (i = 0; i < min; i++)
251			sxiintc_imask[IRQ2REG32(irq)][i] &=
252			    ~(1 << IRQ2BIT32(irq));
253		for (; i < NIPL; i++)
254			sxiintc_imask[IRQ2REG32(irq)][i] |=
255			    (1 << IRQ2BIT32(irq));
256		/* XXX - set enable/disable, priority */
257	}
258
259	sxiintc_setipl(ci->ci_cpl);
260}
261
262void
263sxiintc_splx(int new)
264{
265	struct cpu_info *ci = curcpu();
266	sxiintc_setipl(new);
267
268	if (ci->ci_ipending & arm_smask[ci->ci_cpl])
269		arm_do_pending_intr(ci->ci_cpl);
270}
271
272int
273sxiintc_spllower(int new)
274{
275	struct cpu_info *ci = curcpu();
276	int old = ci->ci_cpl;
277	sxiintc_splx(new);
278	return (old);
279}
280
281int
282sxiintc_splraise(int new)
283{
284	struct cpu_info *ci = curcpu();
285	int old;
286	old = ci->ci_cpl;
287
288	/*
289	 * setipl must always be called because there is a race window
290	 * where the variable is updated before the mask is set
291	 * an interrupt occurs in that window without the mask always
292	 * being set, the hardware might not get updated on the next
293	 * splraise completely messing up spl protection.
294	 */
295	if (old > new)
296		new = old;
297
298	sxiintc_setipl(new);
299
300	return (old);
301}
302
303void
304sxiintc_setipl(int new)
305{
306	struct cpu_info *ci = curcpu();
307	int i, psw;
308#if 1
309	/*
310	 * XXX not needed, because all interrupts are disabled
311	 * by default, so touching maskregs has no effect, i hope.
312	 */
313	if (sxiintc_attached == 0) {
314		ci->ci_cpl = new;
315		return;
316	}
317#endif
318	psw = disable_interrupts(PSR_I);
319	ci->ci_cpl = new;
320	for (i = 0; i < NBANKS; i++)
321		bus_space_write_4(sxiintc_iot, sxiintc_ioh,
322		    INTC_MASK_REG(i), sxiintc_imask[i][new]);
323	restore_interrupts(psw);
324}
325
326void
327sxiintc_irq_handler(void *frame)
328{
329	struct intrhand *ih;
330	void *arg;
331	uint32_t pr;
332	int irq, prio, s;
333
334	irq = bus_space_read_4(sxiintc_iot, sxiintc_ioh, INTC_VECTOR_REG) >> 2;
335	if (irq == 0)
336		return;
337
338	prio = sxiintc_handler[irq].iq_irq;
339	s = sxiintc_splraise(prio);
340	splassert(prio);
341
342	pr = bus_space_read_4(sxiintc_iot, sxiintc_ioh,
343	    INTC_ENABLE_REG(IRQ2REG32(irq)));
344	bus_space_write_4(sxiintc_iot, sxiintc_ioh,
345	    INTC_ENABLE_REG(IRQ2REG32(irq)),
346	    pr & ~(1 << IRQ2BIT32(irq)));
347
348	/* clear pending */
349	pr = bus_space_read_4(sxiintc_iot, sxiintc_ioh,
350	    INTC_IRQ_PENDING_REG(IRQ2REG32(irq)));
351	bus_space_write_4(sxiintc_iot, sxiintc_ioh,
352	    INTC_IRQ_PENDING_REG(IRQ2REG32(irq)),
353	    pr | (1 << IRQ2BIT32(irq)));
354
355	pr = bus_space_read_4(sxiintc_iot, sxiintc_ioh,
356	    INTC_ENABLE_REG(IRQ2REG32(irq)));
357	bus_space_write_4(sxiintc_iot, sxiintc_ioh,
358	    INTC_ENABLE_REG(IRQ2REG32(irq)),
359	    pr | (1 << IRQ2BIT32(irq)));
360
361	TAILQ_FOREACH(ih, &sxiintc_handler[irq].iq_list, ih_list) {
362		if (ih->ih_arg != 0)
363			arg = ih->ih_arg;
364		else
365			arg = frame;
366
367		if (ih->ih_func(arg))
368			ih->ih_count.ec_count++;
369	}
370	sxiintc_splx(s);
371}
372
373void *
374sxiintc_intr_establish(int irq, int level, struct cpu_info *ci,
375    int (*func)(void *), void *arg, char *name)
376{
377	int psw;
378	struct intrhand *ih;
379	uint32_t er;
380
381	if (irq <= 0 || irq >= NIRQ)
382		panic("intr_establish: bogus irq %d %s", irq, name);
383
384	if (ci == NULL)
385		ci = &cpu_info_primary;
386	else if (!CPU_IS_PRIMARY(ci))
387		return NULL;
388
389	DPRINTF(("intr_establish: irq %d level %d [%s]\n", irq, level,
390	    name != NULL ? name : "NULL"));
391
392	psw = disable_interrupts(PSR_I);
393
394	ih = malloc(sizeof(*ih), M_DEVBUF, M_WAITOK);
395	ih->ih_func = func;
396	ih->ih_arg = arg;
397	ih->ih_ipl = level & IPL_IRQMASK;
398	ih->ih_irq = irq;
399	ih->ih_name = name;
400
401	TAILQ_INSERT_TAIL(&sxiintc_handler[irq].iq_list, ih, ih_list);
402
403	if (name != NULL)
404		evcount_attach(&ih->ih_count, name, &ih->ih_irq);
405
406	er = bus_space_read_4(sxiintc_iot, sxiintc_ioh,
407	    INTC_ENABLE_REG(IRQ2REG32(irq)));
408	bus_space_write_4(sxiintc_iot, sxiintc_ioh,
409	    INTC_ENABLE_REG(IRQ2REG32(irq)),
410	    er | (1 << IRQ2BIT32(irq)));
411
412	sxiintc_calc_masks();
413
414	restore_interrupts(psw);
415	return (ih);
416}
417
418void *
419sxiintc_intr_establish_fdt(void *cookie, int *cell, int level,
420    struct cpu_info *ci, int (*func)(void *), void *arg, char *name)
421{
422	return sxiintc_intr_establish(cell[0], level, ci, func, arg, name);
423}
424
425void
426sxiintc_intr_disestablish(void *cookie)
427{
428	struct intrhand *ih = cookie;
429	int irq = ih->ih_irq;
430	int psw;
431	uint32_t er;
432
433	psw = disable_interrupts(PSR_I);
434
435	TAILQ_REMOVE(&sxiintc_handler[irq].iq_list, ih, ih_list);
436
437	if (ih->ih_name != NULL)
438		evcount_detach(&ih->ih_count);
439
440	free(ih, M_DEVBUF, 0);
441
442	er = bus_space_read_4(sxiintc_iot, sxiintc_ioh,
443	    INTC_ENABLE_REG(IRQ2REG32(irq)));
444	bus_space_write_4(sxiintc_iot, sxiintc_ioh,
445	    INTC_ENABLE_REG(IRQ2REG32(irq)),
446	    er & ~(1 << IRQ2BIT32(irq)));
447
448	sxiintc_calc_masks();
449
450	restore_interrupts(psw);
451}
452
453const char *
454sxiintc_intr_string(void *cookie)
455{
456	return "asd?";
457}
458