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/cdefs.h> 40 __FBSDID("$FreeBSD$"); 41 42 #include <sys/param.h> 43 #include <sys/systm.h> 44 #include <sys/bus.h> 45 #include <sys/kernel.h> 46 #include <sys/module.h> 47 48 #include <machine/intr.h> 49 50 #include <dev/ofw/openfirm.h> 51 #include <dev/ofw/ofw_bus.h> 52 #include <dev/ofw/ofw_bus_subr.h> 53 54 #include <arm/arm/gic.h> 55 #include <arm/arm/gic_common.h> 56 57 #ifdef INTRNG 58 struct arm_gic_devinfo { 59 struct ofw_bus_devinfo obdinfo; 60 struct resource_list rl; 61 }; 62 #endif 63 64 struct arm_gic_fdt_softc { 65 struct arm_gic_softc base; 66 pcell_t addr_cells; 67 pcell_t size_cells; 68 }; 69 70 static device_probe_t gic_fdt_probe; 71 static device_attach_t gic_fdt_attach; 72 static ofw_bus_get_devinfo_t gic_ofw_get_devinfo; 73 #ifdef INTRNG 74 static bus_get_resource_list_t gic_fdt_get_resource_list; 75 static bool arm_gic_add_children(device_t); 76 #endif 77 78 static struct ofw_compat_data compat_data[] = { 79 {"arm,gic", true}, /* Non-standard, used in FreeBSD dts. */ 80 {"arm,gic-400", true}, 81 {"arm,cortex-a15-gic", true}, 82 {"arm,cortex-a9-gic", true}, 83 {"arm,cortex-a7-gic", true}, 84 {"arm,arm11mp-gic", true}, 85 {"brcm,brahma-b15-gic", true}, 86 {"qcom,msm-qgic2", true}, 87 {NULL, false} 88 }; 89 90 static device_method_t gic_fdt_methods[] = { 91 /* Device interface */ 92 DEVMETHOD(device_probe, gic_fdt_probe), 93 DEVMETHOD(device_attach, gic_fdt_attach), 94 95 #ifdef INTRNG 96 /* Bus interface */ 97 DEVMETHOD(bus_get_resource_list,gic_fdt_get_resource_list), 98 99 /* ofw_bus interface */ 100 DEVMETHOD(ofw_bus_get_devinfo, gic_ofw_get_devinfo), 101 DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), 102 DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), 103 DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), 104 DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), 105 DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), 106 #endif 107 108 DEVMETHOD_END, 109 }; 110 111 DEFINE_CLASS_1(gic, gic_fdt_driver, gic_fdt_methods, 112 sizeof(struct arm_gic_fdt_softc), arm_gic_driver); 113 114 static devclass_t gic_fdt_devclass; 115 116 EARLY_DRIVER_MODULE(gic, simplebus, gic_fdt_driver, gic_fdt_devclass, 0, 0, 117 BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); 118 EARLY_DRIVER_MODULE(gic, ofwbus, gic_fdt_driver, gic_fdt_devclass, 0, 0, 119 BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); 120 121 static int 122 gic_fdt_probe(device_t dev) 123 { 124 125 if (!ofw_bus_status_okay(dev)) 126 return (ENXIO); 127 128 if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) 129 return (ENXIO); 130 device_set_desc(dev, "ARM Generic Interrupt Controller"); 131 return (BUS_PROBE_DEFAULT); 132 } 133 134 static int 135 gic_fdt_attach(device_t dev) 136 { 137 #ifdef INTRNG 138 struct arm_gic_fdt_softc *sc = device_get_softc(dev); 139 phandle_t pxref; 140 intptr_t xref; 141 #endif 142 int err; 143 144 #ifdef INTRNG 145 sc->base.gic_bus = GIC_BUS_FDT; 146 #endif 147 148 err = arm_gic_attach(dev); 149 if (err != 0) 150 return (err); 151 152 #ifdef INTRNG 153 xref = OF_xref_from_node(ofw_bus_get_node(dev)); 154 155 /* 156 * Now, when everything is initialized, it's right time to 157 * register interrupt controller to interrupt framefork. 158 */ 159 if (intr_pic_register(dev, xref) == NULL) { 160 device_printf(dev, "could not register PIC\n"); 161 goto cleanup; 162 } 163 164 /* 165 * Controller is root if: 166 * - doesn't have interrupt parent 167 * - his interrupt parent is this controller 168 */ 169 pxref = ofw_bus_find_iparent(ofw_bus_get_node(dev)); 170 if (pxref == 0 || xref == pxref) { 171 if (intr_pic_claim_root(dev, xref, arm_gic_intr, sc, 172 GIC_LAST_SGI - GIC_FIRST_SGI + 1) != 0) { 173 device_printf(dev, "could not set PIC as a root\n"); 174 intr_pic_deregister(dev, xref); 175 goto cleanup; 176 } 177 } else { 178 if (sc->base.gic_res[2] == NULL) { 179 device_printf(dev, 180 "not root PIC must have defined interrupt\n"); 181 intr_pic_deregister(dev, xref); 182 goto cleanup; 183 } 184 if (bus_setup_intr(dev, sc->base.gic_res[2], INTR_TYPE_CLK, 185 arm_gic_intr, NULL, sc, &sc->base.gic_intrhand)) { 186 device_printf(dev, "could not setup irq handler\n"); 187 intr_pic_deregister(dev, xref); 188 goto cleanup; 189 } 190 } 191 192 OF_device_register_xref(xref, dev); 193 194 /* If we have children probe and attach them */ 195 if (arm_gic_add_children(dev)) { 196 bus_generic_probe(dev); 197 return (bus_generic_attach(dev)); 198 } 199 #endif 200 201 return (0); 202 203 #ifdef INTRNG 204 cleanup: 205 arm_gic_detach(dev); 206 return(ENXIO); 207 #endif 208 } 209 210 #ifdef INTRNG 211 static struct resource_list * 212 gic_fdt_get_resource_list(device_t bus, device_t child) 213 { 214 struct arm_gic_devinfo *di; 215 216 di = device_get_ivars(child); 217 KASSERT(di != NULL, ("gic_fdt_get_resource_list: No devinfo")); 218 219 return (&di->rl); 220 } 221 222 static int 223 arm_gic_fill_ranges(phandle_t node, struct arm_gic_fdt_softc *sc) 224 { 225 pcell_t host_cells; 226 cell_t *base_ranges; 227 ssize_t nbase_ranges; 228 int i, j, k; 229 230 host_cells = 1; 231 OF_getencprop(OF_parent(node), "#address-cells", &host_cells, 232 sizeof(host_cells)); 233 sc->addr_cells = 2; 234 OF_getencprop(node, "#address-cells", &sc->addr_cells, 235 sizeof(sc->addr_cells)); 236 sc->size_cells = 2; 237 OF_getencprop(node, "#size-cells", &sc->size_cells, 238 sizeof(sc->size_cells)); 239 240 nbase_ranges = OF_getproplen(node, "ranges"); 241 if (nbase_ranges < 0) 242 return (-1); 243 sc->base.nranges = nbase_ranges / sizeof(cell_t) / 244 (sc->addr_cells + host_cells + sc->size_cells); 245 if (sc->base.nranges == 0) 246 return (0); 247 248 sc->base.ranges = malloc(sc->base.nranges * sizeof(sc->base.ranges[0]), 249 M_DEVBUF, M_WAITOK); 250 base_ranges = malloc(nbase_ranges, M_DEVBUF, M_WAITOK); 251 OF_getencprop(node, "ranges", base_ranges, nbase_ranges); 252 253 for (i = 0, j = 0; i < sc->base.nranges; i++) { 254 sc->base.ranges[i].bus = 0; 255 for (k = 0; k < sc->addr_cells; k++) { 256 sc->base.ranges[i].bus <<= 32; 257 sc->base.ranges[i].bus |= base_ranges[j++]; 258 } 259 sc->base.ranges[i].host = 0; 260 for (k = 0; k < host_cells; k++) { 261 sc->base.ranges[i].host <<= 32; 262 sc->base.ranges[i].host |= base_ranges[j++]; 263 } 264 sc->base.ranges[i].size = 0; 265 for (k = 0; k < sc->size_cells; k++) { 266 sc->base.ranges[i].size <<= 32; 267 sc->base.ranges[i].size |= base_ranges[j++]; 268 } 269 } 270 271 free(base_ranges, M_DEVBUF); 272 return (sc->base.nranges); 273 } 274 275 static bool 276 arm_gic_add_children(device_t dev) 277 { 278 struct arm_gic_fdt_softc *sc; 279 struct arm_gic_devinfo *dinfo; 280 phandle_t child, node; 281 device_t cdev; 282 283 sc = device_get_softc(dev); 284 node = ofw_bus_get_node(dev); 285 286 /* If we have no children don't probe for them */ 287 child = OF_child(node); 288 if (child == 0) 289 return (false); 290 291 if (arm_gic_fill_ranges(node, sc) < 0) { 292 device_printf(dev, "Have a child, but no ranges\n"); 293 return (false); 294 } 295 296 for (; child != 0; child = OF_peer(child)) { 297 dinfo = malloc(sizeof(*dinfo), M_DEVBUF, M_WAITOK | M_ZERO); 298 299 if (ofw_bus_gen_setup_devinfo(&dinfo->obdinfo, child) != 0) { 300 free(dinfo, M_DEVBUF); 301 continue; 302 } 303 304 resource_list_init(&dinfo->rl); 305 ofw_bus_reg_to_rl(dev, child, sc->addr_cells, 306 sc->size_cells, &dinfo->rl); 307 308 cdev = device_add_child(dev, NULL, -1); 309 if (cdev == NULL) { 310 device_printf(dev, "<%s>: device_add_child failed\n", 311 dinfo->obdinfo.obd_name); 312 resource_list_free(&dinfo->rl); 313 ofw_bus_gen_destroy_devinfo(&dinfo->obdinfo); 314 free(dinfo, M_DEVBUF); 315 continue; 316 } 317 device_set_ivars(cdev, dinfo); 318 } 319 320 return (true); 321 } 322 323 static const struct ofw_bus_devinfo * 324 gic_ofw_get_devinfo(device_t bus __unused, device_t child) 325 { 326 struct arm_gic_devinfo *di; 327 328 di = device_get_ivars(child); 329 330 return (&di->obdinfo); 331 } 332 333 static struct ofw_compat_data gicv2m_compat_data[] = { 334 {"arm,gic-v2m-frame", true}, 335 {NULL, false} 336 }; 337 338 static int 339 arm_gicv2m_fdt_probe(device_t dev) 340 { 341 342 if (!ofw_bus_status_okay(dev)) 343 return (ENXIO); 344 345 if (!ofw_bus_search_compatible(dev, gicv2m_compat_data)->ocd_data) 346 return (ENXIO); 347 348 device_set_desc(dev, "ARM Generic Interrupt Controller MSI/MSIX"); 349 return (BUS_PROBE_DEFAULT); 350 } 351 352 static int 353 arm_gicv2m_fdt_attach(device_t dev) 354 { 355 struct arm_gicv2m_softc *sc; 356 357 sc = device_get_softc(dev); 358 sc->sc_xref = OF_xref_from_node(ofw_bus_get_node(dev)); 359 360 return (arm_gicv2m_attach(dev)); 361 } 362 363 static device_method_t arm_gicv2m_fdt_methods[] = { 364 /* Device interface */ 365 DEVMETHOD(device_probe, arm_gicv2m_fdt_probe), 366 DEVMETHOD(device_attach, arm_gicv2m_fdt_attach), 367 368 /* End */ 369 DEVMETHOD_END 370 }; 371 372 DEFINE_CLASS_1(gicv2m, arm_gicv2m_fdt_driver, arm_gicv2m_fdt_methods, 373 sizeof(struct arm_gicv2m_softc), arm_gicv2m_driver); 374 375 static devclass_t arm_gicv2m_fdt_devclass; 376 377 EARLY_DRIVER_MODULE(gicv2m, gic, arm_gicv2m_fdt_driver, 378 arm_gicv2m_fdt_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); 379 #endif 380