1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2019 Michal Meloun <mmel@FreeBSD.org>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 *
27 */
28
29#include <sys/param.h>
30#include <sys/systm.h>
31#include <sys/bus.h>
32
33#include <sys/bitset.h>
34#include <sys/kernel.h>
35#include <sys/proc.h>
36#include <sys/rman.h>
37#include <sys/lock.h>
38#include <sys/module.h>
39#include <sys/mutex.h>
40
41#include <machine/bus.h>
42#include <machine/intr.h>
43#include <machine/resource.h>
44
45#include <dev/fdt/simplebus.h>
46
47#include <dev/ofw/ofw_bus.h>
48#include <dev/ofw/ofw_bus_subr.h>
49
50#include "msi_if.h"
51#include "pic_if.h"
52
53#define	MV_AP806_SEI_LOCK(_sc)		mtx_lock(&(_sc)->mtx)
54#define	MV_AP806_SEI_UNLOCK(_sc)	mtx_unlock(&(_sc)->mtx)
55#define	MV_AP806_SEI_LOCK_INIT(_sc)	mtx_init(&_sc->mtx, 			\
56	    device_get_nameunit(_sc->dev), "mv_ap806_sei", MTX_DEF)
57#define	MV_AP806_SEI_LOCK_DESTROY(_sc)	mtx_destroy(&_sc->mtx);
58#define	MV_AP806_SEI_ASSERT_LOCKED(_sc)	mtx_assert(&_sc->mtx, MA_OWNED);
59#define	MV_AP806_SEI_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->mtx, MA_NOTOWNED);
60
61#define GICP_SECR0		0x00
62#define GICP_SECR1		0x04
63#define GICP_SECR(i)		(0x00  + (((i)/32) * 0x4))
64#define GICP_SECR_BIT(i)	((i) % 32)
65#define GICP_SEMR0		0x20
66#define GICP_SEMR1		0x24
67#define GICP_SEMR(i)		(0x20  + (((i)/32) * 0x4))
68#define GICP_SEMR_BIT(i)	((i) % 32)
69
70#define	MV_AP806_SEI_AP_FIRST	0
71#define	MV_AP806_SEI_AP_SIZE	21
72#define	MV_AP806_SEI_CP_FIRST	21
73#define	MV_AP806_SEI_CP_SIZE	43
74#define	MV_AP806_SEI_MAX_NIRQS	(MV_AP806_SEI_AP_SIZE + MV_AP806_SEI_CP_SIZE)
75
76#define	MV_AP806_SEI_SETSPI_OFFSET	0x30
77
78BITSET_DEFINE(sei_msi_bitmap, MV_AP806_SEI_CP_SIZE);
79
80struct mv_ap806_sei_irqsrc {
81	struct intr_irqsrc	isrc;
82	u_int			irq;
83};
84
85struct mv_ap806_sei_softc {
86	device_t		dev;
87	struct resource		*mem_res;
88	struct resource		*irq_res;
89	void			*irq_ih;
90	struct mtx		mtx;
91
92	struct mv_ap806_sei_irqsrc *isrcs;
93
94	struct sei_msi_bitmap	msi_bitmap;
95};
96
97static struct ofw_compat_data compat_data[] = {
98	{"marvell,ap806-sei", 1},
99	{NULL,             0}
100};
101
102#define	RD4(sc, reg)		bus_read_4((sc)->mem_res, (reg))
103#define	WR4(sc, reg, val)	bus_write_4((sc)->mem_res, (reg), (val))
104
105static msi_alloc_msi_t mv_ap806_sei_alloc_msi;
106static msi_release_msi_t mv_ap806_sei_release_msi;
107static msi_map_msi_t mv_ap806_sei_map_msi;
108
109static inline void
110mv_ap806_sei_isrc_mask(struct mv_ap806_sei_softc *sc,
111     struct mv_ap806_sei_irqsrc *sisrc, uint32_t val)
112{
113	uint32_t tmp;
114	int bit;
115
116	bit = GICP_SEMR_BIT(sisrc->irq);
117	MV_AP806_SEI_LOCK(sc);
118	tmp = RD4(sc, GICP_SEMR(sisrc->irq));
119	if (val != 0)
120		tmp |= 1 << bit;
121	else
122		tmp &= ~(1 << bit);
123	WR4(sc, GICP_SEMR(sisrc->irq), tmp);
124	MV_AP806_SEI_UNLOCK(sc);
125}
126
127static inline void
128mv_ap806_sei_isrc_eoi(struct mv_ap806_sei_softc *sc,
129     struct mv_ap806_sei_irqsrc *sisrc)
130{
131
132	WR4(sc, GICP_SECR(sisrc->irq), GICP_SECR_BIT(sisrc->irq));
133}
134
135static void
136mv_ap806_sei_enable_intr(device_t dev, struct intr_irqsrc *isrc)
137{
138	struct mv_ap806_sei_softc *sc;
139	struct mv_ap806_sei_irqsrc *sisrc;
140
141	sc = device_get_softc(dev);
142	sisrc = (struct mv_ap806_sei_irqsrc *)isrc;
143	mv_ap806_sei_isrc_mask(sc, sisrc, 0);
144}
145
146static void
147mv_ap806_sei_disable_intr(device_t dev, struct intr_irqsrc *isrc)
148{
149	struct mv_ap806_sei_softc *sc;
150	struct mv_ap806_sei_irqsrc *sisrc;
151
152	sc = device_get_softc(dev);
153	sisrc = (struct mv_ap806_sei_irqsrc *)isrc;
154	mv_ap806_sei_isrc_mask(sc, sisrc, 1);
155}
156
157static int
158mv_ap806_sei_map(device_t dev, struct intr_map_data *data, u_int *irqp)
159{
160	struct intr_map_data_fdt *daf;
161	u_int irq;
162
163	if (data->type != INTR_MAP_DATA_FDT)
164		return (ENOTSUP);
165
166	daf = (struct intr_map_data_fdt *)data;
167	if (daf->ncells != 1)
168		return (EINVAL);
169
170	if (daf->cells[0] < MV_AP806_SEI_AP_FIRST ||
171	    daf->cells[0] >= MV_AP806_SEI_AP_FIRST + MV_AP806_SEI_AP_SIZE)
172		return (EINVAL);
173
174	irq = daf->cells[0];
175	if (irqp != NULL)
176		*irqp = irq;
177
178	return(0);
179}
180
181static int
182mv_ap806_sei_map_intr(device_t dev, struct intr_map_data *data,
183    struct intr_irqsrc **isrcp)
184{
185	struct mv_ap806_sei_softc *sc;
186	u_int irq;
187	int rv;
188
189	sc = device_get_softc(dev);
190	rv = mv_ap806_sei_map(dev, data, &irq);
191	if (rv == 0)
192		*isrcp = &sc->isrcs[irq].isrc;
193
194	return (rv);
195}
196
197static int
198mv_ap806_sei_setup_intr(device_t dev, struct intr_irqsrc *isrc,
199    struct resource *res, struct intr_map_data *data)
200{
201	struct mv_ap806_sei_softc *sc;
202	struct mv_ap806_sei_irqsrc *sisrc;
203	u_int irq;
204	int rv;
205
206	sc = device_get_softc(dev);
207	sisrc = (struct mv_ap806_sei_irqsrc *)isrc;
208	if (data == NULL)
209		return (ENOTSUP);
210	rv = mv_ap806_sei_map(dev, data, &irq);
211	if (rv != 0)
212		return (rv);
213	if (irq != sisrc->irq)
214		return (EINVAL);
215	mv_ap806_sei_isrc_mask(sc, sisrc, 0);
216	return (0);
217}
218
219static int
220mv_ap806_sei_teardown_intr(device_t dev, struct intr_irqsrc *isrc,
221    struct resource *res, struct intr_map_data *data)
222{
223	struct mv_ap806_sei_softc *sc;
224	struct mv_ap806_sei_irqsrc *sisrc;
225
226	sc = device_get_softc(dev);
227	sisrc = (struct mv_ap806_sei_irqsrc *)isrc;
228
229	mv_ap806_sei_isrc_mask(sc, sisrc, 1);
230	return (0);
231}
232
233static void
234mv_ap806_sei_pre_ithread(device_t dev, struct intr_irqsrc *isrc)
235{
236	struct mv_ap806_sei_softc *sc;
237	struct mv_ap806_sei_irqsrc *sisrc;
238
239	sc = device_get_softc(dev);
240	sisrc = (struct mv_ap806_sei_irqsrc *)isrc;
241
242	mv_ap806_sei_isrc_mask(sc, sisrc, 1);
243	mv_ap806_sei_isrc_eoi(sc, sisrc);
244}
245
246static void
247mv_ap806_sei_post_ithread(device_t dev, struct intr_irqsrc *isrc)
248{
249	struct mv_ap806_sei_softc *sc;
250	struct mv_ap806_sei_irqsrc *sisrc;
251
252	sc = device_get_softc(dev);
253	sisrc = (struct mv_ap806_sei_irqsrc *)isrc;
254
255	mv_ap806_sei_isrc_mask(sc, sisrc, 1);
256}
257
258static void
259mv_ap806_sei_post_filter(device_t dev, struct intr_irqsrc *isrc)
260{
261	struct mv_ap806_sei_softc *sc;
262	struct mv_ap806_sei_irqsrc *sisrc;
263
264	sc = device_get_softc(dev);
265	sisrc = (struct mv_ap806_sei_irqsrc *)isrc;
266
267	mv_ap806_sei_isrc_mask(sc, sisrc, 1);
268	mv_ap806_sei_isrc_eoi(sc, sisrc);
269}
270
271/* ----------------------------------------------------------------------------
272 *
273 *		B u s    i n t e r f a c e
274 */
275static int
276mv_ap806_sei_intr(void *arg)
277{
278	struct mv_ap806_sei_softc *sc;
279	struct mv_ap806_sei_irqsrc *sirq;
280	struct trapframe *tf;
281	uint64_t cause;
282	u_int irq;
283
284	sc = (struct mv_ap806_sei_softc *)arg;
285	tf = curthread->td_intr_frame;
286	while (1) {
287		cause = RD4(sc, GICP_SECR1);
288		cause <<= 32;
289		cause |= RD4(sc, GICP_SECR0);
290
291		irq = ffsll(cause);
292		if (irq == 0) break;
293		irq--;
294		sirq = &sc->isrcs[irq];
295		if (intr_isrc_dispatch(&sirq->isrc, tf) != 0) {
296			mv_ap806_sei_isrc_mask(sc, sirq, 0);
297			mv_ap806_sei_isrc_eoi(sc, sirq);
298			device_printf(sc->dev,
299			    "Stray irq %u disabled\n", irq);
300		}
301	}
302
303	return (FILTER_HANDLED);
304}
305
306static int
307mv_ap806_sei_probe(device_t dev)
308{
309
310	if (!ofw_bus_status_okay(dev))
311		return (ENXIO);
312
313	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
314		return (ENXIO);
315
316	device_set_desc(dev, "Marvell SEI");
317	return (BUS_PROBE_DEFAULT);
318}
319
320static int
321mv_ap806_sei_attach(device_t dev)
322{
323	struct mv_ap806_sei_softc *sc;
324	phandle_t xref, node;
325	uint32_t irq;
326	const char *name;
327	int rv, rid;
328
329	sc = device_get_softc(dev);
330	sc->dev = dev;
331	node = ofw_bus_get_node(dev);
332	MV_AP806_SEI_LOCK_INIT(sc);
333
334	/* Allocate resources. */
335	rid = 0;
336	sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
337	    RF_ACTIVE);
338	if (sc->mem_res == NULL) {
339		device_printf(dev, "Cannot allocate memory resources\n");
340		rv = ENXIO;
341		goto fail;
342	}
343
344	rid = 0;
345	sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE);
346	if (sc->irq_res == NULL) {
347		device_printf(dev, "Cannot allocate IRQ resources\n");
348		rv = ENXIO;
349		goto fail;
350	}
351
352	/* Mask all interrupts) */
353	WR4(sc, GICP_SEMR0, 0xFFFFFFFF);
354	WR4(sc, GICP_SEMR1, 0xFFFFFFFF);
355
356	/* Create all interrupt sources */
357	sc->isrcs = malloc(sizeof(*sc->isrcs) * MV_AP806_SEI_MAX_NIRQS,
358	    M_DEVBUF, M_WAITOK | M_ZERO);
359	name = device_get_nameunit(sc->dev);
360	for (irq = 0; irq < MV_AP806_SEI_MAX_NIRQS; irq++) {
361		sc->isrcs[irq].irq = irq;
362		rv = intr_isrc_register(&sc->isrcs[irq].isrc,
363		    sc->dev, 0, "%s,%u", name, irq);
364		if (rv != 0)
365			goto fail; /* XXX deregister ISRCs */
366	}
367	xref = OF_xref_from_node(node);
368	if (intr_pic_register(dev, xref) == NULL) {
369		device_printf(dev, "Cannot register SEI\n");
370		rv = ENXIO;
371		goto fail;
372	}
373	if (bus_setup_intr(dev, sc->irq_res,INTR_TYPE_MISC | INTR_MPSAFE,
374	    mv_ap806_sei_intr, NULL, sc, &sc->irq_ih)) {
375		device_printf(dev,
376		    "Unable to register interrupt handler\n");
377		rv = ENXIO;
378		goto fail;
379	}
380
381	/*
382	 * Bitmap of all IRQs.
383	 * 1 - available, 0 - used.
384	 */
385	BIT_FILL(MV_AP806_SEI_CP_SIZE, &sc->msi_bitmap);
386
387	OF_device_register_xref(xref, dev);
388	return (0);
389
390fail:
391	if (sc->irq_ih != NULL)
392		bus_teardown_intr(dev, sc->irq_res, sc->irq_ih);
393	if (sc->irq_res != NULL)
394		bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq_res);
395	if (sc->mem_res != NULL)
396		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->mem_res);
397	MV_AP806_SEI_LOCK_DESTROY(sc);
398	return (ENXIO);
399}
400
401static int
402mv_ap806_sei_detach(device_t dev)
403{
404
405	return (EBUSY);
406}
407
408static int
409mv_ap806_sei_alloc_msi(device_t dev, device_t child, int count, int maxcount,
410    device_t *pic, struct intr_irqsrc **srcs)
411{
412	struct mv_ap806_sei_softc *sc;
413	int i, ret = 0, vector;
414
415	sc = device_get_softc(dev);
416
417	for (i = 0; i < count; i++) {
418		/*
419		 * Find first available MSI vector represented by first set bit
420		 * in the bitmap. BIT_FFS starts the count from 1,
421		 * 0 means that nothing was found.
422		 */
423		vector = BIT_FFS_AT(MV_AP806_SEI_CP_SIZE, &sc->msi_bitmap, 0);
424		if (vector == 0) {
425			ret = ENOMEM;
426			i--;
427			goto fail;
428		}
429
430		vector--;
431		BIT_CLR(MV_AP806_SEI_CP_SIZE, vector, &sc->msi_bitmap);
432		vector += MV_AP806_SEI_CP_FIRST;
433
434		srcs[i] = &sc->isrcs[vector].isrc;
435	}
436
437	return (ret);
438fail:
439	mv_ap806_sei_release_msi(dev, child, i + 1, srcs);
440	return (ret);
441}
442
443static int
444mv_ap806_sei_release_msi(device_t dev, device_t child, int count, struct intr_irqsrc **srcs)
445{
446	struct mv_ap806_sei_softc *sc;
447	int i;
448
449	sc = device_get_softc(dev);
450
451	for (i = 0; i < count; i++) {
452		BIT_SET(MV_AP806_SEI_CP_SIZE,
453		    srcs[i]->isrc_irq - MV_AP806_SEI_CP_FIRST,
454		    &sc->msi_bitmap);
455	}
456
457	return (0);
458}
459
460static int
461mv_ap806_sei_map_msi(device_t dev, device_t child, struct intr_irqsrc *isrc,
462    uint64_t *addr, uint32_t *data)
463{
464	struct mv_ap806_sei_softc *sc;
465
466	sc = device_get_softc(dev);
467
468	*addr = rman_get_start(sc->mem_res) + MV_AP806_SEI_SETSPI_OFFSET;
469	*data = isrc->isrc_irq;
470
471	return (0);
472}
473
474static device_method_t mv_ap806_sei_methods[] = {
475	/* Device interface */
476	DEVMETHOD(device_probe,		mv_ap806_sei_probe),
477	DEVMETHOD(device_attach,	mv_ap806_sei_attach),
478	DEVMETHOD(device_detach,	mv_ap806_sei_detach),
479
480	/* Interrupt controller interface */
481	DEVMETHOD(pic_disable_intr,	mv_ap806_sei_disable_intr),
482	DEVMETHOD(pic_enable_intr,	mv_ap806_sei_enable_intr),
483	DEVMETHOD(pic_map_intr,		mv_ap806_sei_map_intr),
484	DEVMETHOD(pic_setup_intr,	mv_ap806_sei_setup_intr),
485	DEVMETHOD(pic_teardown_intr,	mv_ap806_sei_teardown_intr),
486	DEVMETHOD(pic_post_filter,	mv_ap806_sei_post_filter),
487	DEVMETHOD(pic_post_ithread,	mv_ap806_sei_post_ithread),
488	DEVMETHOD(pic_pre_ithread,	mv_ap806_sei_pre_ithread),
489
490	/* MSI interface */
491	DEVMETHOD(msi_alloc_msi,	mv_ap806_sei_alloc_msi),
492	DEVMETHOD(msi_release_msi,	mv_ap806_sei_release_msi),
493	DEVMETHOD(msi_map_msi,		mv_ap806_sei_map_msi),
494
495	DEVMETHOD_END
496};
497
498static driver_t mv_ap806_sei_driver = {
499	"mv_ap806_sei",
500	mv_ap806_sei_methods,
501	sizeof(struct mv_ap806_sei_softc),
502};
503
504EARLY_DRIVER_MODULE(mv_ap806_sei, simplebus, mv_ap806_sei_driver, 0, 0,
505    BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE);
506