]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/arm/allwinner/aw_nmi.c
Merge lldb trunk r366426, resolve conflicts, and update FREEBSD-Xlist.
[FreeBSD/FreeBSD.git] / sys / arm / allwinner / aw_nmi.c
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 <sys/rman.h>
39 #include <machine/bus.h>
40 #include <machine/intr.h>
41
42 #include <dev/fdt/fdt_intr.h>
43 #include <dev/ofw/openfirm.h>
44 #include <dev/ofw/ofw_bus.h>
45 #include <dev/ofw/ofw_bus_subr.h>
46
47 #include "pic_if.h"
48
49 #define NMI_IRQ_CTRL_REG        0x0
50 #define  NMI_IRQ_LOW_LEVEL      0x0
51 #define  NMI_IRQ_LOW_EDGE       0x1
52 #define  NMI_IRQ_HIGH_LEVEL     0x2
53 #define  NMI_IRQ_HIGH_EDGE      0x3
54 #define NMI_IRQ_PENDING_REG     0x4
55 #define  NMI_IRQ_ACK            (1U << 0)
56 #define A20_NMI_IRQ_ENABLE_REG  0x8
57 #define A31_NMI_IRQ_ENABLE_REG  0x34
58 #define  NMI_IRQ_ENABLE         (1U << 0)
59
60 #define R_NMI_IRQ_CTRL_REG      0x0c
61 #define R_NMI_IRQ_PENDING_REG   0x10
62 #define R_NMI_IRQ_ENABLE_REG    0x40
63
64 #define SC_NMI_READ(_sc, _reg)          bus_read_4(_sc->res[0], _reg)
65 #define SC_NMI_WRITE(_sc, _reg, _val)   bus_write_4(_sc->res[0], _reg, _val)
66
67 static struct resource_spec aw_nmi_res_spec[] = {
68         { SYS_RES_MEMORY,       0,      RF_ACTIVE },
69         { SYS_RES_IRQ,          0,      RF_ACTIVE },
70         { -1,                   0,      0 }
71 };
72
73 struct aw_nmi_intr {
74         struct intr_irqsrc      isrc;
75         u_int                   irq;
76         enum intr_polarity      pol;
77         enum intr_trigger       tri;
78 };
79
80 struct aw_nmi_reg_cfg {
81         uint8_t                 ctrl_reg;
82         uint8_t                 pending_reg;
83         uint8_t                 enable_reg;
84 };
85
86 struct aw_nmi_softc {
87         device_t                dev;
88         struct resource *       res[2];
89         void *                  intrcookie;
90         struct aw_nmi_intr      intr;
91         struct aw_nmi_reg_cfg * cfg;
92 };
93
94 static struct aw_nmi_reg_cfg a20_nmi_cfg = {
95         .ctrl_reg =     NMI_IRQ_CTRL_REG,
96         .pending_reg =  NMI_IRQ_PENDING_REG,
97         .enable_reg =   A20_NMI_IRQ_ENABLE_REG,
98 };
99
100 static struct aw_nmi_reg_cfg a31_nmi_cfg = {
101         .ctrl_reg =     NMI_IRQ_CTRL_REG,
102         .pending_reg =  NMI_IRQ_PENDING_REG,
103         .enable_reg =   A31_NMI_IRQ_ENABLE_REG,
104 };
105
106 static struct aw_nmi_reg_cfg a83t_r_nmi_cfg = {
107         .ctrl_reg =     R_NMI_IRQ_CTRL_REG,
108         .pending_reg =  R_NMI_IRQ_PENDING_REG,
109         .enable_reg =   R_NMI_IRQ_ENABLE_REG,
110 };
111
112 static struct ofw_compat_data compat_data[] = {
113         {"allwinner,sun7i-a20-sc-nmi", (uintptr_t)&a20_nmi_cfg},
114         {"allwinner,sun6i-a31-sc-nmi", (uintptr_t)&a31_nmi_cfg},
115         {"allwinner,sun6i-a31-r-intc", (uintptr_t)&a83t_r_nmi_cfg},
116         {"allwinner,sun8i-a83t-r-intc", (uintptr_t)&a83t_r_nmi_cfg},
117         {NULL, 0},
118 };
119
120 static int
121 aw_nmi_intr(void *arg)
122 {
123         struct aw_nmi_softc *sc;
124
125         sc = arg;
126
127         if (SC_NMI_READ(sc, sc->cfg->pending_reg) == 0) {
128                 device_printf(sc->dev, "Spurious interrupt\n");
129                 return (FILTER_HANDLED);
130         }
131
132         if (intr_isrc_dispatch(&sc->intr.isrc, curthread->td_intr_frame) != 0) {
133                 SC_NMI_WRITE(sc, sc->cfg->enable_reg, !NMI_IRQ_ENABLE);
134                 device_printf(sc->dev, "Stray interrupt, NMI disabled\n");
135         }
136
137         return (FILTER_HANDLED);
138 }
139
140 static void
141 aw_nmi_enable_intr(device_t dev, struct intr_irqsrc *isrc)
142 {
143         struct aw_nmi_softc *sc;
144
145         sc = device_get_softc(dev);
146
147         SC_NMI_WRITE(sc, sc->cfg->enable_reg, NMI_IRQ_ENABLE);
148 }
149
150 static void
151 aw_nmi_disable_intr(device_t dev, struct intr_irqsrc *isrc)
152 {
153         struct aw_nmi_softc *sc;
154
155         sc = device_get_softc(dev);
156
157         SC_NMI_WRITE(sc, sc->cfg->enable_reg, !NMI_IRQ_ENABLE);
158 }
159
160 static int
161 aw_nmi_map_fdt(device_t dev, u_int ncells, pcell_t *cells, u_int *irqp,
162     enum intr_polarity *polp, enum intr_trigger *trigp)
163 {
164         u_int irq, tripol;
165         enum intr_polarity pol;
166         enum intr_trigger trig;
167
168         if (ncells != 2) {
169                 device_printf(dev, "Invalid #interrupt-cells\n");
170                 return (EINVAL);
171         }
172
173         irq = cells[0];
174         if (irq != 0) {
175                 device_printf(dev, "Controller only support irq 0\n");
176                 return (EINVAL);
177         }
178
179         tripol = cells[1];
180
181         switch (tripol) {
182         case FDT_INTR_EDGE_RISING:
183                 trig = INTR_TRIGGER_EDGE;
184                 pol  = INTR_POLARITY_HIGH;
185                 break;
186         case FDT_INTR_EDGE_FALLING:
187                 trig = INTR_TRIGGER_EDGE;
188                 pol  = INTR_POLARITY_LOW;
189                 break;
190         case FDT_INTR_LEVEL_HIGH:
191                 trig = INTR_TRIGGER_LEVEL;
192                 pol  = INTR_POLARITY_HIGH;
193                 break;
194         case FDT_INTR_LEVEL_LOW:
195                 trig = INTR_TRIGGER_LEVEL;
196                 pol  = INTR_POLARITY_LOW;
197                 break;
198         default:
199                 device_printf(dev, "unsupported trigger/polarity 0x%2x\n",
200                     tripol);
201                 return (ENOTSUP);
202         }
203
204         *irqp = irq;
205         if (polp != NULL)
206                 *polp = pol;
207         if (trigp != NULL)
208                 *trigp = trig;
209         return (0);
210 }
211
212 static int
213 aw_nmi_map_intr(device_t dev, struct intr_map_data *data,
214     struct intr_irqsrc **isrcp)
215 {
216         struct intr_map_data_fdt *daf;
217         struct aw_nmi_softc *sc;
218         int error;
219         u_int irq;
220
221         if (data->type != INTR_MAP_DATA_FDT)
222                 return (ENOTSUP);
223
224         sc = device_get_softc(dev);
225         daf = (struct intr_map_data_fdt *)data;
226
227         error = aw_nmi_map_fdt(dev, daf->ncells, daf->cells, &irq, NULL, NULL);
228         if (error == 0)
229                 *isrcp = &sc->intr.isrc;
230
231         return (error);
232 }
233
234 static int
235 aw_nmi_setup_intr(device_t dev, struct intr_irqsrc *isrc,
236     struct resource *res, struct intr_map_data *data)
237 {
238         struct intr_map_data_fdt *daf;
239         struct aw_nmi_softc *sc;
240         struct aw_nmi_intr *nmi_intr;
241         int error, icfg;
242         u_int irq;
243         enum intr_trigger trig;
244         enum intr_polarity pol;
245
246         /* Get config for interrupt. */
247         if (data == NULL || data->type != INTR_MAP_DATA_FDT)
248                 return (ENOTSUP);
249
250         sc = device_get_softc(dev);
251         nmi_intr = (struct aw_nmi_intr *)isrc;
252         daf = (struct intr_map_data_fdt *)data;
253
254         error = aw_nmi_map_fdt(dev, daf->ncells, daf->cells, &irq, &pol, &trig);
255         if (error != 0)
256                 return (error);
257         if (nmi_intr->irq != irq)
258                 return (EINVAL);
259
260         /* Compare config if this is not first setup. */
261         if (isrc->isrc_handlers != 0) {
262                 if (pol != nmi_intr->pol || trig != nmi_intr->tri)
263                         return (EINVAL);
264                 else
265                         return (0);
266         }
267
268         nmi_intr->pol = pol;
269         nmi_intr->tri = trig;
270
271         if (trig == INTR_TRIGGER_LEVEL) {
272                 if (pol == INTR_POLARITY_LOW)
273                         icfg = NMI_IRQ_LOW_LEVEL;
274                 else
275                         icfg = NMI_IRQ_HIGH_LEVEL;
276         } else {
277                 if (pol == INTR_POLARITY_HIGH)
278                         icfg = NMI_IRQ_HIGH_EDGE;
279                 else
280                         icfg = NMI_IRQ_LOW_EDGE;
281         }
282
283         SC_NMI_WRITE(sc, sc->cfg->ctrl_reg, icfg);
284
285         return (0);
286 }
287
288 static int
289 aw_nmi_teardown_intr(device_t dev, struct intr_irqsrc *isrc,
290     struct resource *res, struct intr_map_data *data)
291 {
292         struct aw_nmi_softc *sc;
293
294         sc = device_get_softc(dev);
295
296         if (isrc->isrc_handlers == 0) {
297                 sc->intr.pol = INTR_POLARITY_CONFORM;
298                 sc->intr.tri = INTR_TRIGGER_CONFORM;
299
300                 SC_NMI_WRITE(sc, sc->cfg->enable_reg, !NMI_IRQ_ENABLE);
301         }
302
303         return (0);
304 }
305
306 static void
307 aw_nmi_pre_ithread(device_t dev, struct intr_irqsrc *isrc)
308 {
309         struct aw_nmi_softc *sc;
310
311         sc = device_get_softc(dev);
312         aw_nmi_disable_intr(dev, isrc);
313         SC_NMI_WRITE(sc, sc->cfg->pending_reg, NMI_IRQ_ACK);
314 }
315
316 static void
317 aw_nmi_post_ithread(device_t dev, struct intr_irqsrc *isrc)
318 {
319
320         arm_irq_memory_barrier(0);
321         aw_nmi_enable_intr(dev, isrc);
322 }
323
324 static void
325 aw_nmi_post_filter(device_t dev, struct intr_irqsrc *isrc)
326 {
327         struct aw_nmi_softc *sc;
328
329         sc = device_get_softc(dev);
330
331         arm_irq_memory_barrier(0);
332         SC_NMI_WRITE(sc, sc->cfg->pending_reg, NMI_IRQ_ACK);
333 }
334
335 static int
336 aw_nmi_probe(device_t dev)
337 {
338
339         if (!ofw_bus_status_okay(dev))
340                 return (ENXIO);
341
342         if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
343                 return (ENXIO);
344         device_set_desc(dev, "Allwinner NMI Controller");
345         return (BUS_PROBE_DEFAULT);
346 }
347
348 static int
349 aw_nmi_attach(device_t dev)
350 {
351         struct aw_nmi_softc *sc;
352         phandle_t xref;
353
354         sc = device_get_softc(dev);
355         sc->dev = dev;
356         sc->cfg = (struct aw_nmi_reg_cfg *)
357             ofw_bus_search_compatible(dev, compat_data)->ocd_data;
358
359         if (bus_alloc_resources(dev, aw_nmi_res_spec, sc->res) != 0) {
360                 device_printf(dev, "can't allocate device resources\n");
361                 return (ENXIO);
362         }
363         if ((bus_setup_intr(dev, sc->res[1], INTR_TYPE_MISC,
364             aw_nmi_intr, NULL, sc, &sc->intrcookie))) {
365                 device_printf(dev, "unable to register interrupt handler\n");
366                 bus_release_resources(dev, aw_nmi_res_spec, sc->res);
367                 return (ENXIO);
368         }
369
370         /* Disable and clear interrupts */
371         SC_NMI_WRITE(sc, sc->cfg->enable_reg, !NMI_IRQ_ENABLE);
372         SC_NMI_WRITE(sc, sc->cfg->pending_reg, NMI_IRQ_ACK);
373
374         xref = OF_xref_from_node(ofw_bus_get_node(dev));
375         /* Register our isrc */
376         sc->intr.irq = 0;
377         sc->intr.pol = INTR_POLARITY_CONFORM;
378         sc->intr.tri = INTR_TRIGGER_CONFORM;
379         if (intr_isrc_register(&sc->intr.isrc, sc->dev, 0, "%s,%u",
380               device_get_nameunit(sc->dev), sc->intr.irq) != 0)
381                 goto error;
382
383         if (intr_pic_register(dev, (intptr_t)xref) == NULL) {
384                 device_printf(dev, "could not register pic\n");
385                 goto error;
386         }
387         return (0);
388
389 error:
390         bus_teardown_intr(dev, sc->res[1], sc->intrcookie);
391         bus_release_resources(dev, aw_nmi_res_spec, sc->res);
392         return (ENXIO);
393 }
394
395 static device_method_t aw_nmi_methods[] = {
396         DEVMETHOD(device_probe,         aw_nmi_probe),
397         DEVMETHOD(device_attach,        aw_nmi_attach),
398
399         /* Interrupt controller interface */
400         DEVMETHOD(pic_disable_intr,     aw_nmi_disable_intr),
401         DEVMETHOD(pic_enable_intr,      aw_nmi_enable_intr),
402         DEVMETHOD(pic_map_intr,         aw_nmi_map_intr),
403         DEVMETHOD(pic_setup_intr,       aw_nmi_setup_intr),
404         DEVMETHOD(pic_teardown_intr,    aw_nmi_teardown_intr),
405         DEVMETHOD(pic_post_filter,      aw_nmi_post_filter),
406         DEVMETHOD(pic_post_ithread,     aw_nmi_post_ithread),
407         DEVMETHOD(pic_pre_ithread,      aw_nmi_pre_ithread),
408
409         {0, 0},
410 };
411
412 static driver_t aw_nmi_driver = {
413         "aw_nmi",
414         aw_nmi_methods,
415         sizeof(struct aw_nmi_softc),
416 };
417
418 static devclass_t aw_nmi_devclass;
419
420 EARLY_DRIVER_MODULE(aw_nmi, simplebus, aw_nmi_driver,
421     aw_nmi_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE);