xref: /freebsd/sys/arm/allwinner/aw_nmi.c (revision 8ef24a0d4b28fe230e20637f56869cc4148cd2ca)
1 /*-
2  * Copyright (c) 2016 Emmanuel Vadot <manu@freebsd.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
29 
30 #include "opt_platform.h"
31 
32 #include <sys/param.h>
33 #include <sys/systm.h>
34 #include <sys/bus.h>
35 #include <sys/kernel.h>
36 #include <sys/module.h>
37 #include <sys/proc.h>
38 #include <machine/bus.h>
39 #include <machine/intr.h>
40 
41 #include <dev/fdt/fdt_common.h>
42 #include <dev/ofw/openfirm.h>
43 #include <dev/ofw/ofw_bus.h>
44 #include <dev/ofw/ofw_bus_subr.h>
45 
46 #include <dt-bindings/interrupt-controller/irq.h>
47 
48 #include "pic_if.h"
49 
50 #define	NMI_IRQ_CTRL_REG	0x0
51 #define	 NMI_IRQ_LOW_LEVEL	0x0
52 #define	 NMI_IRQ_LOW_EDGE	0x1
53 #define	 NMI_IRQ_HIGH_LEVEL	0x2
54 #define	 NMI_IRQ_HIGH_EDGE	0x3
55 #define	NMI_IRQ_PENDING_REG	0x4
56 #define	 NMI_IRQ_ACK		(1U << 0)
57 #define	A20_NMI_IRQ_ENABLE_REG	0x8
58 #define	A31_NMI_IRQ_ENABLE_REG	0x34
59 #define	 NMI_IRQ_ENABLE		(1U << 0)
60 
61 #define	SC_NMI_READ(_sc, _reg)		bus_read_4(_sc->res[0], _reg)
62 #define	SC_NMI_WRITE(_sc, _reg, _val)	bus_write_4(_sc->res[0], _reg, _val)
63 
64 static struct resource_spec aw_nmi_res_spec[] = {
65 	{ SYS_RES_MEMORY,	0,	RF_ACTIVE },
66 	{ SYS_RES_IRQ,		0,	RF_ACTIVE },
67 	{ -1,			0,	0 }
68 };
69 
70 struct aw_nmi_intr {
71 	struct intr_irqsrc	isrc;
72 	u_int			irq;
73 	enum intr_polarity	pol;
74 	enum intr_trigger	tri;
75 };
76 
77 struct aw_nmi_softc {
78 	device_t		dev;
79 	struct resource *	res[2];
80 	void *			intrcookie;
81 	struct aw_nmi_intr	intr;
82 	uint8_t			enable_reg;
83 };
84 
85 #define	A20_NMI	1
86 #define	A31_NMI	2
87 
88 static struct ofw_compat_data compat_data[] = {
89 	{"allwinner,sun7i-a20-sc-nmi", A20_NMI},
90 	{"allwinner,sun6i-a31-sc-nmi", A31_NMI},
91 
92 	{NULL, 0},
93 };
94 
95 static int
96 aw_nmi_intr(void *arg)
97 {
98 	struct aw_nmi_softc *sc;
99 
100 	sc = arg;
101 
102 	if (SC_NMI_READ(sc, NMI_IRQ_PENDING_REG) == 0) {
103 		device_printf(sc->dev, "Spurious interrupt\n");
104 		return (FILTER_HANDLED);
105 	}
106 
107 	if (intr_isrc_dispatch(&sc->intr.isrc, curthread->td_intr_frame) != 0) {
108 		SC_NMI_WRITE(sc, sc->enable_reg, !NMI_IRQ_ENABLE);
109 		device_printf(sc->dev, "Stray interrupt, NMI disabled\n");
110 	}
111 
112 	return (FILTER_HANDLED);
113 }
114 
115 static void
116 aw_nmi_enable_intr(device_t dev, struct intr_irqsrc *isrc)
117 {
118 	struct aw_nmi_softc *sc;
119 
120 	sc = device_get_softc(dev);
121 
122 	SC_NMI_WRITE(sc, sc->enable_reg, NMI_IRQ_ENABLE);
123 }
124 
125 static void
126 aw_nmi_disable_intr(device_t dev, struct intr_irqsrc *isrc)
127 {
128 	struct aw_nmi_softc *sc;
129 
130 	sc = device_get_softc(dev);
131 
132 	SC_NMI_WRITE(sc, sc->enable_reg, !NMI_IRQ_ENABLE);
133 }
134 
135 static int
136 aw_nmi_map_fdt(device_t dev, u_int ncells, pcell_t *cells, u_int *irqp,
137     enum intr_polarity *polp, enum intr_trigger *trigp)
138 {
139 	u_int irq, tripol;
140 	enum intr_polarity pol;
141 	enum intr_trigger trig;
142 
143 	if (ncells != 2) {
144 		device_printf(dev, "Invalid #interrupt-cells\n");
145 		return (EINVAL);
146 	}
147 
148 	irq = cells[0];
149 	if (irq != 0) {
150 		device_printf(dev, "Controller only support irq 0\n");
151 		return (EINVAL);
152 	}
153 
154 	tripol = cells[1];
155 
156 	switch (tripol) {
157 	case IRQ_TYPE_EDGE_RISING:
158 		trig = INTR_TRIGGER_EDGE;
159 		pol  = INTR_POLARITY_HIGH;
160 		break;
161 	case IRQ_TYPE_EDGE_FALLING:
162 		trig = INTR_TRIGGER_EDGE;
163 		pol  = INTR_POLARITY_LOW;
164 		break;
165 	case IRQ_TYPE_LEVEL_HIGH:
166 		trig = INTR_TRIGGER_LEVEL;
167 		pol  = INTR_POLARITY_HIGH;
168 		break;
169 	case IRQ_TYPE_LEVEL_LOW:
170 		trig = INTR_TRIGGER_LEVEL;
171 		pol  = INTR_POLARITY_LOW;
172 		break;
173 	default:
174 		device_printf(dev, "unsupported trigger/polarity 0x%2x\n",
175 		    tripol);
176 		return (ENOTSUP);
177 	}
178 
179 	*irqp = irq;
180 	if (polp != NULL)
181 		*polp = pol;
182 	if (trigp != NULL)
183 		*trigp = trig;
184 	return (0);
185 }
186 
187 static int
188 aw_nmi_map_intr(device_t dev, struct intr_map_data *data,
189     struct intr_irqsrc **isrcp)
190 {
191 	struct intr_map_data_fdt *daf;
192 	struct aw_nmi_softc *sc;
193 	int error;
194 	u_int irq;
195 
196 	if (data->type != INTR_MAP_DATA_FDT)
197 		return (ENOTSUP);
198 
199 	sc = device_get_softc(dev);
200 	daf = (struct intr_map_data_fdt *)data;
201 
202 	error = aw_nmi_map_fdt(dev, daf->ncells, daf->cells, &irq, NULL, NULL);
203 	if (error == 0)
204 		*isrcp = &sc->intr.isrc;
205 
206 	return (error);
207 }
208 
209 static int
210 aw_nmi_setup_intr(device_t dev, struct intr_irqsrc *isrc,
211     struct resource *res, struct intr_map_data *data)
212 {
213 	struct intr_map_data_fdt *daf;
214 	struct aw_nmi_softc *sc;
215 	struct aw_nmi_intr *nmi_intr;
216 	int error, icfg;
217 	u_int irq;
218 	enum intr_trigger trig;
219 	enum intr_polarity pol;
220 
221 	/* Get config for interrupt. */
222 	if (data == NULL || data->type != INTR_MAP_DATA_FDT)
223 		return (ENOTSUP);
224 
225 	sc = device_get_softc(dev);
226 	nmi_intr = (struct aw_nmi_intr *)isrc;
227 	daf = (struct intr_map_data_fdt *)data;
228 
229 	error = aw_nmi_map_fdt(dev, daf->ncells, daf->cells, &irq, &pol, &trig);
230 	if (error != 0)
231 		return (error);
232 	if (nmi_intr->irq != irq)
233 		return (EINVAL);
234 
235 	/* Compare config if this is not first setup. */
236 	if (isrc->isrc_handlers != 0) {
237 		if (pol != nmi_intr->pol || trig != nmi_intr->tri)
238 			return (EINVAL);
239 		else
240 			return (0);
241 	}
242 
243 	nmi_intr->pol = pol;
244 	nmi_intr->tri = trig;
245 
246 	if (trig == INTR_TRIGGER_LEVEL) {
247 		if (pol == INTR_POLARITY_LOW)
248 			icfg = NMI_IRQ_LOW_LEVEL;
249 		else
250 			icfg = NMI_IRQ_HIGH_LEVEL;
251 	} else {
252 		if (pol == INTR_POLARITY_HIGH)
253 			icfg = NMI_IRQ_HIGH_EDGE;
254 		else
255 			icfg = NMI_IRQ_LOW_EDGE;
256 	}
257 
258 	SC_NMI_WRITE(sc, NMI_IRQ_CTRL_REG, icfg);
259 
260 	return (0);
261 }
262 
263 static int
264 aw_nmi_teardown_intr(device_t dev, struct intr_irqsrc *isrc,
265     struct resource *res, struct intr_map_data *data)
266 {
267 	struct aw_nmi_softc *sc;
268 
269 	sc = device_get_softc(dev);
270 
271 	if (isrc->isrc_handlers == 0) {
272 		sc->intr.pol = INTR_POLARITY_CONFORM;
273 		sc->intr.tri = INTR_TRIGGER_CONFORM;
274 
275 		SC_NMI_WRITE(sc, sc->enable_reg, !NMI_IRQ_ENABLE);
276 	}
277 
278 	return (0);
279 }
280 
281 static void
282 aw_nmi_pre_ithread(device_t dev, struct intr_irqsrc *isrc)
283 {
284 	struct aw_nmi_softc *sc;
285 
286 	sc = device_get_softc(dev);
287 	aw_nmi_disable_intr(dev, isrc);
288 	SC_NMI_WRITE(sc, NMI_IRQ_PENDING_REG, NMI_IRQ_ACK);
289 }
290 
291 static void
292 aw_nmi_post_ithread(device_t dev, struct intr_irqsrc *isrc)
293 {
294 
295 	arm_irq_memory_barrier(0);
296 	aw_nmi_enable_intr(dev, isrc);
297 }
298 
299 static void
300 aw_nmi_post_filter(device_t dev, struct intr_irqsrc *isrc)
301 {
302 	struct aw_nmi_softc *sc;
303 
304 	sc = device_get_softc(dev);
305 
306 	arm_irq_memory_barrier(0);
307 	SC_NMI_WRITE(sc, NMI_IRQ_PENDING_REG, NMI_IRQ_ACK);
308 }
309 
310 static int
311 aw_nmi_probe(device_t dev)
312 {
313 
314 	if (!ofw_bus_status_okay(dev))
315 		return (ENXIO);
316 
317 	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
318 		return (ENXIO);
319 	device_set_desc(dev, "Allwinner NMI Controller");
320 	return (BUS_PROBE_DEFAULT);
321 }
322 
323 static int
324 aw_nmi_attach(device_t dev)
325 {
326 	struct aw_nmi_softc *sc;
327 	phandle_t xref;
328 
329 	sc = device_get_softc(dev);
330 	sc->dev = dev;
331 
332 	if (bus_alloc_resources(dev, aw_nmi_res_spec, sc->res) != 0) {
333 		device_printf(dev, "can't allocate device resources\n");
334 		return (ENXIO);
335 	}
336 	if ((bus_setup_intr(dev, sc->res[1], INTR_TYPE_MISC,
337 	    aw_nmi_intr, NULL, sc, &sc->intrcookie))) {
338 		device_printf(dev, "unable to register interrupt handler\n");
339 		bus_release_resources(dev, aw_nmi_res_spec, sc->res);
340 		return (ENXIO);
341 	}
342 
343 	switch (ofw_bus_search_compatible(dev, compat_data)->ocd_data) {
344 	case A20_NMI:
345 		sc->enable_reg = A20_NMI_IRQ_ENABLE_REG;
346 		break;
347 	case A31_NMI:
348 		sc->enable_reg = A31_NMI_IRQ_ENABLE_REG;
349 		break;
350 	}
351 
352 	/* Disable and clear interrupts */
353 	SC_NMI_WRITE(sc, sc->enable_reg, !NMI_IRQ_ENABLE);
354 	SC_NMI_WRITE(sc, NMI_IRQ_PENDING_REG, NMI_IRQ_ACK);
355 
356 	xref = OF_xref_from_node(ofw_bus_get_node(dev));
357 	/* Register our isrc */
358 	sc->intr.irq = 0;
359 	sc->intr.pol = INTR_POLARITY_CONFORM;
360 	sc->intr.tri = INTR_TRIGGER_CONFORM;
361 	if (intr_isrc_register(&sc->intr.isrc, sc->dev, 0, "%s,%u",
362 	      device_get_nameunit(sc->dev), sc->intr.irq) != 0)
363 		goto error;
364 
365 	if (intr_pic_register(dev, (intptr_t)xref) != 0) {
366 		device_printf(dev, "could not register pic\n");
367 		goto error;
368 	}
369 	return (0);
370 
371 error:
372 	bus_teardown_intr(dev, sc->res[1], sc->intrcookie);
373 	bus_release_resources(dev, aw_nmi_res_spec, sc->res);
374 	return (ENXIO);
375 }
376 
377 static device_method_t aw_nmi_methods[] = {
378 	DEVMETHOD(device_probe,		aw_nmi_probe),
379 	DEVMETHOD(device_attach,	aw_nmi_attach),
380 
381 	/* Interrupt controller interface */
382 	DEVMETHOD(pic_disable_intr,	aw_nmi_disable_intr),
383 	DEVMETHOD(pic_enable_intr,	aw_nmi_enable_intr),
384 	DEVMETHOD(pic_map_intr,		aw_nmi_map_intr),
385 	DEVMETHOD(pic_setup_intr,	aw_nmi_setup_intr),
386 	DEVMETHOD(pic_teardown_intr,	aw_nmi_teardown_intr),
387 	DEVMETHOD(pic_post_filter,	aw_nmi_post_filter),
388 	DEVMETHOD(pic_post_ithread,	aw_nmi_post_ithread),
389 	DEVMETHOD(pic_pre_ithread,	aw_nmi_pre_ithread),
390 
391 	{0, 0},
392 };
393 
394 static driver_t aw_nmi_driver = {
395 	"aw_nmi",
396 	aw_nmi_methods,
397 	sizeof(struct aw_nmi_softc),
398 };
399 
400 static devclass_t aw_nmi_devclass;
401 
402 EARLY_DRIVER_MODULE(aw_nmi, simplebus, aw_nmi_driver,
403     aw_nmi_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LAST);
404