xref: /freebsd/sys/dev/mailbox/arm/arm_doorbell.c (revision 9f44a47fd07924afc035991af15d84e6585dea4f)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2022 Ruslan Bukin <br@bsdpad.com>
5  *
6  * This work was supported by Innovate UK project 105694, "Digital Security
7  * by Design (DSbD) Technology Platform Prototype".
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  */
30 
31 #include <sys/cdefs.h>
32 __FBSDID("$FreeBSD$");
33 
34 #include <sys/param.h>
35 #include <sys/systm.h>
36 #include <sys/bus.h>
37 #include <sys/rman.h>
38 #include <sys/kernel.h>
39 #include <sys/module.h>
40 
41 #include <machine/bus.h>
42 
43 #include <dev/fdt/simplebus.h>
44 #include <dev/fdt/fdt_common.h>
45 #include <dev/ofw/ofw_bus_subr.h>
46 
47 #include "arm_doorbell.h"
48 
49 #define	MHU_CHAN_RX_LP		0x000	/* Low priority channel */
50 #define	MHU_CHAN_RX_HP		0x020	/* High priority channel */
51 #define	MHU_CHAN_RX_SEC		0x200	/* Secure channel */
52 #define	 MHU_INTR_STAT		0x00
53 #define	 MHU_INTR_SET		0x08
54 #define	 MHU_INTR_CLEAR		0x10
55 
56 #define	MHU_TX_REG_OFFSET	0x100
57 
58 #define	DOORBELL_N_CHANNELS	3
59 #define	DOORBELL_N_DOORBELLS	(DOORBELL_N_CHANNELS * 32)
60 
61 struct arm_doorbell dbells[DOORBELL_N_DOORBELLS];
62 
63 static struct resource_spec arm_doorbell_spec[] = {
64 	{ SYS_RES_MEMORY,	0,	RF_ACTIVE },
65 	{ SYS_RES_IRQ,		0,	RF_ACTIVE },
66 	{ SYS_RES_IRQ,		1,	RF_ACTIVE },
67 	{ -1, 0 }
68 };
69 
70 struct arm_doorbell_softc {
71 	struct resource		*res[3];
72 	void			*lp_intr_cookie;
73 	void			*hp_intr_cookie;
74 	device_t		dev;
75 };
76 
77 static void
78 arm_doorbell_lp_intr(void *arg)
79 {
80 	struct arm_doorbell_softc *sc;
81 	struct arm_doorbell *db;
82 	uint32_t reg;
83 	int i;
84 
85 	sc = arg;
86 
87 	reg = bus_read_4(sc->res[0], MHU_CHAN_RX_LP + MHU_INTR_STAT);
88 	for (i = 0; i < 32; i++) {
89 		if (reg & (1 << i)) {
90 			db = &dbells[i];
91 			bus_write_4(sc->res[0], MHU_CHAN_RX_LP + MHU_INTR_CLEAR,
92 			    (1 << i));
93 			if (db->func != NULL)
94 				db->func(db->arg);
95 		}
96 	}
97 }
98 
99 static void
100 arm_doorbell_hp_intr(void *arg)
101 {
102 	struct arm_doorbell_softc *sc;
103 	struct arm_doorbell *db;
104 	uint32_t reg;
105 	int i;
106 
107 	sc = arg;
108 
109 	reg = bus_read_4(sc->res[0], MHU_CHAN_RX_HP + MHU_INTR_STAT);
110 	for (i = 0; i < 32; i++) {
111 		if (reg & (1 << i)) {
112 			db = &dbells[i];
113 			bus_write_4(sc->res[0], MHU_CHAN_RX_HP + MHU_INTR_CLEAR,
114 			    (1 << i));
115 			if (db->func != NULL)
116 				db->func(db->arg);
117 		}
118 	}
119 }
120 
121 static int
122 arm_doorbell_probe(device_t dev)
123 {
124 
125 	if (!ofw_bus_is_compatible(dev, "arm,mhu-doorbell"))
126 		return (ENXIO);
127 
128 	if (!ofw_bus_status_okay(dev))
129 		return (ENXIO);
130 
131 	device_set_desc(dev, "ARM MHU Doorbell");
132 
133 	return (BUS_PROBE_DEFAULT);
134 }
135 
136 static int
137 arm_doorbell_attach(device_t dev)
138 {
139 	struct arm_doorbell_softc *sc;
140 	phandle_t node;
141 	int error;
142 
143 	sc = device_get_softc(dev);
144 	sc->dev = dev;
145 
146 	node = ofw_bus_get_node(dev);
147 	if (node == -1)
148 		return (ENXIO);
149 
150 	if (bus_alloc_resources(dev, arm_doorbell_spec, sc->res) != 0) {
151 		device_printf(dev, "Can't allocate resources for device.\n");
152 		return (ENXIO);
153 	}
154 
155 	/* Setup interrupt handlers. */
156 	error = bus_setup_intr(dev, sc->res[1], INTR_TYPE_MISC | INTR_MPSAFE,
157 	    NULL, arm_doorbell_lp_intr, sc, &sc->lp_intr_cookie);
158 	if (error != 0) {
159 		device_printf(dev, "Can't setup LP interrupt handler.\n");
160 		bus_release_resources(dev, arm_doorbell_spec, sc->res);
161 		return (ENXIO);
162 	}
163 
164 	error = bus_setup_intr(dev, sc->res[2], INTR_TYPE_MISC | INTR_MPSAFE,
165 	    NULL, arm_doorbell_hp_intr, sc, &sc->hp_intr_cookie);
166 	if (error != 0) {
167 		device_printf(dev, "Can't setup HP interrupt handler.\n");
168 		bus_release_resources(dev, arm_doorbell_spec, sc->res);
169 		return (ENXIO);
170 	}
171 
172 	OF_device_register_xref(OF_xref_from_node(node), dev);
173 
174 	return (0);
175 }
176 
177 static int
178 arm_doorbell_detach(device_t dev)
179 {
180 
181 	return (EBUSY);
182 }
183 
184 struct arm_doorbell *
185 arm_doorbell_ofw_get(device_t dev, const char *name)
186 {
187 	phandle_t node, parent;
188 	struct arm_doorbell *db;
189 	device_t db_dev;
190 	pcell_t *cells;
191 	int nmboxes;
192 	int ncells;
193 	int idx;
194 	int db_id;
195 	int error;
196 	int chan;
197 
198 	node = ofw_bus_get_node(dev);
199 
200 	error = ofw_bus_parse_xref_list_get_length(node, "mboxes",
201 	    "#mbox-cells", &nmboxes);
202 	if (error) {
203 		device_printf(dev, "%s can't get mboxes list.\n", __func__);
204 		return (NULL);
205 	}
206 
207 	if (nmboxes == 0) {
208 		device_printf(dev, "%s mbox list is empty.\n", __func__);
209 		return (NULL);
210 	}
211 
212 	error = ofw_bus_find_string_index(node, "mbox-names", name, &idx);
213 	if (error != 0) {
214 		device_printf(dev, "%s can't find string index.\n",
215 		    __func__);
216 		return (NULL);
217 	}
218 
219 	error = ofw_bus_parse_xref_list_alloc(node, "mboxes", "#mbox-cells",
220 	    idx, &parent, &ncells, &cells);
221 	if (error != 0) {
222 		device_printf(dev, "%s can't get mbox device xref\n",
223 		    __func__);
224 		return (NULL);
225 	}
226 
227 	if (ncells != 2) {
228 		device_printf(dev, "Unexpected data size.\n");
229 		OF_prop_free(cells);
230 		return (NULL);
231 	}
232 
233 	db_dev = OF_device_from_xref(parent);
234 	if (db_dev == NULL) {
235 		device_printf(dev, "%s: Can't get arm_doorbell device\n",
236 		    __func__);
237 		OF_prop_free(cells);
238 		return (NULL);
239 	}
240 
241 	chan = cells[0];
242 	if (chan >= DOORBELL_N_CHANNELS) {
243 		device_printf(dev, "Unexpected channel number.\n");
244 		OF_prop_free(cells);
245 		return (NULL);
246 	}
247 
248 	db_id = cells[1];
249 	if (db_id >= 32) {
250 		device_printf(dev, "Unexpected channel bit.\n");
251 		OF_prop_free(cells);
252 		return (NULL);
253 	}
254 
255 	db = &dbells[chan * db_id];
256 	db->dev = dev;
257 	db->db_dev = db_dev;
258 	db->chan = chan;
259 	db->db = db_id;
260 
261 	OF_prop_free(cells);
262 
263 	return (db);
264 }
265 
266 void
267 arm_doorbell_set(struct arm_doorbell *db)
268 {
269 	struct arm_doorbell_softc *sc;
270 	uint32_t offset;
271 
272 	sc = device_get_softc(db->db_dev);
273 
274 	switch (db->chan) {
275 	case 0:
276 		offset = MHU_CHAN_RX_LP;
277 		break;
278 	case 1:
279 		offset = MHU_CHAN_RX_HP;
280 		break;
281 	case 2:
282 		offset = MHU_CHAN_RX_SEC;
283 		break;
284 	default:
285 		panic("not reached");
286 	};
287 
288 	offset |= MHU_TX_REG_OFFSET;
289 
290 	bus_write_4(sc->res[0], offset + MHU_INTR_SET, (1 << db->db));
291 }
292 
293 int
294 arm_doorbell_get(struct arm_doorbell *db)
295 {
296 	struct arm_doorbell_softc *sc;
297 	uint32_t offset;
298 	uint32_t reg;
299 
300 	sc = device_get_softc(db->db_dev);
301 
302 	switch (db->chan) {
303 	case 0:
304 		offset = MHU_CHAN_RX_LP;
305 		break;
306 	case 1:
307 		offset = MHU_CHAN_RX_HP;
308 		break;
309 	case 2:
310 		offset = MHU_CHAN_RX_SEC;
311 		break;
312 	default:
313 		panic("not reached");
314 	};
315 
316 	reg = bus_read_4(sc->res[0], offset + MHU_INTR_STAT);
317 	if (reg & (1 << db->db)) {
318 		bus_write_4(sc->res[0], offset + MHU_INTR_CLEAR,
319 		    (1 << db->db));
320 		return (1);
321 	}
322 
323 	return (0);
324 }
325 
326 void
327 arm_doorbell_set_handler(struct arm_doorbell *db, void (*func)(void *),
328     void *arg)
329 {
330 
331 	db->func = func;
332 	db->arg = arg;
333 }
334 
335 static device_method_t arm_doorbell_methods[] = {
336 	DEVMETHOD(device_probe,		arm_doorbell_probe),
337 	DEVMETHOD(device_attach,	arm_doorbell_attach),
338 	DEVMETHOD(device_detach,	arm_doorbell_detach),
339 	DEVMETHOD_END
340 };
341 
342 DEFINE_CLASS_1(arm_doorbell, arm_doorbell_driver, arm_doorbell_methods,
343     sizeof(struct arm_doorbell_softc), simplebus_driver);
344 
345 EARLY_DRIVER_MODULE(arm_doorbell, simplebus, arm_doorbell_driver, 0, 0,
346     BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE);
347 MODULE_VERSION(arm_doorbell, 1);
348