ofwbus.c revision 171805
1343181Sdim/*- 2343181Sdim * Copyright 1998 Massachusetts Institute of Technology 3353358Sdim * 4353358Sdim * Permission to use, copy, modify, and distribute this software and 5353358Sdim * its documentation for any purpose and without fee is hereby 6343181Sdim * granted, provided that both the above copyright notice and this 7343181Sdim * permission notice appear in all copies, that both the above 8343181Sdim * copyright notice and this permission notice appear in all 9343181Sdim * supporting documentation, and that the name of M.I.T. not be used 10343181Sdim * in advertising or publicity pertaining to distribution of the 11343181Sdim * software without specific, written prior permission. M.I.T. makes 12343181Sdim * no representations about the suitability of this software for any 13343181Sdim * purpose. It is provided "as is" without express or implied 14343181Sdim * warranty. 15343181Sdim * 16343181Sdim * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS 17343181Sdim * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE, 18343181Sdim * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19343181Sdim * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT 20343181Sdim * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21343181Sdim * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22343181Sdim * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 23343181Sdim * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24343181Sdim * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25343181Sdim * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 26343181Sdim * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27343181Sdim * SUCH DAMAGE. 28343181Sdim */ 29343181Sdim/*- 30343181Sdim * Copyright 2001 by Thomas Moestl <tmm@FreeBSD.org>. All rights reserved. 31343181Sdim * 32353358Sdim * Redistribution and use in source and binary forms, with or without 33343181Sdim * modification, are permitted provided that the following conditions 34343181Sdim * are met: 35343181Sdim * 1. Redistributions of source code must retain the above copyright 36353358Sdim * notice, this list of conditions and the following disclaimer. 37353358Sdim * 2. Redistributions in binary form must reproduce the above copyright 38353358Sdim * notice, this list of conditions and the following disclaimer in the 39353358Sdim * documentation and/or other materials provided with the distribution. 40343181Sdim * 41343181Sdim * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 42353358Sdim * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 43343181Sdim * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 44343181Sdim * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 45343181Sdim * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 46343181Sdim * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 47353358Sdim * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 48343181Sdim * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 49343181Sdim * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 50343181Sdim * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 51353358Sdim * SUCH DAMAGE. 52353358Sdim * 53353358Sdim * from: FreeBSD: src/sys/i386/i386/nexus.c,v 1.43 2001/02/09 54353358Sdim * 55343181Sdim * $FreeBSD: head/sys/powerpc/aim/nexus.c 171805 2007-08-11 19:25:32Z marcel $ 56343181Sdim */ 57343181Sdim#include "opt_psim.h" 58343181Sdim 59343181Sdim#include <sys/param.h> 60353358Sdim#include <sys/systm.h> 61353358Sdim#include <sys/module.h> 62353358Sdim#include <sys/bus.h> 63353358Sdim#include <sys/cons.h> 64343181Sdim#include <sys/kernel.h> 65343181Sdim#include <sys/malloc.h> 66343181Sdim 67343181Sdim#include <dev/ofw/openfirm.h> 68343181Sdim 69343181Sdim#include <machine/bus.h> 70343181Sdim#include <machine/frame.h> 71343181Sdim#include <machine/intr_machdep.h> 72343181Sdim#include <machine/nexusvar.h> 73343181Sdim#include <machine/resource.h> 74343181Sdim 75343181Sdim#include <sys/rman.h> 76343181Sdim 77343181Sdim#include "ofw_bus_if.h" 78343181Sdim#include "pic_if.h" 79343181Sdim 80343181Sdim/* 81343181Sdim * The nexus (which is a pseudo-bus actually) iterates over the nodes that 82343181Sdim * exist in Open Firmware and adds them as devices to this bus so that drivers 83343181Sdim * can be attached to them. 84353358Sdim * 85353358Sdim * Maybe this code should get into dev/ofw to some extent, as some of it should 86353358Sdim * work for all Open Firmware based machines... 87343181Sdim */ 88343181Sdim 89343181Sdimstatic MALLOC_DEFINE(M_NEXUS, "nexus", "nexus device information"); 90343181Sdim 91343181Sdimstruct nexus_devinfo { 92353358Sdim phandle_t ndi_node; 93343181Sdim /* Some common properties. */ 94343181Sdim const char *ndi_name; 95353358Sdim const char *ndi_device_type; 96343181Sdim const char *ndi_compatible; 97353358Sdim}; 98353358Sdim 99343181Sdimstruct nexus_softc { 100343181Sdim struct rman sc_rman; 101343181Sdim}; 102343181Sdim 103343181Sdim/* 104343181Sdim * Device interface 105343181Sdim */ 106343181Sdimstatic int nexus_probe(device_t); 107343181Sdimstatic void nexus_probe_nomatch(device_t, device_t); 108343181Sdim 109343181Sdim/* 110343181Sdim * Bus interface 111343181Sdim */ 112343181Sdimstatic device_t nexus_add_child(device_t, int, const char *, int); 113343181Sdimstatic int nexus_read_ivar(device_t, device_t, int, uintptr_t *); 114343181Sdimstatic int nexus_write_ivar(device_t, device_t, int, uintptr_t); 115343181Sdimstatic int nexus_setup_intr(device_t, device_t, struct resource *, int, 116343181Sdim driver_filter_t *, driver_intr_t *, void *, void **); 117343181Sdimstatic int nexus_teardown_intr(device_t, device_t, struct resource *, 118343181Sdim void *); 119343181Sdimstatic struct resource *nexus_alloc_resource(device_t, device_t, int, int *, 120343181Sdim u_long, u_long, u_long, u_int); 121353358Sdimstatic int nexus_activate_resource(device_t, device_t, int, int, 122343181Sdim struct resource *); 123343181Sdimstatic int nexus_deactivate_resource(device_t, device_t, int, int, 124343181Sdim struct resource *); 125343181Sdimstatic int nexus_release_resource(device_t, device_t, int, int, 126353358Sdim struct resource *); 127343181Sdim 128343181Sdimstatic phandle_t nexus_ofw_get_node(device_t, device_t); 129343181Sdimstatic const char *nexus_ofw_get_name(device_t, device_t); 130343181Sdimstatic const char *nexus_ofw_get_type(device_t, device_t); 131343181Sdimstatic const char *nexus_ofw_get_compat(device_t, device_t); 132343181Sdim 133343181Sdim/* 134343181Sdim * Local routines 135343181Sdim */ 136353358Sdimstatic device_t nexus_device_from_node(device_t, phandle_t); 137343181Sdim 138343181Sdimstatic device_method_t nexus_methods[] = { 139343181Sdim /* Device interface */ 140343181Sdim DEVMETHOD(device_probe, nexus_probe), 141353358Sdim DEVMETHOD(device_attach, bus_generic_attach), 142343181Sdim DEVMETHOD(device_detach, bus_generic_detach), 143343181Sdim DEVMETHOD(device_shutdown, bus_generic_shutdown), 144343181Sdim DEVMETHOD(device_suspend, bus_generic_suspend), 145343181Sdim DEVMETHOD(device_resume, bus_generic_resume), 146343181Sdim 147343181Sdim /* Bus interface. Resource management is business of the children... */ 148343181Sdim DEVMETHOD(bus_add_child, nexus_add_child), 149343181Sdim DEVMETHOD(bus_print_child, bus_generic_print_child), 150343181Sdim DEVMETHOD(bus_probe_nomatch, nexus_probe_nomatch), 151353358Sdim DEVMETHOD(bus_read_ivar, nexus_read_ivar), 152353358Sdim DEVMETHOD(bus_write_ivar, nexus_write_ivar), 153353358Sdim DEVMETHOD(bus_setup_intr, nexus_setup_intr), 154343181Sdim DEVMETHOD(bus_teardown_intr, nexus_teardown_intr), 155343181Sdim DEVMETHOD(bus_alloc_resource, nexus_alloc_resource), 156343181Sdim DEVMETHOD(bus_activate_resource, nexus_activate_resource), 157343181Sdim DEVMETHOD(bus_deactivate_resource, nexus_deactivate_resource), 158353358Sdim DEVMETHOD(bus_release_resource, nexus_release_resource), 159343181Sdim 160343181Sdim /* OFW bus interface */ 161343181Sdim DEVMETHOD(ofw_bus_get_node, nexus_ofw_get_node), 162343181Sdim DEVMETHOD(ofw_bus_get_name, nexus_ofw_get_name), 163343181Sdim DEVMETHOD(ofw_bus_get_type, nexus_ofw_get_type), 164343181Sdim DEVMETHOD(ofw_bus_get_compat, nexus_ofw_get_compat), 165343181Sdim 166343181Sdim { 0, 0 } 167343181Sdim}; 168343181Sdim 169343181Sdimstatic driver_t nexus_driver = { 170343181Sdim "nexus", 171343181Sdim nexus_methods, 172343181Sdim sizeof(struct nexus_softc), 173353358Sdim}; 174343181Sdim 175343181Sdimstatic devclass_t nexus_devclass; 176343181Sdim 177343181SdimDRIVER_MODULE(nexus, root, nexus_driver, nexus_devclass, 0, 0); 178353358Sdim 179343181Sdimstatic int 180343181Sdimnexus_probe(device_t dev) 181353358Sdim{ 182353358Sdim phandle_t root; 183343181Sdim phandle_t child; 184343181Sdim struct nexus_softc *sc; 185343181Sdim u_long start, end; 186343181Sdim 187343181Sdim if ((root = OF_peer(0)) == -1) 188343181Sdim panic("nexus_probe: OF_peer failed."); 189343181Sdim 190343181Sdim sc = device_get_softc(dev); 191343181Sdim 192343181Sdim start = 0; 193343181Sdim end = INTR_VECTORS - 1; 194343181Sdim 195343181Sdim sc->sc_rman.rm_start = start; 196353358Sdim sc->sc_rman.rm_end = end; 197343181Sdim sc->sc_rman.rm_type = RMAN_ARRAY; 198343181Sdim sc->sc_rman.rm_descr = "Interrupt request lines"; 199353358Sdim if (rman_init(&sc->sc_rman) || 200353358Sdim rman_manage_region(&sc->sc_rman, start, end)) 201343181Sdim panic("nexus_probe IRQ rman"); 202343181Sdim 203353358Sdim /* 204343181Sdim * Allow devices to identify 205343181Sdim */ 206353358Sdim bus_generic_probe(dev); 207353358Sdim 208343181Sdim /* 209343181Sdim * Now walk the OFW tree to locate top-level devices 210343181Sdim */ 211343181Sdim for (child = OF_child(root); child != 0; child = OF_peer(child)) { 212343181Sdim if (child == -1) 213343181Sdim panic("nexus_probe(): OF_child failed."); 214343181Sdim (void)nexus_device_from_node(dev, child); 215353358Sdim 216343181Sdim } 217343181Sdim device_set_desc(dev, "Open Firmware Nexus device"); 218343181Sdim return (0); 219343181Sdim} 220343181Sdim 221343181Sdimstatic void 222353358Sdimnexus_probe_nomatch(device_t dev, device_t child) 223343181Sdim{ 224353358Sdim char *name, *type; 225343181Sdim 226343181Sdim if (BUS_READ_IVAR(dev, child, NEXUS_IVAR_NAME, 227343181Sdim (uintptr_t *)&name) != 0 || 228343181Sdim BUS_READ_IVAR(dev, child, NEXUS_IVAR_DEVICE_TYPE, 229343181Sdim (uintptr_t *)&type) != 0) 230343181Sdim return; 231343181Sdim 232343181Sdim if (type == NULL) 233343181Sdim type = "(unknown)"; 234343181Sdim 235343181Sdim if (bootverbose) 236343181Sdim device_printf(dev, "<%s>, type %s (no driver attached)\n", 237343181Sdim name, type); 238343181Sdim} 239343181Sdim 240343181Sdimstatic device_t 241343181Sdimnexus_add_child(device_t dev, int order, const char *name, int unit) 242343181Sdim{ 243343181Sdim device_t child; 244343181Sdim struct nexus_devinfo *dinfo; 245343181Sdim 246343181Sdim child = device_add_child_ordered(dev, order, name, unit); 247343181Sdim if (child == NULL) 248343181Sdim return (NULL); 249343181Sdim 250353358Sdim dinfo = malloc(sizeof(struct nexus_devinfo), M_NEXUS, M_NOWAIT|M_ZERO); 251343181Sdim if (dinfo == NULL) 252343181Sdim return (NULL); 253343181Sdim 254343181Sdim dinfo->ndi_node = -1; 255343181Sdim dinfo->ndi_name = name; 256343181Sdim device_set_ivars(child, dinfo); 257343181Sdim 258343181Sdim return (child); 259343181Sdim} 260343181Sdim 261343181Sdim 262343181Sdimstatic int 263343181Sdimnexus_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) 264353358Sdim{ 265343181Sdim struct nexus_devinfo *dinfo; 266343181Sdim 267343181Sdim if ((dinfo = device_get_ivars(child)) == 0) 268353358Sdim return (ENOENT); 269343181Sdim switch (which) { 270343181Sdim case NEXUS_IVAR_NODE: 271343181Sdim *result = dinfo->ndi_node; 272353358Sdim break; 273343181Sdim case NEXUS_IVAR_NAME: 274343181Sdim *result = (uintptr_t)dinfo->ndi_name; 275343181Sdim break; 276343181Sdim case NEXUS_IVAR_DEVICE_TYPE: 277343181Sdim *result = (uintptr_t)dinfo->ndi_device_type; 278343181Sdim break; 279343181Sdim case NEXUS_IVAR_COMPATIBLE: 280343181Sdim *result = (uintptr_t)dinfo->ndi_compatible; 281343181Sdim break; 282343181Sdim default: 283343181Sdim return (ENOENT); 284343181Sdim } 285343181Sdim return 0; 286343181Sdim} 287343181Sdim 288343181Sdimstatic int 289343181Sdimnexus_write_ivar(device_t dev, device_t child, int which, uintptr_t value) 290343181Sdim{ 291343181Sdim struct nexus_devinfo *dinfo; 292343181Sdim 293343181Sdim if ((dinfo = device_get_ivars(child)) == 0) 294343181Sdim return (ENOENT); 295343181Sdim 296343181Sdim switch (which) { 297343181Sdim case NEXUS_IVAR_NAME: 298343181Sdim return (EINVAL); 299343181Sdim 300343181Sdim /* Identified devices may want to set these */ 301343181Sdim case NEXUS_IVAR_NODE: 302343181Sdim dinfo->ndi_node = (phandle_t)value; 303343181Sdim break; 304343181Sdim 305343181Sdim case NEXUS_IVAR_DEVICE_TYPE: 306343181Sdim dinfo->ndi_device_type = (char *)value; 307343181Sdim break; 308343181Sdim 309343181Sdim default: 310343181Sdim return (ENOENT); 311343181Sdim } 312353358Sdim return 0; 313343181Sdim} 314343181Sdim 315343181Sdimstatic int 316353358Sdimnexus_setup_intr(device_t dev, device_t child, struct resource *res, int flags, 317343181Sdim driver_filter_t *filter, driver_intr_t *ihand, void *arg, void **cookiep) 318343181Sdim{ 319343181Sdim driver_t *driver; 320353358Sdim int error; 321343181Sdim 322343181Sdim /* somebody tried to setup an irq that failed to allocate! */ 323343181Sdim if (res == NULL) 324343181Sdim panic("nexus_setup_intr: NULL irq resource!"); 325343181Sdim 326343181Sdim *cookiep = 0; 327343181Sdim if ((rman_get_flags(res) & RF_SHAREABLE) == 0) 328343181Sdim flags |= INTR_EXCL; 329353358Sdim 330343181Sdim driver = device_get_driver(child); 331353358Sdim 332343181Sdim /* 333343181Sdim * We depend here on rman_activate_resource() being idempotent. 334343181Sdim */ 335353358Sdim error = rman_activate_resource(res); 336343181Sdim if (error) 337343181Sdim return (error); 338353358Sdim 339343181Sdim error = powerpc_setup_intr(device_get_nameunit(child), 340343181Sdim rman_get_start(res), filter, ihand, arg, flags, cookiep); 341343181Sdim 342343181Sdim return (error); 343343181Sdim} 344343181Sdim 345343181Sdimstatic int 346343181Sdimnexus_teardown_intr(device_t dev, device_t child, struct resource *res, 347343181Sdim void *cookie) 348353358Sdim{ 349343181Sdim 350343181Sdim return (powerpc_teardown_intr(cookie)); 351343181Sdim} 352353358Sdim 353343181Sdim/* 354343181Sdim * Allocate resources at the behest of a child. This only handles interrupts, 355343181Sdim * since I/O resources are handled by child busses. 356343181Sdim */ 357343181Sdimstatic struct resource * 358343181Sdimnexus_alloc_resource(device_t bus, device_t child, int type, int *rid, 359343181Sdim u_long start, u_long end, u_long count, u_int flags) 360343181Sdim{ 361343181Sdim struct nexus_softc *sc; 362343181Sdim struct resource *rv; 363343181Sdim 364343181Sdim if (type != SYS_RES_IRQ) { 365343181Sdim device_printf(bus, "unknown resource request from %s\n", 366343181Sdim device_get_nameunit(child)); 367343181Sdim return (NULL); 368343181Sdim } 369343181Sdim 370343181Sdim if (count == 0 || start + count - 1 != end) { 371353358Sdim device_printf(bus, "invalid IRQ allocation from %s\n", 372343181Sdim device_get_nameunit(child)); 373343181Sdim return (NULL); 374353358Sdim } 375343181Sdim 376343181Sdim sc = device_get_softc(bus); 377353358Sdim 378343181Sdim rv = rman_reserve_resource(&sc->sc_rman, start, end, count, 379343181Sdim flags, child); 380343181Sdim if (rv == NULL) { 381353358Sdim device_printf(bus, "IRQ allocation failed for %s\n", 382343181Sdim device_get_nameunit(child)); 383343181Sdim } else 384343181Sdim rman_set_rid(rv, *rid); 385343181Sdim 386343181Sdim return (rv); 387343181Sdim} 388343181Sdim 389343181Sdimstatic int 390343181Sdimnexus_activate_resource(device_t bus, device_t child, int type, int rid, 391343181Sdim struct resource *res) 392343181Sdim{ 393353358Sdim 394343181Sdim /* Not much to be done yet... */ 395343181Sdim return (rman_activate_resource(res)); 396343181Sdim} 397343181Sdim 398353358Sdimstatic int 399343181Sdimnexus_deactivate_resource(device_t bus, device_t child, int type, int rid, 400343181Sdim struct resource *res) 401353358Sdim{ 402343181Sdim 403343181Sdim /* Not much to be done yet... */ 404353358Sdim return (rman_deactivate_resource(res)); 405343181Sdim} 406343181Sdim 407343181Sdimstatic int 408343181Sdimnexus_release_resource(device_t bus, device_t child, int type, int rid, 409343181Sdim struct resource *res) 410343181Sdim{ 411343181Sdim 412343181Sdim if (type != SYS_RES_IRQ) { 413343181Sdim device_printf(bus, "unknown resource request from %s\n", 414343181Sdim device_get_nameunit(child)); 415343181Sdim return (EINVAL); 416343181Sdim } 417353358Sdim 418353358Sdim return (rman_release_resource(res)); 419353358Sdim} 420353358Sdim 421353358Sdimstatic device_t 422343181Sdimnexus_device_from_node(device_t parent, phandle_t node) 423343181Sdim{ 424343181Sdim device_t cdev; 425343181Sdim struct nexus_devinfo *dinfo; 426343181Sdim char *name, *type, *compatible; 427353358Sdim 428353358Sdim OF_getprop_alloc(node, "name", 1, (void **)&name); 429353358Sdim OF_getprop_alloc(node, "device_type", 1, (void **)&type); 430353358Sdim OF_getprop_alloc(node, "compatible", 1, (void **)&compatible); 431353358Sdim cdev = device_add_child(parent, NULL, -1); 432353358Sdim if (cdev != NULL) { 433353358Sdim dinfo = malloc(sizeof(*dinfo), M_NEXUS, M_WAITOK); 434353358Sdim dinfo->ndi_node = node; 435353358Sdim dinfo->ndi_name = name; 436353358Sdim dinfo->ndi_device_type = type; 437353358Sdim dinfo->ndi_compatible = compatible; 438353358Sdim device_set_ivars(cdev, dinfo); 439343181Sdim } else 440343181Sdim free(name, M_OFWPROP); 441343181Sdim 442343181Sdim return (cdev); 443343181Sdim} 444343181Sdim 445343181Sdimstatic const char * 446343181Sdimnexus_ofw_get_name(device_t bus, device_t dev) 447343181Sdim{ 448343181Sdim struct nexus_devinfo *dinfo; 449343181Sdim 450343181Sdim if ((dinfo = device_get_ivars(dev)) == NULL) 451343181Sdim return (NULL); 452343181Sdim 453343181Sdim return (dinfo->ndi_name); 454343181Sdim} 455343181Sdim 456343181Sdimstatic phandle_t 457343181Sdimnexus_ofw_get_node(device_t bus, device_t dev) 458343181Sdim{ 459343181Sdim struct nexus_devinfo *dinfo; 460343181Sdim 461343181Sdim if ((dinfo = device_get_ivars(dev)) == NULL) 462343181Sdim return (0); 463343181Sdim 464343181Sdim return (dinfo->ndi_node); 465343181Sdim} 466343181Sdim 467343181Sdimstatic const char * 468343181Sdimnexus_ofw_get_type(device_t bus, device_t dev) 469343181Sdim{ 470343181Sdim struct nexus_devinfo *dinfo; 471343181Sdim 472343181Sdim if ((dinfo = device_get_ivars(dev)) == NULL) 473343181Sdim return (NULL); 474343181Sdim 475343181Sdim return (dinfo->ndi_device_type); 476343181Sdim} 477343181Sdim 478343181Sdimstatic const char * 479343181Sdimnexus_ofw_get_compat(device_t bus, device_t dev) 480343181Sdim{ 481343181Sdim struct nexus_devinfo *dinfo; 482343181Sdim 483343181Sdim if ((dinfo = device_get_ivars(dev)) == NULL) 484343181Sdim return (NULL); 485343181Sdim 486343181Sdim return (dinfo->ndi_compatible); 487343181Sdim} 488343181Sdim