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);
Cache object: e85bd8b924bde0e72679d3c37cf807e8
|