1/*-
2 * Copyright (c) 2012 Damjan Marion <dmarion@Freebsd.org>
3 * All rights reserved.
4 *
5 * Based on OMAP3 INTC code by Ben Gray
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29
30#include <sys/cdefs.h>
31__FBSDID("$FreeBSD$");
32
33#include "opt_platform.h"
34
35#include <sys/param.h>
36#include <sys/systm.h>
37#include <sys/bus.h>
38#include <sys/kernel.h>
39#include <sys/ktr.h>
40#include <sys/module.h>
41#include <sys/proc.h>
42#include <sys/rman.h>
43#include <machine/bus.h>
44#include <machine/intr.h>
45
46#include <dev/fdt/fdt_common.h>
47#include <dev/ofw/openfirm.h>
48#include <dev/ofw/ofw_bus.h>
49#include <dev/ofw/ofw_bus_subr.h>
50
51#ifdef INTRNG
52#include "pic_if.h"
53#endif
54
55#define INTC_REVISION		0x00
56#define INTC_SYSCONFIG		0x10
57#define INTC_SYSSTATUS		0x14
58#define INTC_SIR_IRQ		0x40
59#define INTC_CONTROL		0x48
60#define INTC_THRESHOLD		0x68
61#define INTC_MIR_CLEAR(x)	(0x88 + ((x) * 0x20))
62#define INTC_MIR_SET(x)		(0x8C + ((x) * 0x20))
63#define INTC_ISR_SET(x)		(0x90 + ((x) * 0x20))
64#define INTC_ISR_CLEAR(x)	(0x94 + ((x) * 0x20))
65
66#define INTC_SIR_SPURIOUS_MASK	0xffffff80
67#define INTC_SIR_ACTIVE_MASK	0x7f
68
69#define INTC_NIRQS	128
70
71#ifdef INTRNG
72struct ti_aintc_irqsrc {
73	struct intr_irqsrc	tai_isrc;
74	u_int			tai_irq;
75};
76#endif
77
78struct ti_aintc_softc {
79	device_t		sc_dev;
80	struct resource *	aintc_res[3];
81	bus_space_tag_t		aintc_bst;
82	bus_space_handle_t	aintc_bsh;
83	uint8_t			ver;
84#ifdef INTRNG
85	struct ti_aintc_irqsrc	aintc_isrcs[INTC_NIRQS];
86#endif
87};
88
89static struct resource_spec ti_aintc_spec[] = {
90	{ SYS_RES_MEMORY,	0,	RF_ACTIVE },
91	{ -1, 0 }
92};
93
94static struct ti_aintc_softc *ti_aintc_sc = NULL;
95
96#define	aintc_read_4(_sc, reg)		\
97    bus_space_read_4((_sc)->aintc_bst, (_sc)->aintc_bsh, (reg))
98#define	aintc_write_4(_sc, reg, val)		\
99    bus_space_write_4((_sc)->aintc_bst, (_sc)->aintc_bsh, (reg), (val))
100
101/* List of compatible strings for FDT tree */
102static struct ofw_compat_data compat_data[] = {
103	{"ti,am33xx-intc",	1},
104	{"ti,omap2-intc",	1},
105	{NULL,		 	0},
106};
107
108#ifdef INTRNG
109static inline void
110ti_aintc_irq_eoi(struct ti_aintc_softc *sc)
111{
112
113	aintc_write_4(sc, INTC_CONTROL, 1);
114}
115
116static inline void
117ti_aintc_irq_mask(struct ti_aintc_softc *sc, u_int irq)
118{
119
120	aintc_write_4(sc, INTC_MIR_SET(irq >> 5), (1UL << (irq & 0x1F)));
121}
122
123static inline void
124ti_aintc_irq_unmask(struct ti_aintc_softc *sc, u_int irq)
125{
126
127	aintc_write_4(sc, INTC_MIR_CLEAR(irq >> 5), (1UL << (irq & 0x1F)));
128}
129
130static int
131ti_aintc_intr(void *arg)
132{
133	uint32_t irq;
134	struct ti_aintc_softc *sc = arg;
135
136	/* Get active interrupt */
137	irq = aintc_read_4(sc, INTC_SIR_IRQ);
138	if ((irq & INTC_SIR_SPURIOUS_MASK) != 0) {
139		device_printf(sc->sc_dev,
140		    "Spurious interrupt detected (0x%08x)\n", irq);
141		ti_aintc_irq_eoi(sc);
142		return (FILTER_HANDLED);
143	}
144
145	/* Only level-sensitive interrupts detection is supported. */
146	irq &= INTC_SIR_ACTIVE_MASK;
147	if (intr_isrc_dispatch(&sc->aintc_isrcs[irq].tai_isrc,
148	    curthread->td_intr_frame) != 0) {
149		ti_aintc_irq_mask(sc, irq);
150		ti_aintc_irq_eoi(sc);
151		device_printf(sc->sc_dev, "Stray irq %u disabled\n", irq);
152	}
153
154	arm_irq_memory_barrier(irq); /* XXX */
155	return (FILTER_HANDLED);
156}
157
158static void
159ti_aintc_enable_intr(device_t dev, struct intr_irqsrc *isrc)
160{
161	u_int irq = ((struct ti_aintc_irqsrc *)isrc)->tai_irq;
162	struct ti_aintc_softc *sc = device_get_softc(dev);
163
164	arm_irq_memory_barrier(irq);
165	ti_aintc_irq_unmask(sc, irq);
166}
167
168static void
169ti_aintc_disable_intr(device_t dev, struct intr_irqsrc *isrc)
170{
171	u_int irq = ((struct ti_aintc_irqsrc *)isrc)->tai_irq;
172	struct ti_aintc_softc *sc = device_get_softc(dev);
173
174	ti_aintc_irq_mask(sc, irq);
175}
176
177static int
178ti_aintc_map_intr(device_t dev, struct intr_map_data *data,
179    struct intr_irqsrc **isrcp)
180{
181	struct intr_map_data_fdt *daf;
182	struct ti_aintc_softc *sc;
183
184	if (data->type != INTR_MAP_DATA_FDT)
185		return (ENOTSUP);
186
187	daf = (struct intr_map_data_fdt *)data;
188	if (daf->ncells != 1 || daf->cells[0] >= INTC_NIRQS)
189		return (EINVAL);
190
191	sc = device_get_softc(dev);
192	*isrcp = &sc->aintc_isrcs[daf->cells[0]].tai_isrc;
193	return (0);
194}
195
196static void
197ti_aintc_pre_ithread(device_t dev, struct intr_irqsrc *isrc)
198{
199	u_int irq = ((struct ti_aintc_irqsrc *)isrc)->tai_irq;
200	struct ti_aintc_softc *sc = device_get_softc(dev);
201
202	ti_aintc_irq_mask(sc, irq);
203	ti_aintc_irq_eoi(sc);
204}
205
206static void
207ti_aintc_post_ithread(device_t dev, struct intr_irqsrc *isrc)
208{
209
210	ti_aintc_enable_intr(dev, isrc);
211}
212
213static void
214ti_aintc_post_filter(device_t dev, struct intr_irqsrc *isrc)
215{
216
217	ti_aintc_irq_eoi(device_get_softc(dev));
218}
219
220static int
221ti_aintc_pic_attach(struct ti_aintc_softc *sc)
222{
223	struct intr_pic *pic;
224	int error;
225	uint32_t irq;
226	const char *name;
227	intptr_t xref;
228
229	name = device_get_nameunit(sc->sc_dev);
230	for (irq = 0; irq < INTC_NIRQS; irq++) {
231		sc->aintc_isrcs[irq].tai_irq = irq;
232
233		error = intr_isrc_register(&sc->aintc_isrcs[irq].tai_isrc,
234		    sc->sc_dev, 0, "%s,%u", name, irq);
235		if (error != 0)
236			return (error);
237	}
238
239	xref = OF_xref_from_node(ofw_bus_get_node(sc->sc_dev));
240	pic = intr_pic_register(sc->sc_dev, xref);
241	if (pic == NULL)
242		return (ENXIO);
243
244	return (intr_pic_claim_root(sc->sc_dev, xref, ti_aintc_intr, sc, 0));
245}
246
247#else
248static void
249aintc_post_filter(void *arg)
250{
251
252	arm_irq_memory_barrier(0);
253	aintc_write_4(ti_aintc_sc, INTC_CONTROL, 1); /* EOI */
254}
255#endif
256
257static int
258ti_aintc_probe(device_t dev)
259{
260	if (!ofw_bus_status_okay(dev))
261		return (ENXIO);
262
263	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
264		return (ENXIO);
265
266	device_set_desc(dev, "TI AINTC Interrupt Controller");
267	return (BUS_PROBE_DEFAULT);
268}
269
270static int
271ti_aintc_attach(device_t dev)
272{
273	struct		ti_aintc_softc *sc = device_get_softc(dev);
274	uint32_t x;
275
276	sc->sc_dev = dev;
277
278	if (ti_aintc_sc)
279		return (ENXIO);
280
281	if (bus_alloc_resources(dev, ti_aintc_spec, sc->aintc_res)) {
282		device_printf(dev, "could not allocate resources\n");
283		return (ENXIO);
284	}
285
286	sc->aintc_bst = rman_get_bustag(sc->aintc_res[0]);
287	sc->aintc_bsh = rman_get_bushandle(sc->aintc_res[0]);
288
289	ti_aintc_sc = sc;
290
291	x = aintc_read_4(sc, INTC_REVISION);
292	device_printf(dev, "Revision %u.%u\n",(x >> 4) & 0xF, x & 0xF);
293
294	/* SoftReset */
295	aintc_write_4(sc, INTC_SYSCONFIG, 2);
296
297	/* Wait for reset to complete */
298	while(!(aintc_read_4(sc, INTC_SYSSTATUS) & 1));
299
300	/*Set Priority Threshold */
301	aintc_write_4(sc, INTC_THRESHOLD, 0xFF);
302
303#ifndef INTRNG
304	arm_post_filter = aintc_post_filter;
305#else
306	if (ti_aintc_pic_attach(sc) != 0) {
307		device_printf(dev, "could not attach PIC\n");
308		return (ENXIO);
309	}
310#endif
311	return (0);
312}
313
314static device_method_t ti_aintc_methods[] = {
315	DEVMETHOD(device_probe,		ti_aintc_probe),
316	DEVMETHOD(device_attach,	ti_aintc_attach),
317
318#ifdef INTRNG
319	DEVMETHOD(pic_disable_intr,	ti_aintc_disable_intr),
320	DEVMETHOD(pic_enable_intr,	ti_aintc_enable_intr),
321	DEVMETHOD(pic_map_intr,		ti_aintc_map_intr),
322	DEVMETHOD(pic_post_filter,	ti_aintc_post_filter),
323	DEVMETHOD(pic_post_ithread,	ti_aintc_post_ithread),
324	DEVMETHOD(pic_pre_ithread,	ti_aintc_pre_ithread),
325#endif
326
327	{ 0, 0 }
328};
329
330static driver_t ti_aintc_driver = {
331	"aintc",
332	ti_aintc_methods,
333	sizeof(struct ti_aintc_softc),
334};
335
336static devclass_t ti_aintc_devclass;
337
338EARLY_DRIVER_MODULE(aintc, simplebus, ti_aintc_driver, ti_aintc_devclass,
339    0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE);
340SIMPLEBUS_PNP_INFO(compat_data);
341
342#ifndef INTRNG
343int
344arm_get_next_irq(int last_irq)
345{
346	struct ti_aintc_softc *sc = ti_aintc_sc;
347	uint32_t active_irq;
348
349	/* Get the next active interrupt */
350	active_irq = aintc_read_4(sc, INTC_SIR_IRQ);
351
352	/* Check for spurious interrupt */
353	if ((active_irq & 0xffffff80)) {
354		device_printf(sc->sc_dev,
355		    "Spurious interrupt detected (0x%08x)\n", active_irq);
356		aintc_write_4(sc, INTC_SIR_IRQ, 0);
357		return -1;
358	}
359
360	if (active_irq != last_irq)
361		return active_irq;
362	else
363		return -1;
364}
365
366void
367arm_mask_irq(uintptr_t nb)
368{
369	struct ti_aintc_softc *sc = ti_aintc_sc;
370
371	aintc_write_4(sc, INTC_MIR_SET(nb >> 5), (1UL << (nb & 0x1F)));
372	aintc_write_4(sc, INTC_CONTROL, 1); /* EOI */
373}
374
375void
376arm_unmask_irq(uintptr_t nb)
377{
378	struct ti_aintc_softc *sc = ti_aintc_sc;
379
380	arm_irq_memory_barrier(nb);
381	aintc_write_4(sc, INTC_MIR_CLEAR(nb >> 5), (1UL << (nb & 0x1F)));
382}
383#endif
384