sxiintc.c revision 1.12
1/*	$OpenBSD: sxiintc.c,v 1.12 2024/04/29 12:33:17 jsg 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
134struct intrq sxiintc_handler[NIRQ];
135u_int32_t sxiintc_smask[NIPL];
136u_int32_t sxiintc_imask[NBANKS][NIPL];
137struct interrupt_controller sxiintc_ic;
138
139bus_space_tag_t		sxiintc_iot;
140bus_space_handle_t	sxiintc_ioh;
141int			sxiintc_nirq;
142
143int	sxiintc_match(struct device *, void *, void *);
144void	sxiintc_attach(struct device *, struct device *, void *);
145int	sxiintc_spllower(int);
146int	sxiintc_splraise(int);
147void	sxiintc_setipl(int);
148void	sxiintc_calc_masks(void);
149void	*sxiintc_intr_establish_fdt(void *, int *, int, struct cpu_info *,
150	    int (*)(void *), void *, char *);
151
152const struct cfattach	sxiintc_ca = {
153	sizeof (struct device), sxiintc_match, sxiintc_attach
154};
155
156struct cfdriver sxiintc_cd = {
157	NULL, "sxiintc", DV_DULL
158};
159
160int sxiintc_attached = 0;
161
162int
163sxiintc_match(struct device *parent, void *match, void *aux)
164{
165	struct fdt_attach_args *faa = aux;
166
167	return OF_is_compatible(faa->fa_node, "allwinner,sun4i-a10-ic");
168}
169
170void
171sxiintc_attach(struct device *parent, struct device *self, void *aux)
172{
173	struct fdt_attach_args *faa = aux;
174	int i, j;
175
176	sxiintc_iot = faa->fa_iot;
177	if (bus_space_map(sxiintc_iot, faa->fa_reg[0].addr,
178	    faa->fa_reg[0].size, 0, &sxiintc_ioh))
179		panic("sxiintc_attach: bus_space_map failed!");
180
181	/* disable/mask/clear all interrupts */
182	for (i = 0; i < NBANKS; i++) {
183		bus_space_write_4(sxiintc_iot, sxiintc_ioh, INTC_ENABLE_REG(i), 0);
184		bus_space_write_4(sxiintc_iot, sxiintc_ioh, INTC_MASK_REG(i), 0);
185		bus_space_write_4(sxiintc_iot, sxiintc_ioh, INTC_IRQ_PENDING_REG(i),
186		    0xffffffff);
187		for (j = 0; j < NIPL; j++)
188			sxiintc_imask[i][j] = 0;
189	}
190
191	/* XXX */
192	bus_space_write_4(sxiintc_iot, sxiintc_ioh, INTC_PROTECTION_REG, 1);
193	bus_space_write_4(sxiintc_iot, sxiintc_ioh, INTC_NMI_CTRL_REG, 0);
194
195	for (i = 0; i < NIRQ; i++)
196		TAILQ_INIT(&sxiintc_handler[i].iq_list);
197
198	sxiintc_calc_masks();
199
200	arm_init_smask();
201	sxiintc_attached = 1;
202
203	/* insert self as interrupt handler */
204	arm_set_intr_handler(sxiintc_splraise, sxiintc_spllower, sxiintc_splx,
205	    sxiintc_setipl,
206	    sxiintc_intr_establish, sxiintc_intr_disestablish, sxiintc_intr_string,
207	    sxiintc_irq_handler);
208	sxiintc_setipl(IPL_HIGH);  /* XXX ??? */
209	enable_interrupts(PSR_I);
210	printf("\n");
211
212	sxiintc_ic.ic_node = faa->fa_node;
213	sxiintc_ic.ic_establish = sxiintc_intr_establish_fdt;
214	arm_intr_register_fdt(&sxiintc_ic);
215}
216
217void
218sxiintc_calc_masks(void)
219{
220	struct cpu_info *ci = curcpu();
221	int irq;
222	struct intrhand *ih;
223	int i;
224
225	for (irq = 0; irq < NIRQ; irq++) {
226		int max = IPL_NONE;
227		int min = IPL_HIGH;
228		TAILQ_FOREACH(ih, &sxiintc_handler[irq].iq_list, ih_list) {
229			if (ih->ih_ipl > max)
230				max = ih->ih_ipl;
231			if (ih->ih_ipl < min)
232				min = ih->ih_ipl;
233		}
234
235		sxiintc_handler[irq].iq_irq = max;
236
237		if (max == IPL_NONE)
238			min = IPL_NONE;
239
240#ifdef DEBUG_INTC
241		if (min != IPL_NONE) {
242			printf("irq %d to block at %d %d reg %d bit %d\n",
243			    irq, max, min, IRQ2REG32(irq),
244			    IRQ2BIT32(irq));
245		}
246#endif
247		/* Enable interrupts at lower levels, clear -> enable */
248		for (i = 0; i < min; i++)
249			sxiintc_imask[IRQ2REG32(irq)][i] &=
250			    ~(1 << IRQ2BIT32(irq));
251		for (; i < NIPL; i++)
252			sxiintc_imask[IRQ2REG32(irq)][i] |=
253			    (1 << IRQ2BIT32(irq));
254		/* XXX - set enable/disable, priority */
255	}
256
257	sxiintc_setipl(ci->ci_cpl);
258}
259
260void
261sxiintc_splx(int new)
262{
263	struct cpu_info *ci = curcpu();
264	sxiintc_setipl(new);
265
266	if (ci->ci_ipending & arm_smask[ci->ci_cpl])
267		arm_do_pending_intr(ci->ci_cpl);
268}
269
270int
271sxiintc_spllower(int new)
272{
273	struct cpu_info *ci = curcpu();
274	int old = ci->ci_cpl;
275	sxiintc_splx(new);
276	return (old);
277}
278
279int
280sxiintc_splraise(int new)
281{
282	struct cpu_info *ci = curcpu();
283	int old;
284	old = ci->ci_cpl;
285
286	/*
287	 * setipl must always be called because there is a race window
288	 * where the variable is updated before the mask is set
289	 * an interrupt occurs in that window without the mask always
290	 * being set, the hardware might not get updated on the next
291	 * splraise completely messing up spl protection.
292	 */
293	if (old > new)
294		new = old;
295
296	sxiintc_setipl(new);
297
298	return (old);
299}
300
301void
302sxiintc_setipl(int new)
303{
304	struct cpu_info *ci = curcpu();
305	int i, psw;
306#if 1
307	/*
308	 * XXX not needed, because all interrupts are disabled
309	 * by default, so touching maskregs has no effect, i hope.
310	 */
311	if (sxiintc_attached == 0) {
312		ci->ci_cpl = new;
313		return;
314	}
315#endif
316	psw = disable_interrupts(PSR_I);
317	ci->ci_cpl = new;
318	for (i = 0; i < NBANKS; i++)
319		bus_space_write_4(sxiintc_iot, sxiintc_ioh,
320		    INTC_MASK_REG(i), sxiintc_imask[i][new]);
321	restore_interrupts(psw);
322}
323
324void
325sxiintc_irq_handler(void *frame)
326{
327	struct intrhand *ih;
328	void *arg;
329	uint32_t pr;
330	int irq, prio, s;
331
332	irq = bus_space_read_4(sxiintc_iot, sxiintc_ioh, INTC_VECTOR_REG) >> 2;
333	if (irq == 0)
334		return;
335
336	prio = sxiintc_handler[irq].iq_irq;
337	s = sxiintc_splraise(prio);
338	splassert(prio);
339
340	pr = bus_space_read_4(sxiintc_iot, sxiintc_ioh,
341	    INTC_ENABLE_REG(IRQ2REG32(irq)));
342	bus_space_write_4(sxiintc_iot, sxiintc_ioh,
343	    INTC_ENABLE_REG(IRQ2REG32(irq)),
344	    pr & ~(1 << IRQ2BIT32(irq)));
345
346	/* clear pending */
347	pr = bus_space_read_4(sxiintc_iot, sxiintc_ioh,
348	    INTC_IRQ_PENDING_REG(IRQ2REG32(irq)));
349	bus_space_write_4(sxiintc_iot, sxiintc_ioh,
350	    INTC_IRQ_PENDING_REG(IRQ2REG32(irq)),
351	    pr | (1 << IRQ2BIT32(irq)));
352
353	pr = bus_space_read_4(sxiintc_iot, sxiintc_ioh,
354	    INTC_ENABLE_REG(IRQ2REG32(irq)));
355	bus_space_write_4(sxiintc_iot, sxiintc_ioh,
356	    INTC_ENABLE_REG(IRQ2REG32(irq)),
357	    pr | (1 << IRQ2BIT32(irq)));
358
359	TAILQ_FOREACH(ih, &sxiintc_handler[irq].iq_list, ih_list) {
360		if (ih->ih_arg)
361			arg = ih->ih_arg;
362		else
363			arg = frame;
364
365		if (ih->ih_func(arg))
366			ih->ih_count.ec_count++;
367	}
368	sxiintc_splx(s);
369}
370
371void *
372sxiintc_intr_establish(int irq, int level, struct cpu_info *ci,
373    int (*func)(void *), void *arg, char *name)
374{
375	int psw;
376	struct intrhand *ih;
377	uint32_t er;
378
379	if (irq <= 0 || irq >= NIRQ)
380		panic("intr_establish: bogus irq %d %s", irq, name);
381
382	if (ci == NULL)
383		ci = &cpu_info_primary;
384	else if (!CPU_IS_PRIMARY(ci))
385		return NULL;
386
387	DPRINTF(("intr_establish: irq %d level %d [%s]\n", irq, level,
388	    name != NULL ? name : "NULL"));
389
390	psw = disable_interrupts(PSR_I);
391
392	ih = malloc(sizeof(*ih), M_DEVBUF, M_WAITOK);
393	ih->ih_func = func;
394	ih->ih_arg = arg;
395	ih->ih_ipl = level & IPL_IRQMASK;
396	ih->ih_irq = irq;
397	ih->ih_name = name;
398
399	TAILQ_INSERT_TAIL(&sxiintc_handler[irq].iq_list, ih, ih_list);
400
401	if (name != NULL)
402		evcount_attach(&ih->ih_count, name, &ih->ih_irq);
403
404	er = bus_space_read_4(sxiintc_iot, sxiintc_ioh,
405	    INTC_ENABLE_REG(IRQ2REG32(irq)));
406	bus_space_write_4(sxiintc_iot, sxiintc_ioh,
407	    INTC_ENABLE_REG(IRQ2REG32(irq)),
408	    er | (1 << IRQ2BIT32(irq)));
409
410	sxiintc_calc_masks();
411
412	restore_interrupts(psw);
413	return (ih);
414}
415
416void *
417sxiintc_intr_establish_fdt(void *cookie, int *cell, int level,
418    struct cpu_info *ci, int (*func)(void *), void *arg, char *name)
419{
420	return sxiintc_intr_establish(cell[0], level, ci, func, arg, name);
421}
422
423void
424sxiintc_intr_disestablish(void *cookie)
425{
426	struct intrhand *ih = cookie;
427	int irq = ih->ih_irq;
428	int psw;
429	uint32_t er;
430
431	psw = disable_interrupts(PSR_I);
432
433	TAILQ_REMOVE(&sxiintc_handler[irq].iq_list, ih, ih_list);
434
435	if (ih->ih_name != NULL)
436		evcount_detach(&ih->ih_count);
437
438	free(ih, M_DEVBUF, 0);
439
440	er = bus_space_read_4(sxiintc_iot, sxiintc_ioh,
441	    INTC_ENABLE_REG(IRQ2REG32(irq)));
442	bus_space_write_4(sxiintc_iot, sxiintc_ioh,
443	    INTC_ENABLE_REG(IRQ2REG32(irq)),
444	    er & ~(1 << IRQ2BIT32(irq)));
445
446	sxiintc_calc_masks();
447
448	restore_interrupts(psw);
449}
450
451const char *
452sxiintc_intr_string(void *cookie)
453{
454	return "asd?";
455}
456