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