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