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
67static 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
73struct aw_nmi_intr {
74	struct intr_irqsrc	isrc;
75	u_int			irq;
76	enum intr_polarity	pol;
77	enum intr_trigger	tri;
78};
79
80struct aw_nmi_reg_cfg {
81	uint8_t			ctrl_reg;
82	uint8_t			pending_reg;
83	uint8_t			enable_reg;
84};
85
86struct 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
94static 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
100static 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
106static 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
112static 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
120static int
121aw_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
140static void
141aw_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
150static void
151aw_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
160static int
161aw_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
212static int
213aw_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
234static int
235aw_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
288static int
289aw_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
306static void
307aw_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
316static void
317aw_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
324static void
325aw_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
335static int
336aw_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
348static int
349aw_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
389error:
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
395static 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
412static driver_t aw_nmi_driver = {
413	"aw_nmi",
414	aw_nmi_methods,
415	sizeof(struct aw_nmi_softc),
416};
417
418static devclass_t aw_nmi_devclass;
419
420EARLY_DRIVER_MODULE(aw_nmi, simplebus, aw_nmi_driver,
421    aw_nmi_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE);
422