1 /*-
2 * Copyright (c) 2011,2016 The FreeBSD Foundation
3 * All rights reserved.
4 *
5 * This software was developed by Andrew Turner under
6 * sponsorship from the FreeBSD Foundation.
7 *
8 * Developed by Damjan Marion <damjan.marion@gmail.com>
9 *
10 * Based on OMAP4 GIC code by Ben Gray
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. The name of the company nor the name of the author may be used to
21 * endorse or promote products derived from this software without specific
22 * prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37 #include "opt_platform.h"
38
39 #include <sys/param.h>
40 #include <sys/systm.h>
41 #include <sys/bus.h>
42 #include <sys/kernel.h>
43 #include <sys/module.h>
44
45 #include <machine/intr.h>
46
47 #include <dev/ofw/openfirm.h>
48 #include <dev/ofw/ofw_bus.h>
49 #include <dev/ofw/ofw_bus_subr.h>
50
51 #include <arm/arm/gic.h>
52 #include <arm/arm/gic_common.h>
53
54 struct arm_gic_devinfo {
55 struct ofw_bus_devinfo obdinfo;
56 struct resource_list rl;
57 };
58
59 struct arm_gic_fdt_softc {
60 struct arm_gic_softc base;
61 pcell_t addr_cells;
62 pcell_t size_cells;
63 };
64
65 static device_probe_t gic_fdt_probe;
66 static device_attach_t gic_fdt_attach;
67 static ofw_bus_get_devinfo_t gic_ofw_get_devinfo;
68 static bus_get_resource_list_t gic_fdt_get_resource_list;
69 static bool arm_gic_add_children(device_t);
70
71 static struct ofw_compat_data compat_data[] = {
72 {"arm,gic", true}, /* Non-standard, used in FreeBSD dts. */
73 {"arm,gic-400", true},
74 {"arm,cortex-a15-gic", true},
75 {"arm,cortex-a9-gic", true},
76 {"arm,cortex-a7-gic", true},
77 {"arm,arm11mp-gic", true},
78 {"brcm,brahma-b15-gic", true},
79 {"qcom,msm-qgic2", true},
80 {NULL, false}
81 };
82
83 static device_method_t gic_fdt_methods[] = {
84 /* Device interface */
85 DEVMETHOD(device_probe, gic_fdt_probe),
86 DEVMETHOD(device_attach, gic_fdt_attach),
87
88 /* Bus interface */
89 DEVMETHOD(bus_get_resource_list,gic_fdt_get_resource_list),
90 DEVMETHOD(bus_get_device_path, ofw_bus_gen_get_device_path),
91
92 /* ofw_bus interface */
93 DEVMETHOD(ofw_bus_get_devinfo, gic_ofw_get_devinfo),
94 DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat),
95 DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model),
96 DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name),
97 DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node),
98 DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type),
99
100 DEVMETHOD_END,
101 };
102
103 DEFINE_CLASS_1(gic, gic_fdt_driver, gic_fdt_methods,
104 sizeof(struct arm_gic_fdt_softc), arm_gic_driver);
105
106 EARLY_DRIVER_MODULE(gic, simplebus, gic_fdt_driver, 0, 0,
107 BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE);
108 EARLY_DRIVER_MODULE(gic, ofwbus, gic_fdt_driver, 0, 0,
109 BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE);
110
111 static int
gic_fdt_probe(device_t dev)112 gic_fdt_probe(device_t dev)
113 {
114
115 if (!ofw_bus_status_okay(dev))
116 return (ENXIO);
117
118 if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data)
119 return (ENXIO);
120 device_set_desc(dev, "ARM Generic Interrupt Controller");
121 return (BUS_PROBE_DEFAULT);
122 }
123
124 static int
gic_fdt_attach(device_t dev)125 gic_fdt_attach(device_t dev)
126 {
127 struct arm_gic_fdt_softc *sc = device_get_softc(dev);
128 phandle_t pxref;
129 intptr_t xref;
130 int err;
131
132 sc->base.gic_bus = GIC_BUS_FDT;
133
134 err = arm_gic_attach(dev);
135 if (err != 0)
136 return (err);
137
138 xref = OF_xref_from_node(ofw_bus_get_node(dev));
139
140 /*
141 * Now, when everything is initialized, it's right time to
142 * register interrupt controller to interrupt framefork.
143 */
144 if (intr_pic_register(dev, xref) == NULL) {
145 device_printf(dev, "could not register PIC\n");
146 goto cleanup;
147 }
148
149 /*
150 * Controller is root if:
151 * - doesn't have interrupt parent
152 * - his interrupt parent is this controller
153 */
154 pxref = ofw_bus_find_iparent(ofw_bus_get_node(dev));
155 if (pxref == 0 || xref == pxref) {
156 if (intr_pic_claim_root(dev, xref, arm_gic_intr, sc, INTR_ROOT_IRQ)
157 != 0) {
158 device_printf(dev, "could not set PIC as a root\n");
159 intr_pic_deregister(dev, xref);
160 goto cleanup;
161 }
162
163 #ifdef SMP
164 if (intr_ipi_pic_register(dev, 0) != 0) {
165 device_printf(dev, "could not register for IPIs\n");
166 goto cleanup;
167 }
168 #endif
169 } else {
170 if (sc->base.gic_res[2] == NULL) {
171 device_printf(dev,
172 "not root PIC must have defined interrupt\n");
173 intr_pic_deregister(dev, xref);
174 goto cleanup;
175 }
176 if (bus_setup_intr(dev, sc->base.gic_res[2], INTR_TYPE_CLK,
177 arm_gic_intr, NULL, sc, &sc->base.gic_intrhand)) {
178 device_printf(dev, "could not setup irq handler\n");
179 intr_pic_deregister(dev, xref);
180 goto cleanup;
181 }
182 }
183
184 OF_device_register_xref(xref, dev);
185
186 /* If we have children probe and attach them */
187 if (arm_gic_add_children(dev)) {
188 bus_identify_children(dev);
189 bus_attach_children(dev);
190 }
191
192 return (0);
193
194 cleanup:
195 arm_gic_detach(dev);
196 return(ENXIO);
197 }
198
199 static struct resource_list *
gic_fdt_get_resource_list(device_t bus,device_t child)200 gic_fdt_get_resource_list(device_t bus, device_t child)
201 {
202 struct arm_gic_devinfo *di;
203
204 di = device_get_ivars(child);
205 KASSERT(di != NULL, ("gic_fdt_get_resource_list: No devinfo"));
206
207 return (&di->rl);
208 }
209
210 static int
arm_gic_fill_ranges(phandle_t node,struct arm_gic_fdt_softc * sc)211 arm_gic_fill_ranges(phandle_t node, struct arm_gic_fdt_softc *sc)
212 {
213 pcell_t host_cells;
214 cell_t *base_ranges;
215 ssize_t nbase_ranges;
216 int i, j, k;
217
218 host_cells = 1;
219 OF_getencprop(OF_parent(node), "#address-cells", &host_cells,
220 sizeof(host_cells));
221 sc->addr_cells = 2;
222 OF_getencprop(node, "#address-cells", &sc->addr_cells,
223 sizeof(sc->addr_cells));
224 sc->size_cells = 2;
225 OF_getencprop(node, "#size-cells", &sc->size_cells,
226 sizeof(sc->size_cells));
227
228 nbase_ranges = OF_getproplen(node, "ranges");
229 if (nbase_ranges < 0)
230 return (-1);
231 sc->base.nranges = nbase_ranges / sizeof(cell_t) /
232 (sc->addr_cells + host_cells + sc->size_cells);
233 if (sc->base.nranges == 0)
234 return (0);
235
236 sc->base.ranges = malloc(sc->base.nranges * sizeof(sc->base.ranges[0]),
237 M_DEVBUF, M_WAITOK);
238 base_ranges = malloc(nbase_ranges, M_DEVBUF, M_WAITOK);
239 OF_getencprop(node, "ranges", base_ranges, nbase_ranges);
240
241 for (i = 0, j = 0; i < sc->base.nranges; i++) {
242 sc->base.ranges[i].bus = 0;
243 for (k = 0; k < sc->addr_cells; k++) {
244 sc->base.ranges[i].bus <<= 32;
245 sc->base.ranges[i].bus |= base_ranges[j++];
246 }
247 sc->base.ranges[i].host = 0;
248 for (k = 0; k < host_cells; k++) {
249 sc->base.ranges[i].host <<= 32;
250 sc->base.ranges[i].host |= base_ranges[j++];
251 }
252 sc->base.ranges[i].size = 0;
253 for (k = 0; k < sc->size_cells; k++) {
254 sc->base.ranges[i].size <<= 32;
255 sc->base.ranges[i].size |= base_ranges[j++];
256 }
257 }
258
259 free(base_ranges, M_DEVBUF);
260 return (sc->base.nranges);
261 }
262
263 static bool
arm_gic_add_children(device_t dev)264 arm_gic_add_children(device_t dev)
265 {
266 struct arm_gic_fdt_softc *sc;
267 struct arm_gic_devinfo *dinfo;
268 phandle_t child, node;
269 device_t cdev;
270
271 sc = device_get_softc(dev);
272 node = ofw_bus_get_node(dev);
273
274 /* If we have no children don't probe for them */
275 child = OF_child(node);
276 if (child == 0)
277 return (false);
278
279 if (arm_gic_fill_ranges(node, sc) < 0) {
280 device_printf(dev, "Have a child, but no ranges\n");
281 return (false);
282 }
283
284 for (; child != 0; child = OF_peer(child)) {
285 dinfo = malloc(sizeof(*dinfo), M_DEVBUF, M_WAITOK | M_ZERO);
286
287 if (ofw_bus_gen_setup_devinfo(&dinfo->obdinfo, child) != 0) {
288 free(dinfo, M_DEVBUF);
289 continue;
290 }
291
292 resource_list_init(&dinfo->rl);
293 ofw_bus_reg_to_rl(dev, child, sc->addr_cells,
294 sc->size_cells, &dinfo->rl);
295
296 cdev = device_add_child(dev, NULL, DEVICE_UNIT_ANY);
297 if (cdev == NULL) {
298 device_printf(dev, "<%s>: device_add_child failed\n",
299 dinfo->obdinfo.obd_name);
300 resource_list_free(&dinfo->rl);
301 ofw_bus_gen_destroy_devinfo(&dinfo->obdinfo);
302 free(dinfo, M_DEVBUF);
303 continue;
304 }
305 device_set_ivars(cdev, dinfo);
306 }
307
308 return (true);
309 }
310
311 static const struct ofw_bus_devinfo *
gic_ofw_get_devinfo(device_t bus __unused,device_t child)312 gic_ofw_get_devinfo(device_t bus __unused, device_t child)
313 {
314 struct arm_gic_devinfo *di;
315
316 di = device_get_ivars(child);
317
318 return (&di->obdinfo);
319 }
320
321 static struct ofw_compat_data gicv2m_compat_data[] = {
322 {"arm,gic-v2m-frame", true},
323 {NULL, false}
324 };
325
326 static int
arm_gicv2m_fdt_probe(device_t dev)327 arm_gicv2m_fdt_probe(device_t dev)
328 {
329
330 if (!ofw_bus_status_okay(dev))
331 return (ENXIO);
332
333 if (!ofw_bus_search_compatible(dev, gicv2m_compat_data)->ocd_data)
334 return (ENXIO);
335
336 device_set_desc(dev, "ARM Generic Interrupt Controller MSI/MSIX");
337 return (BUS_PROBE_DEFAULT);
338 }
339
340 static int
arm_gicv2m_fdt_attach(device_t dev)341 arm_gicv2m_fdt_attach(device_t dev)
342 {
343 struct arm_gicv2m_softc *sc;
344
345 sc = device_get_softc(dev);
346 sc->sc_xref = OF_xref_from_node(ofw_bus_get_node(dev));
347
348 return (arm_gicv2m_attach(dev));
349 }
350
351 static device_method_t arm_gicv2m_fdt_methods[] = {
352 /* Device interface */
353 DEVMETHOD(device_probe, arm_gicv2m_fdt_probe),
354 DEVMETHOD(device_attach, arm_gicv2m_fdt_attach),
355
356 /* End */
357 DEVMETHOD_END
358 };
359
360 DEFINE_CLASS_1(gicv2m, arm_gicv2m_fdt_driver, arm_gicv2m_fdt_methods,
361 sizeof(struct arm_gicv2m_softc), arm_gicv2m_driver);
362
363 EARLY_DRIVER_MODULE(gicv2m, gic, arm_gicv2m_fdt_driver, 0, 0,
364 BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE);
365