1/*	$OpenBSD: mvicu.c,v 1.8 2021/10/24 17:52:26 mpi Exp $	*/
2/*
3 * Copyright (c) 2018 Mark Kettenis <kettenis@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include <sys/param.h>
19#include <sys/systm.h>
20#include <sys/device.h>
21#include <sys/malloc.h>
22
23#include <machine/intr.h>
24#include <machine/bus.h>
25#include <machine/fdt.h>
26
27#include <dev/ofw/openfirm.h>
28#include <dev/ofw/fdt.h>
29
30/* Registers. */
31#define ICU_SETSPI_NSR_AL	0x10
32#define ICU_SETSPI_NSR_AH	0x14
33#define ICU_CLRSPI_NSR_AL	0x18
34#define ICU_CLRSPI_NSR_AH	0x1c
35#define ICU_SET_SEI_AL		0x50
36#define ICU_SET_SEI_AH		0x54
37#define ICU_CLR_SEI_AL		0x58
38#define ICU_CLR_SEI_AH		0x5c
39#define ICU_INT_CFG(x)	(0x100 + (x) * 4)
40#define  ICU_INT_ENABLE		(1 << 24)
41#define  ICU_INT_EDGE		(1 << 28)
42#define  ICU_INT_GROUP_SHIFT	29
43#define  ICU_INT_MASK		0x3ff
44
45#define GICP_SETSPI_NSR		0x00
46#define GICP_CLRSPI_NSR		0x08
47
48/* Devices */
49#define ICU_DEVICE_SATA0	109
50#define ICU_DEVICE_SATA1	107
51#define ICU_DEVICE_NIRQ		207
52
53/* Groups. */
54#define ICU_GRP_NSR		0x0
55#define ICU_GRP_SR		0x1
56#define ICU_GRP_SEI		0x4
57#define ICU_GRP_REI		0x5
58
59#define HREAD4(sc, reg)							\
60	(bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
61#define HWRITE4(sc, reg, val)						\
62	bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
63
64struct mvicu_softc;
65struct mvicu_subnode {
66	struct mvicu_softc	*sn_sc;
67	int			sn_group;
68	struct interrupt_controller sn_ic;
69	struct interrupt_controller *sn_parent_ic;
70};
71
72struct mvicu_softc {
73	struct device		sc_dev;
74	bus_space_tag_t		sc_iot;
75	bus_space_handle_t	sc_ioh;
76
77	uint64_t		sc_nsr_addr;
78	uint64_t		sc_sei_addr;
79
80	int			sc_legacy;
81	struct mvicu_subnode	*sc_nodes;
82};
83
84int mvicu_match(struct device *, void *, void *);
85void mvicu_attach(struct device *, struct device *, void *);
86
87const struct cfattach	mvicu_ca = {
88	sizeof (struct mvicu_softc), mvicu_match, mvicu_attach
89};
90
91struct cfdriver mvicu_cd = {
92	NULL, "mvicu", DV_DULL
93};
94
95void	mvicu_register(struct mvicu_softc *, int, int);
96void	*mvicu_intr_establish(void *, int *, int, struct cpu_info *,
97	    int (*)(void *), void *, char *);
98void	mvicu_intr_disestablish(void *);
99void	mvicu_intr_barrier(void *);
100
101int
102mvicu_match(struct device *parent, void *match, void *aux)
103{
104	struct fdt_attach_args *faa = aux;
105
106	return OF_is_compatible(faa->fa_node, "marvell,cp110-icu");
107}
108
109void
110mvicu_attach(struct device *parent, struct device *self, void *aux)
111{
112	struct mvicu_softc *sc = (struct mvicu_softc *)self;
113	struct fdt_attach_args *faa = aux;
114	int i, node, nchildren;
115
116	if (faa->fa_nreg < 1) {
117		printf(": no registers\n");
118		return;
119	}
120
121	sc->sc_iot = faa->fa_iot;
122	if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
123	    faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
124		printf(": can't map registers\n");
125		return;
126	}
127
128	printf("\n");
129
130	if (OF_child(faa->fa_node) == 0) {
131		sc->sc_legacy = 1;
132		sc->sc_nodes = mallocarray(1, sizeof(*sc->sc_nodes),
133		    M_DEVBUF, M_WAITOK | M_ZERO);
134		mvicu_register(sc, faa->fa_node, 0);
135	} else {
136		for (node = OF_child(faa->fa_node), nchildren = 0;
137		    node; node = OF_peer(node))
138			nchildren++;
139		sc->sc_nodes = mallocarray(nchildren, sizeof(*sc->sc_nodes),
140		    M_DEVBUF, M_WAITOK | M_ZERO);
141		for (node = OF_child(faa->fa_node), i = 0; node;
142		    node = OF_peer(node))
143			mvicu_register(sc, node, i++);
144	}
145}
146
147void
148mvicu_register(struct mvicu_softc *sc, int node, int idx)
149{
150	struct mvicu_subnode *sn = &sc->sc_nodes[idx];
151	struct interrupt_controller *ic;
152	uint32_t phandle = 0;
153	uint32_t group;
154	int i;
155
156	sn->sn_group = -1;
157	if (OF_is_compatible(node, "marvell,cp110-icu") ||
158	    OF_is_compatible(node, "marvell,cp110-icu-nsr"))
159		sn->sn_group = ICU_GRP_NSR;
160	if (OF_is_compatible(node, "marvell,cp110-icu-sei"))
161		sn->sn_group = ICU_GRP_SEI;
162
163	for (i = 0; i < ICU_DEVICE_NIRQ; i++) {
164		group = HREAD4(sc, ICU_INT_CFG(i)) >> ICU_INT_GROUP_SHIFT;
165		if ((sn->sn_group == ICU_GRP_NSR && group == ICU_GRP_NSR) ||
166		    (sn->sn_group == ICU_GRP_SEI && group == ICU_GRP_SEI))
167			HWRITE4(sc, ICU_INT_CFG(i), 0);
168	}
169
170	sn->sn_sc = sc;
171	sn->sn_ic.ic_node = node;
172	sn->sn_ic.ic_cookie = sn;
173	sn->sn_ic.ic_establish = mvicu_intr_establish;
174	sn->sn_ic.ic_disestablish = mvicu_intr_disestablish;
175	sn->sn_ic.ic_barrier = mvicu_intr_barrier;
176
177	while (node && !phandle) {
178		phandle = OF_getpropint(node, "msi-parent", 0);
179		node = OF_parent(node);
180	}
181	if (phandle == 0)
182		return;
183
184	extern LIST_HEAD(, interrupt_controller) interrupt_controllers;
185	LIST_FOREACH(ic, &interrupt_controllers, ic_list) {
186		if (ic->ic_phandle == phandle)
187			break;
188	}
189	if (ic == NULL)
190		return;
191
192	sn->sn_parent_ic = ic;
193	fdt_intr_register(&sn->sn_ic);
194}
195
196void *
197mvicu_intr_establish(void *cookie, int *cell, int level,
198    struct cpu_info *ci, int (*func)(void *), void *arg, char *name)
199{
200	struct mvicu_subnode *sn = cookie;
201	struct mvicu_softc *sc = sn->sn_sc;
202	struct interrupt_controller *ic = sn->sn_parent_ic;
203	struct machine_intr_handle *ih;
204	uint32_t idx, flags;
205	uint64_t addr, data;
206	int edge = 0;
207
208	if (sc->sc_legacy) {
209		if (cell[0] != ICU_GRP_NSR)
210			return NULL;
211		idx = cell[1];
212		flags = cell[2];
213		edge = ((flags & 0xf) == 0x1);
214	} else if (sn->sn_group == ICU_GRP_NSR) {
215		idx = cell[0];
216		flags = cell[1];
217		edge = ((flags & 0xf) == 0x1);
218	} else if (sn->sn_group == ICU_GRP_SEI) {
219		idx = cell[0];
220		flags = cell[1];
221		edge = 1;
222	} else {
223		return NULL;
224	}
225
226	data = flags;
227	cookie = ic->ic_establish_msi(ic->ic_cookie, &addr, &data,
228	    level, ci, func, arg, name);
229	if (cookie == NULL)
230		return NULL;
231
232	if (sn->sn_group == ICU_GRP_NSR && !sc->sc_nsr_addr) {
233		sc->sc_nsr_addr = addr;
234		HWRITE4(sc, ICU_SETSPI_NSR_AL,
235		    (addr + GICP_SETSPI_NSR) & 0xffffffff);
236		HWRITE4(sc, ICU_SETSPI_NSR_AH,
237		    (addr + GICP_SETSPI_NSR) >> 32);
238		HWRITE4(sc, ICU_CLRSPI_NSR_AL,
239		    (addr + GICP_CLRSPI_NSR) & 0xffffffff);
240		HWRITE4(sc, ICU_CLRSPI_NSR_AH,
241		    (addr + GICP_CLRSPI_NSR) >> 32);
242	}
243
244	if (sn->sn_group == ICU_GRP_SEI && !sc->sc_sei_addr) {
245		sc->sc_sei_addr = addr;
246		HWRITE4(sc, ICU_SET_SEI_AL, addr & 0xffffffff);
247		HWRITE4(sc, ICU_SET_SEI_AH, addr >> 32);
248	}
249
250	/* Configure ICU. */
251	HWRITE4(sc, ICU_INT_CFG(idx), data | ICU_INT_ENABLE |
252	    (sn->sn_group << ICU_INT_GROUP_SHIFT) | (edge ? ICU_INT_EDGE : 0));
253
254	/* Need to configure interrupt for both SATA ports. */
255	if (idx == ICU_DEVICE_SATA0 || idx == ICU_DEVICE_SATA1) {
256		HWRITE4(sc, ICU_INT_CFG(ICU_DEVICE_SATA0), data |
257		    ICU_INT_ENABLE | (sn->sn_group << ICU_INT_GROUP_SHIFT) |
258		    (edge ? ICU_INT_EDGE : 0));
259		HWRITE4(sc, ICU_INT_CFG(ICU_DEVICE_SATA1), data |
260		    ICU_INT_ENABLE | (sn->sn_group << ICU_INT_GROUP_SHIFT) |
261		    (edge ? ICU_INT_EDGE : 0));
262	}
263
264	ih = malloc(sizeof(*ih), M_DEVBUF, M_WAITOK);
265	ih->ih_ic = ic;
266	ih->ih_ih = cookie;
267
268	return ih;
269}
270
271void
272mvicu_intr_disestablish(void *cookie)
273{
274	panic("%s", __func__);
275}
276
277void
278mvicu_intr_barrier(void *cookie)
279{
280	struct machine_intr_handle *ih = cookie;
281	struct interrupt_controller *ic = ih->ih_ic;
282
283	ic->ic_barrier(ih->ih_ih);
284}
285