1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2018 Rubicon Communications, LLC (Netgate)
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#include <sys/param.h>
29#include <sys/systm.h>
30#include <sys/bus.h>
31
32#include <sys/kernel.h>
33#include <sys/module.h>
34#include <sys/rman.h>
35#include <sys/lock.h>
36#include <sys/mutex.h>
37
38#include <machine/bus.h>
39#include <machine/resource.h>
40#include <machine/intr.h>
41
42#include <dev/fdt/simplebus.h>
43
44#include <dev/ofw/ofw_bus.h>
45#include <dev/ofw/ofw_bus_subr.h>
46
47#include <dt-bindings/interrupt-controller/irq.h>
48
49#include "pic_if.h"
50#include "msi_if.h"
51
52#define	ICU_TYPE_NSR		1
53#define	ICU_TYPE_SEI		2
54
55#define	ICU_GRP_NSR		0x0
56#define	ICU_GRP_SR		0x1
57#define	ICU_GRP_SEI		0x4
58#define	ICU_GRP_REI		0x5
59
60#define	ICU_SETSPI_NSR_AL	0x10
61#define	ICU_SETSPI_NSR_AH	0x14
62#define	ICU_CLRSPI_NSR_AL	0x18
63#define	ICU_CLRSPI_NSR_AH	0x1c
64#define	ICU_SETSPI_SEI_AL	0x50
65#define	ICU_SETSPI_SEI_AH	0x54
66#define	ICU_INT_CFG(x)	(0x100 + (x) * 4)
67#define	 ICU_INT_ENABLE		(1 << 24)
68#define	 ICU_INT_EDGE		(1 << 28)
69#define	 ICU_INT_GROUP_SHIFT	29
70#define	 ICU_INT_MASK		0x3ff
71
72#define	ICU_INT_SATA0		109
73#define	ICU_INT_SATA1		107
74
75#define	MV_CP110_ICU_MAX_NIRQS	207
76
77#define	MV_CP110_ICU_CLRSPI_OFFSET	0x8
78
79struct mv_cp110_icu_softc {
80	device_t		dev;
81	device_t		parent;
82	struct resource		*res;
83	struct intr_map_data_fdt *parent_map_data;
84	bool			initialized;
85	int			type;
86};
87
88static struct resource_spec mv_cp110_icu_res_spec[] = {
89	{ SYS_RES_MEMORY,	0,	RF_ACTIVE | RF_SHAREABLE },
90	{ -1, 0 }
91};
92
93static struct ofw_compat_data compat_data[] = {
94	{"marvell,cp110-icu-nsr",	ICU_TYPE_NSR},
95	{"marvell,cp110-icu-sei",	ICU_TYPE_SEI},
96	{NULL,				0}
97};
98
99#define	RD4(sc, reg)		bus_read_4((sc)->res, (reg))
100#define	WR4(sc, reg, val)	bus_write_4((sc)->res, (reg), (val))
101
102static int
103mv_cp110_icu_probe(device_t dev)
104{
105
106	if (!ofw_bus_status_okay(dev))
107		return (ENXIO);
108
109	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
110		return (ENXIO);
111
112	device_set_desc(dev, "Marvell Interrupt Consolidation Unit");
113	return (BUS_PROBE_DEFAULT);
114}
115
116static int
117mv_cp110_icu_attach(device_t dev)
118{
119	struct mv_cp110_icu_softc *sc;
120	phandle_t node, msi_parent;
121	uint32_t reg, icu_grp;
122	int i;
123
124	sc = device_get_softc(dev);
125	sc->dev = dev;
126	node = ofw_bus_get_node(dev);
127	sc->type = (int)ofw_bus_search_compatible(dev, compat_data)->ocd_data;
128	sc->initialized = false;
129
130	if (OF_getencprop(node, "msi-parent", &msi_parent,
131	    sizeof(phandle_t)) <= 0) {
132		device_printf(dev, "cannot find msi-parent property\n");
133		return (ENXIO);
134	}
135
136	if ((sc->parent = OF_device_from_xref(msi_parent)) == NULL) {
137		device_printf(dev, "cannot find msi-parent device\n");
138		return (ENXIO);
139	}
140	if (bus_alloc_resources(dev, mv_cp110_icu_res_spec, &sc->res) != 0) {
141		device_printf(dev, "cannot allocate resources for device\n");
142		return (ENXIO);
143	}
144
145	if (intr_pic_register(dev, OF_xref_from_node(node)) == NULL) {
146		device_printf(dev, "Cannot register ICU\n");
147		goto fail;
148	}
149
150	/* Allocate GICP/SEI compatible mapping entry (2 cells) */
151	sc->parent_map_data = (struct intr_map_data_fdt *)intr_alloc_map_data(
152	    INTR_MAP_DATA_FDT, sizeof(struct intr_map_data_fdt) +
153	    + 3 * sizeof(phandle_t), M_WAITOK | M_ZERO);
154
155	/* Clear any previous mapping done by firmware. */
156	for (i = 0; i < MV_CP110_ICU_MAX_NIRQS; i++) {
157		reg = RD4(sc, ICU_INT_CFG(i));
158		icu_grp = reg >> ICU_INT_GROUP_SHIFT;
159
160		if (icu_grp == ICU_GRP_NSR || icu_grp == ICU_GRP_SEI)
161			WR4(sc, ICU_INT_CFG(i), 0);
162	}
163
164	return (0);
165
166fail:
167	bus_release_resources(dev, mv_cp110_icu_res_spec, &sc->res);
168	return (ENXIO);
169}
170
171static struct intr_map_data *
172mv_cp110_icu_convert_map_data(struct mv_cp110_icu_softc *sc, struct intr_map_data *data)
173{
174	struct intr_map_data_fdt *daf;
175	uint32_t reg, irq_no, irq_type;
176
177	daf = (struct intr_map_data_fdt *)data;
178	if (daf->ncells != 2)
179		return (NULL);
180
181	irq_no = daf->cells[0];
182	if (irq_no >= MV_CP110_ICU_MAX_NIRQS)
183		return (NULL);
184
185	irq_type = daf->cells[1];
186	if (irq_type != IRQ_TYPE_LEVEL_HIGH &&
187	    irq_type != IRQ_TYPE_EDGE_RISING)
188		return (NULL);
189
190	/* ICU -> GICP/SEI mapping is set in mv_cp110_icu_map_intr. */
191	reg = RD4(sc, ICU_INT_CFG(irq_no));
192
193	/* Construct GICP compatible mapping. */
194	sc->parent_map_data->ncells = 2;
195	sc->parent_map_data->cells[0] = reg & ICU_INT_MASK;
196	sc->parent_map_data->cells[1] = irq_type;
197
198	return ((struct intr_map_data *)sc->parent_map_data);
199}
200
201static int
202mv_cp110_icu_detach(device_t dev)
203{
204
205	return (EBUSY);
206}
207
208static int
209mv_cp110_icu_activate_intr(device_t dev, struct intr_irqsrc *isrc,
210    struct resource *res, struct intr_map_data *data)
211{
212	struct mv_cp110_icu_softc *sc;
213
214	sc = device_get_softc(dev);
215	data = mv_cp110_icu_convert_map_data(sc, data);
216	if (data == NULL)
217		return (EINVAL);
218	return (PIC_ACTIVATE_INTR(sc->parent, isrc, res, data));
219}
220
221static void
222mv_cp110_icu_enable_intr(device_t dev, struct intr_irqsrc *isrc)
223{
224	struct mv_cp110_icu_softc *sc;
225	sc = device_get_softc(dev);
226
227	PIC_ENABLE_INTR(sc->parent, isrc);
228}
229
230static void
231mv_cp110_icu_disable_intr(device_t dev, struct intr_irqsrc *isrc)
232{
233	struct mv_cp110_icu_softc *sc;
234
235	sc = device_get_softc(dev);
236
237	PIC_DISABLE_INTR(sc->parent, isrc);
238}
239
240static void
241mv_cp110_icu_init(struct mv_cp110_icu_softc *sc, uint64_t addr)
242{
243
244	if (sc->initialized)
245		return;
246
247	switch (sc->type) {
248	case ICU_TYPE_NSR:
249		WR4(sc, ICU_SETSPI_NSR_AL, addr & UINT32_MAX);
250		WR4(sc, ICU_SETSPI_NSR_AH, (addr >> 32) & UINT32_MAX);
251		addr += MV_CP110_ICU_CLRSPI_OFFSET;
252		WR4(sc, ICU_CLRSPI_NSR_AL, addr & UINT32_MAX);
253		WR4(sc, ICU_CLRSPI_NSR_AH, (addr >> 32) & UINT32_MAX);
254		break;
255	case ICU_TYPE_SEI:
256		WR4(sc, ICU_SETSPI_SEI_AL, addr & UINT32_MAX);
257		WR4(sc, ICU_SETSPI_SEI_AH, (addr >> 32) & UINT32_MAX);
258		break;
259	default:
260		panic("Unkown ICU type.");
261	}
262
263	sc->initialized = true;
264}
265
266static int
267mv_cp110_icu_map_intr(device_t dev, struct intr_map_data *data,
268    struct intr_irqsrc **isrcp)
269{
270	struct mv_cp110_icu_softc *sc;
271	struct intr_map_data_fdt *daf;
272	uint32_t vector, irq_no, irq_type;
273	uint64_t addr;
274	int ret;
275
276	sc = device_get_softc(dev);
277
278	if (data->type != INTR_MAP_DATA_FDT)
279		return (ENOTSUP);
280
281	/* Parse original */
282	daf = (struct intr_map_data_fdt *)data;
283	if (daf->ncells != 2)
284		return (EINVAL);
285
286	irq_no = daf->cells[0];
287	if (irq_no >= MV_CP110_ICU_MAX_NIRQS)
288		return (EINVAL);
289
290	irq_type = daf->cells[1];
291	if (irq_type != IRQ_TYPE_LEVEL_HIGH &&
292	    irq_type != IRQ_TYPE_EDGE_RISING)
293		return (EINVAL);
294
295	/*
296	 * Allocate MSI vector.
297	 * We don't use intr_alloc_msi wrapper, since it registers a new irq
298	 * in the kernel. In our case irq was already added by the ofw code.
299	 */
300	ret = MSI_ALLOC_MSI(sc->parent, dev, 1, 1, NULL, isrcp);
301	if (ret != 0)
302		return (ret);
303
304	ret = MSI_MAP_MSI(sc->parent, dev, *isrcp, &addr, &vector);
305	if (ret != 0)
306		goto fail;
307
308	mv_cp110_icu_init(sc, addr);
309	vector |= ICU_INT_ENABLE;
310
311	if (sc->type == ICU_TYPE_NSR)
312		vector |= ICU_GRP_NSR << ICU_INT_GROUP_SHIFT;
313	else
314		vector |= ICU_GRP_SEI << ICU_INT_GROUP_SHIFT;
315
316	if (irq_type & IRQ_TYPE_EDGE_BOTH)
317		vector |= ICU_INT_EDGE;
318
319	WR4(sc, ICU_INT_CFG(irq_no), vector);
320
321	/*
322	 * SATA controller has two ports, each gets its own interrupt.
323	 * The problem is that only one irq is described in dts.
324	 * Also ahci_generic driver supports only one irq per controller.
325	 * As a workaround map both interrupts when one of them is allocated.
326	 * This allows us to use both SATA ports.
327	 */
328	if (irq_no == ICU_INT_SATA0)
329		WR4(sc, ICU_INT_CFG(ICU_INT_SATA1), vector);
330	if (irq_no == ICU_INT_SATA1)
331		WR4(sc, ICU_INT_CFG(ICU_INT_SATA0), vector);
332
333	(*isrcp)->isrc_dev = sc->dev;
334	return (ret);
335
336fail:
337	if (*isrcp != NULL)
338		MSI_RELEASE_MSI(sc->parent, dev, 1, isrcp);
339
340	return (ret);
341}
342
343static int
344mv_cp110_icu_deactivate_intr(device_t dev, struct intr_irqsrc *isrc,
345    struct resource *res, struct intr_map_data *data)
346{
347	struct mv_cp110_icu_softc *sc;
348	struct intr_map_data_fdt *daf;
349	int irq_no, ret;
350
351	if (data->type != INTR_MAP_DATA_FDT)
352		return (ENOTSUP);
353
354	sc = device_get_softc(dev);
355	daf = (struct intr_map_data_fdt *)data;
356	if (daf->ncells != 2)
357		return (EINVAL);
358
359	irq_no = daf->cells[0];
360	data = mv_cp110_icu_convert_map_data(sc, data);
361	if (data == NULL)
362		return (EINVAL);
363
364	/* Clear the mapping. */
365	WR4(sc, ICU_INT_CFG(irq_no), 0);
366
367	ret = PIC_DEACTIVATE_INTR(sc->parent, isrc, res, data);
368	if (ret != 0)
369		return (ret);
370
371	return (MSI_RELEASE_MSI(sc->parent, dev, 1, &isrc));
372}
373
374static int
375mv_cp110_icu_setup_intr(device_t dev, struct intr_irqsrc *isrc,
376    struct resource *res, struct intr_map_data *data)
377{
378	struct mv_cp110_icu_softc *sc;
379
380	sc = device_get_softc(dev);
381	data = mv_cp110_icu_convert_map_data(sc, data);
382	if (data == NULL)
383		return (EINVAL);
384
385	return (PIC_SETUP_INTR(sc->parent, isrc, res, data));
386}
387
388static int
389mv_cp110_icu_teardown_intr(device_t dev, struct intr_irqsrc *isrc,
390    struct resource *res, struct intr_map_data *data)
391{
392	struct mv_cp110_icu_softc *sc;
393
394	sc = device_get_softc(dev);
395	data = mv_cp110_icu_convert_map_data(sc, data);
396	if (data == NULL)
397		return (EINVAL);
398
399	return (PIC_TEARDOWN_INTR(sc->parent, isrc, res, data));
400}
401
402static void
403mv_cp110_icu_pre_ithread(device_t dev, struct intr_irqsrc *isrc)
404{
405	struct mv_cp110_icu_softc *sc;
406
407	sc = device_get_softc(dev);
408
409	PIC_PRE_ITHREAD(sc->parent, isrc);
410}
411
412static void
413mv_cp110_icu_post_ithread(device_t dev, struct intr_irqsrc *isrc)
414{
415	struct mv_cp110_icu_softc *sc;
416
417	sc = device_get_softc(dev);
418
419	PIC_POST_ITHREAD(sc->parent, isrc);
420}
421
422static void
423mv_cp110_icu_post_filter(device_t dev, struct intr_irqsrc *isrc)
424{
425	struct mv_cp110_icu_softc *sc;
426
427	sc = device_get_softc(dev);
428
429	PIC_POST_FILTER(sc->parent, isrc);
430}
431
432static device_method_t mv_cp110_icu_methods[] = {
433	/* Device interface */
434	DEVMETHOD(device_probe,		mv_cp110_icu_probe),
435	DEVMETHOD(device_attach,	mv_cp110_icu_attach),
436	DEVMETHOD(device_detach,	mv_cp110_icu_detach),
437
438	/* Interrupt controller interface */
439	DEVMETHOD(pic_activate_intr,	mv_cp110_icu_activate_intr),
440	DEVMETHOD(pic_disable_intr,	mv_cp110_icu_disable_intr),
441	DEVMETHOD(pic_enable_intr,	mv_cp110_icu_enable_intr),
442	DEVMETHOD(pic_map_intr,		mv_cp110_icu_map_intr),
443	DEVMETHOD(pic_deactivate_intr,	mv_cp110_icu_deactivate_intr),
444	DEVMETHOD(pic_setup_intr,	mv_cp110_icu_setup_intr),
445	DEVMETHOD(pic_teardown_intr,	mv_cp110_icu_teardown_intr),
446	DEVMETHOD(pic_post_filter,	mv_cp110_icu_post_filter),
447	DEVMETHOD(pic_post_ithread,	mv_cp110_icu_post_ithread),
448	DEVMETHOD(pic_pre_ithread,	mv_cp110_icu_pre_ithread),
449
450	DEVMETHOD_END
451};
452
453static driver_t mv_cp110_icu_driver = {
454	"mv_cp110_icu",
455	mv_cp110_icu_methods,
456	sizeof(struct mv_cp110_icu_softc),
457};
458
459EARLY_DRIVER_MODULE(mv_cp110_icu, mv_cp110_icu_bus, mv_cp110_icu_driver, 0, 0,
460    BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LAST);
461