xref: /freebsd/sys/dev/mailbox/arm/arm_doorbell.c (revision fdafd315ad0d0f28a11b9fb4476a9ab059c62b92)
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/param.h>
32 #include <sys/systm.h>
33 #include <sys/bus.h>
34 #include <sys/rman.h>
35 #include <sys/kernel.h>
36 #include <sys/module.h>
37 
38 #include <machine/bus.h>
39 
40 #include <dev/fdt/simplebus.h>
41 #include <dev/fdt/fdt_common.h>
42 #include <dev/ofw/ofw_bus_subr.h>
43 
44 #include "arm_doorbell.h"
45 
46 #define	MHU_CHAN_RX_LP		0x000	/* Low priority channel */
47 #define	MHU_CHAN_RX_HP		0x020	/* High priority channel */
48 #define	MHU_CHAN_RX_SEC		0x200	/* Secure channel */
49 #define	 MHU_INTR_STAT		0x00
50 #define	 MHU_INTR_SET		0x08
51 #define	 MHU_INTR_CLEAR		0x10
52 
53 #define	MHU_TX_REG_OFFSET	0x100
54 
55 #define	DOORBELL_N_CHANNELS	3
56 #define	DOORBELL_N_DOORBELLS	(DOORBELL_N_CHANNELS * 32)
57 
58 struct arm_doorbell dbells[DOORBELL_N_DOORBELLS];
59 
60 static struct resource_spec arm_doorbell_spec[] = {
61 	{ SYS_RES_MEMORY,	0,	RF_ACTIVE },
62 	{ SYS_RES_IRQ,		0,	RF_ACTIVE },
63 	{ SYS_RES_IRQ,		1,	RF_ACTIVE },
64 	{ -1, 0 }
65 };
66 
67 struct arm_doorbell_softc {
68 	struct resource		*res[3];
69 	void			*lp_intr_cookie;
70 	void			*hp_intr_cookie;
71 	device_t		dev;
72 };
73 
74 static void
arm_doorbell_lp_intr(void * arg)75 arm_doorbell_lp_intr(void *arg)
76 {
77 	struct arm_doorbell_softc *sc;
78 	struct arm_doorbell *db;
79 	uint32_t reg;
80 	int i;
81 
82 	sc = arg;
83 
84 	reg = bus_read_4(sc->res[0], MHU_CHAN_RX_LP + MHU_INTR_STAT);
85 	for (i = 0; i < 32; i++) {
86 		if (reg & (1 << i)) {
87 			db = &dbells[i];
88 			bus_write_4(sc->res[0], MHU_CHAN_RX_LP + MHU_INTR_CLEAR,
89 			    (1 << i));
90 			if (db->func != NULL)
91 				db->func(db->arg);
92 		}
93 	}
94 }
95 
96 static void
arm_doorbell_hp_intr(void * arg)97 arm_doorbell_hp_intr(void *arg)
98 {
99 	struct arm_doorbell_softc *sc;
100 	struct arm_doorbell *db;
101 	uint32_t reg;
102 	int i;
103 
104 	sc = arg;
105 
106 	reg = bus_read_4(sc->res[0], MHU_CHAN_RX_HP + MHU_INTR_STAT);
107 	for (i = 0; i < 32; i++) {
108 		if (reg & (1 << i)) {
109 			db = &dbells[i];
110 			bus_write_4(sc->res[0], MHU_CHAN_RX_HP + MHU_INTR_CLEAR,
111 			    (1 << i));
112 			if (db->func != NULL)
113 				db->func(db->arg);
114 		}
115 	}
116 }
117 
118 static int
arm_doorbell_probe(device_t dev)119 arm_doorbell_probe(device_t dev)
120 {
121 
122 	if (!ofw_bus_is_compatible(dev, "arm,mhu-doorbell"))
123 		return (ENXIO);
124 
125 	if (!ofw_bus_status_okay(dev))
126 		return (ENXIO);
127 
128 	device_set_desc(dev, "ARM MHU Doorbell");
129 
130 	return (BUS_PROBE_DEFAULT);
131 }
132 
133 static int
arm_doorbell_attach(device_t dev)134 arm_doorbell_attach(device_t dev)
135 {
136 	struct arm_doorbell_softc *sc;
137 	phandle_t node;
138 	int error;
139 
140 	sc = device_get_softc(dev);
141 	sc->dev = dev;
142 
143 	node = ofw_bus_get_node(dev);
144 	if (node == -1)
145 		return (ENXIO);
146 
147 	if (bus_alloc_resources(dev, arm_doorbell_spec, sc->res) != 0) {
148 		device_printf(dev, "Can't allocate resources for device.\n");
149 		return (ENXIO);
150 	}
151 
152 	/* Setup interrupt handlers. */
153 	error = bus_setup_intr(dev, sc->res[1], INTR_TYPE_MISC | INTR_MPSAFE,
154 	    NULL, arm_doorbell_lp_intr, sc, &sc->lp_intr_cookie);
155 	if (error != 0) {
156 		device_printf(dev, "Can't setup LP interrupt handler.\n");
157 		bus_release_resources(dev, arm_doorbell_spec, sc->res);
158 		return (ENXIO);
159 	}
160 
161 	error = bus_setup_intr(dev, sc->res[2], INTR_TYPE_MISC | INTR_MPSAFE,
162 	    NULL, arm_doorbell_hp_intr, sc, &sc->hp_intr_cookie);
163 	if (error != 0) {
164 		device_printf(dev, "Can't setup HP interrupt handler.\n");
165 		bus_release_resources(dev, arm_doorbell_spec, sc->res);
166 		return (ENXIO);
167 	}
168 
169 	OF_device_register_xref(OF_xref_from_node(node), dev);
170 
171 	return (0);
172 }
173 
174 static int
arm_doorbell_detach(device_t dev)175 arm_doorbell_detach(device_t dev)
176 {
177 
178 	return (EBUSY);
179 }
180 
181 struct arm_doorbell *
arm_doorbell_ofw_get(device_t dev,const char * name)182 arm_doorbell_ofw_get(device_t dev, const char *name)
183 {
184 	phandle_t node, parent;
185 	struct arm_doorbell *db;
186 	device_t db_dev;
187 	pcell_t *cells;
188 	int nmboxes;
189 	int ncells;
190 	int idx;
191 	int db_id;
192 	int error;
193 	int chan;
194 
195 	node = ofw_bus_get_node(dev);
196 
197 	error = ofw_bus_parse_xref_list_get_length(node, "mboxes",
198 	    "#mbox-cells", &nmboxes);
199 	if (error) {
200 		device_printf(dev, "%s can't get mboxes list.\n", __func__);
201 		return (NULL);
202 	}
203 
204 	if (nmboxes == 0) {
205 		device_printf(dev, "%s mbox list is empty.\n", __func__);
206 		return (NULL);
207 	}
208 
209 	error = ofw_bus_find_string_index(node, "mbox-names", name, &idx);
210 	if (error != 0) {
211 		device_printf(dev, "%s can't find string index.\n",
212 		    __func__);
213 		return (NULL);
214 	}
215 
216 	error = ofw_bus_parse_xref_list_alloc(node, "mboxes", "#mbox-cells",
217 	    idx, &parent, &ncells, &cells);
218 	if (error != 0) {
219 		device_printf(dev, "%s can't get mbox device xref\n",
220 		    __func__);
221 		return (NULL);
222 	}
223 
224 	if (ncells != 2) {
225 		device_printf(dev, "Unexpected data size.\n");
226 		OF_prop_free(cells);
227 		return (NULL);
228 	}
229 
230 	db_dev = OF_device_from_xref(parent);
231 	if (db_dev == NULL) {
232 		device_printf(dev, "%s: Can't get arm_doorbell device\n",
233 		    __func__);
234 		OF_prop_free(cells);
235 		return (NULL);
236 	}
237 
238 	chan = cells[0];
239 	if (chan >= DOORBELL_N_CHANNELS) {
240 		device_printf(dev, "Unexpected channel number.\n");
241 		OF_prop_free(cells);
242 		return (NULL);
243 	}
244 
245 	db_id = cells[1];
246 	if (db_id >= 32) {
247 		device_printf(dev, "Unexpected channel bit.\n");
248 		OF_prop_free(cells);
249 		return (NULL);
250 	}
251 
252 	db = &dbells[chan * db_id];
253 	db->dev = dev;
254 	db->db_dev = db_dev;
255 	db->chan = chan;
256 	db->db = db_id;
257 
258 	OF_prop_free(cells);
259 
260 	return (db);
261 }
262 
263 void
arm_doorbell_set(struct arm_doorbell * db)264 arm_doorbell_set(struct arm_doorbell *db)
265 {
266 	struct arm_doorbell_softc *sc;
267 	uint32_t offset;
268 
269 	sc = device_get_softc(db->db_dev);
270 
271 	switch (db->chan) {
272 	case 0:
273 		offset = MHU_CHAN_RX_LP;
274 		break;
275 	case 1:
276 		offset = MHU_CHAN_RX_HP;
277 		break;
278 	case 2:
279 		offset = MHU_CHAN_RX_SEC;
280 		break;
281 	default:
282 		panic("not reached");
283 	};
284 
285 	offset |= MHU_TX_REG_OFFSET;
286 
287 	bus_write_4(sc->res[0], offset + MHU_INTR_SET, (1 << db->db));
288 }
289 
290 int
arm_doorbell_get(struct arm_doorbell * db)291 arm_doorbell_get(struct arm_doorbell *db)
292 {
293 	struct arm_doorbell_softc *sc;
294 	uint32_t offset;
295 	uint32_t reg;
296 
297 	sc = device_get_softc(db->db_dev);
298 
299 	switch (db->chan) {
300 	case 0:
301 		offset = MHU_CHAN_RX_LP;
302 		break;
303 	case 1:
304 		offset = MHU_CHAN_RX_HP;
305 		break;
306 	case 2:
307 		offset = MHU_CHAN_RX_SEC;
308 		break;
309 	default:
310 		panic("not reached");
311 	};
312 
313 	reg = bus_read_4(sc->res[0], offset + MHU_INTR_STAT);
314 	if (reg & (1 << db->db)) {
315 		bus_write_4(sc->res[0], offset + MHU_INTR_CLEAR,
316 		    (1 << db->db));
317 		return (1);
318 	}
319 
320 	return (0);
321 }
322 
323 void
arm_doorbell_set_handler(struct arm_doorbell * db,void (* func)(void *),void * arg)324 arm_doorbell_set_handler(struct arm_doorbell *db, void (*func)(void *),
325     void *arg)
326 {
327 
328 	db->func = func;
329 	db->arg = arg;
330 }
331 
332 static device_method_t arm_doorbell_methods[] = {
333 	DEVMETHOD(device_probe,		arm_doorbell_probe),
334 	DEVMETHOD(device_attach,	arm_doorbell_attach),
335 	DEVMETHOD(device_detach,	arm_doorbell_detach),
336 	DEVMETHOD_END
337 };
338 
339 DEFINE_CLASS_1(arm_doorbell, arm_doorbell_driver, arm_doorbell_methods,
340     sizeof(struct arm_doorbell_softc), simplebus_driver);
341 
342 EARLY_DRIVER_MODULE(arm_doorbell, simplebus, arm_doorbell_driver, 0, 0,
343     BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE);
344 MODULE_VERSION(arm_doorbell, 1);
345