diff options
-rw-r--r-- | sys/sparc64/ebus/ebus.c | 495 | ||||
-rw-r--r-- | sys/sparc64/ebus/ebusvar.h | 67 |
2 files changed, 562 insertions, 0 deletions
diff --git a/sys/sparc64/ebus/ebus.c b/sys/sparc64/ebus/ebus.c new file mode 100644 index 0000000..2d3ebac --- /dev/null +++ b/sys/sparc64/ebus/ebus.c @@ -0,0 +1,495 @@ +/* + * Copyright (c) 1999, 2000 Matthew R. Green + * Copyright (c) 2001 Thomas Moestl <tmm@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: NetBSD: ebus.c,v 1.26 2001/09/10 16:27:53 eeh Exp + * + * $FreeBSD$ + */ + +#include "opt_ebus.h" + +/* + * UltraSPARC 5 and beyond ebus support. + * + * note that this driver is not complete: + * - ebus2 dma code is completely unwritten + * - interrupt establish is written and appears to work + * - bus map code is written and appears to work + * XXX: This is PCI specific, however, there exist SBus-to-EBus bridges... + * XXX: The EBus was designed to allow easy adaption of ISA devices to it - a + * compatability layer for ISA devices might be nice, although probably not + * easily possible because of some cruft (like in[bwl]/out[bwl] and friends). + * Additionally, the existing ISA code is limited to one ISA bus, however, + * there are machines with both ISA and EBus. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/malloc.h> + +#include <machine/bus.h> +#include <machine/ofw_bus.h> +#include <machine/resource.h> + +#include <ofw/openfirm.h> +#include <ofw/ofw_pci.h> + +#include <pci/pcireg.h> +#include <pci/pcivar.h> + +#include <sparc64/pci/ofw_pci.h> + +/* + * The register, ranges and interrupt map properties are identical to the ISA + * ones. + */ +#include <sparc64/isa/ofw_isa.h> +#include <sparc64/ebus/ebusvar.h> + +#ifdef EBUS_DEBUG +#define EDB_PROM 0x01 +#define EDB_CHILD 0x02 +#define EDB_INTRMAP 0x04 +#define EDB_BUSMAP 0x08 +#define EDB_BUSDMA 0x10 +#define EDB_INTR 0x20 +int ebus_debug = 0xff; +#define DPRINTF(l, s) do { if (ebus_debug & l) printf s; } while (0) +#else +#define DPRINTF(l, s) +#endif + +struct ebus_devinfo { + char *edi_name; /* PROM name */ + char *edi_compat; + phandle_t edi_node; /* PROM node */ + + struct resource_list edi_rl; +}; + +struct ebus_softc { + phandle_t sc_node; + + struct isa_ranges *sc_range; + + struct ofw_pci_register *sc_reg; + + int sc_imap_type; + + struct isa_imap *sc_ebus_imap; + struct isa_imap_msk sc_ebus_imapmsk; + + struct ofw_pci_imap *sc_pci_imap; + struct ofw_pci_imap_msk sc_pci_imapmsk; + + int sc_nrange; + int sc_nreg; + int sc_nimap; +}; + +static int ebus_probe(device_t); +static int ebus_print_child(device_t, device_t); +static void ebus_probe_nomatch(device_t, device_t); +static int ebus_read_ivar(device_t, device_t, int, uintptr_t *); +static int ebus_write_ivar(device_t, device_t, int, uintptr_t); +static struct resource *ebus_alloc_resource(device_t, device_t, int, int *, + u_long, u_long, u_long, u_int); +static struct resource_list *ebus_get_resource_list(device_t, device_t); + +static struct ebus_devinfo *ebus_setup_dinfo(struct ebus_softc *, + phandle_t, char *); +static void ebus_destroy_dinfo(struct ebus_devinfo *); +static int ebus_print_res(struct ebus_devinfo *); +static int ebus_map_intr(struct ebus_softc *, int, struct isa_regs *, int); + +static device_method_t ebus_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ebus_probe), + DEVMETHOD(device_attach, bus_generic_attach), + + /* Bus interface */ + DEVMETHOD(bus_print_child, ebus_print_child), + DEVMETHOD(bus_probe_nomatch, ebus_probe_nomatch), + DEVMETHOD(bus_read_ivar, ebus_read_ivar), + DEVMETHOD(bus_write_ivar, ebus_write_ivar), + DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), + DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), + DEVMETHOD(bus_alloc_resource, ebus_alloc_resource), + DEVMETHOD(bus_get_resource_list, ebus_get_resource_list), + DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), + DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), + DEVMETHOD(bus_release_resource, bus_generic_rl_release_resource), + DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), + + { 0, 0 } +}; + +static driver_t ebus_driver = { + "ebus", + ebus_methods, + sizeof(struct ebus_softc), +}; + +static devclass_t ebus_devclass; + +DRIVER_MODULE(ebus, pci, ebus_driver, ebus_devclass, 0, 0); + +static int +ebus_probe(device_t dev) +{ + struct ebus_softc *sc; + struct ebus_devinfo *edi; + char name[10]; + device_t cdev; + phandle_t node; + char *cname; + + /* + * XXX: PCI specific! There should be a common cookie IVAR value for all + * buses! Does not really matter much here though... + */ + node = ofw_pci_find_node(pci_get_bus(dev), pci_get_slot(dev), + pci_get_function(dev)); + if (node == 0) + return (ENXIO); + + /* Match a real ebus */ + OF_getprop(node, "name", &name, sizeof(name)); + if (pci_get_class(dev) != PCIC_BRIDGE || + pci_get_vendor(dev) != 0x108e || + strcmp(name, "ebus") != 0) + return (ENXIO); + + if (pci_get_device(dev) == 0x1000) + device_set_desc(dev, "PCI-EBus2 bridge"); + else if (pci_get_device(dev) == 0x1100) + device_set_desc(dev, "PCI-EBus3 bridge"); + else + return (ENXIO); + + device_printf(dev, "revision 0x%02x\n", pci_get_revid(dev)); + + sc = device_get_softc(dev); + sc->sc_node = node; + + /* + * Fill in our softc with information from the prom. + * There are two possible cases how interrupt mapping needs to be + * handled: + * - if the ebus node has an interrupt-map properties, the interrut + * numbers in child nodes can be mapped using lookups in this map, + * using the registers of the child node in question to find the + * map entry + * - if it does not have such a properties, the interrupts are mapped + * in the next higher interrupt map (PCI in our case), using the + * interrupt number of the child, but the registers of the ebus + * node, to find the mapping. + */ + sc->sc_imap_type = EBUS_IT_EBUS; + sc->sc_nimap = OF_getprop_alloc(node, "interrupt-map", + sizeof(*sc->sc_ebus_imap), (void **)&sc->sc_ebus_imap); + if (sc->sc_nimap == -1) { + sc->sc_nimap = ofw_pci_find_imap(node, &sc->sc_pci_imap, + &sc->sc_pci_imapmsk); + if (sc->sc_nimap == -1) + panic("ebus_probe: no interrupt map found"); + } else { + if (OF_getprop(node, "interrupt-map-mask", + &sc->sc_ebus_imapmsk, sizeof(sc->sc_ebus_imapmsk)) == -1) { + panic("ebus_probe: could not get ebus " + "interrupt-map-mask"); + } + } + + sc->sc_nrange = OF_getprop_alloc(node, "ranges", + sizeof(*sc->sc_range), (void **)&sc->sc_range); + sc->sc_nreg = OF_getprop_alloc(node, "reg", + sizeof(*sc->sc_reg), (void **)&sc->sc_reg); + if (sc->sc_nrange == -1 || sc->sc_nreg == -1) + panic("ebus_attach: could not get ranges/reg property"); + + /* + * now attach all our children + */ + DPRINTF(EDB_CHILD, ("ebus node %08x, searching children...\n", node)); + for (node = OF_child(node); node > 0; node = OF_peer(node)) { + if ((OF_getprop_alloc(node, "name", 1, (void **)&cname)) == -1) + continue; + + if ((edi = ebus_setup_dinfo(sc, node, cname)) == NULL) { + device_printf(dev, "<%s>: incomplete\n", cname); + free(cname, M_OFWPROP); + continue; + } + if ((cdev = device_add_child(dev, NULL, -1)) == NULL) + panic("ebus_attach: device_add_child failed"); + device_set_ivars(cdev, edi); + } + return (0); +} + +static int +ebus_print_child(device_t dev, device_t child) +{ + struct ebus_devinfo *edi; + int retval; + + edi = device_get_ivars(child); + retval = bus_print_child_header(dev, child); + retval += ebus_print_res(edi); + retval += bus_print_child_footer(dev, child); + return (retval); +} + +static void +ebus_probe_nomatch(device_t dev, device_t child) +{ + struct ebus_devinfo *edi; + + edi = device_get_ivars(child); + device_printf(dev, "<%s>", edi->edi_name); + ebus_print_res(edi); + printf(" (no driver attached)\n"); +} + +static int +ebus_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) +{ + struct ebus_devinfo *dinfo; + + if ((dinfo = device_get_ivars(child)) == NULL) + return (ENOENT); + switch (which) { + case EBUS_IVAR_COMPAT: + *result = (uintptr_t)dinfo->edi_compat; + case EBUS_IVAR_NAME: + *result = (uintptr_t)dinfo->edi_name; + break; + case EBUS_IVAR_NODE: + *result = dinfo->edi_node; + break; + default: + return (ENOENT); + } + return 0; +} + +static int +ebus_write_ivar(device_t dev, device_t child, int which, uintptr_t value) +{ + struct ebus_devinfo *dinfo; + + if ((dinfo = device_get_ivars(child)) == NULL) + return (ENOENT); + switch (which) { + case EBUS_IVAR_COMPAT: + case EBUS_IVAR_NAME: + case EBUS_IVAR_NODE: + return (EINVAL); + default: + return (ENOENT); + } + return 0; +} + +static struct resource * +ebus_alloc_resource(device_t bus, device_t child, int type, int *rid, + u_long start, u_long end, u_long count, u_int flags) +{ + struct ebus_softc *sc; + struct resource_list *rl; + struct resource_list_entry *rle; + struct ebus_devinfo *edi; + int passthrough = (device_get_parent(child) != bus); + int isdefault = (start == 0UL && end == ~0UL); + u_long nstart, nend; + int ptype; + + sc = (struct ebus_softc *)device_get_softc(bus); + edi = device_get_ivars(child); + rl = &edi->edi_rl; + /* + * Map ebus ranges to PCI ranges. This may include changing the + * allocation type. + */ + ptype = type; + nstart = start; + nend = end; + switch (type) { + case SYS_RES_IOPORT: + if (!isdefault && !passthrough) { + ptype = ofw_isa_map_iorange(sc->sc_range, sc->sc_nrange, + &nstart, &nend); + } + if (isdefault && passthrough) { + panic("ebus_alloc_resource: passthrough of default " + "allocation not supported"); + } + break; + case SYS_RES_IRQ: + break; + default: + panic("ebus_alloc_resource: unsupported resource type %d", + type); + } + + /* + * This inlines a modified resource_list_alloc(); this is needed + * because the resources need to be mapped into the bus space. + * This could be done when the resources are added to the resource + * lists, however doing it here is slightly more flexible. + */ + if (passthrough) { + return (BUS_ALLOC_RESOURCE(device_get_parent(bus), child, + ptype, rid, nstart, nend, count, flags)); + } + + rle = resource_list_find(rl, type, *rid); + if (rle == NULL) + return (NULL); /* no resource of that type/rid */ + + if (rle->res != NULL) + panic("ebus_alloc_resource: resource entry is busy"); + + if (isdefault) { + nstart = rle->start; + count = ulmax(count, rle->count); + nend = ulmax(rle->end, nstart + count - 1); + if (type == SYS_RES_IOPORT) { + ptype = ofw_isa_map_iorange(sc->sc_range, sc->sc_nrange, + &nstart, &nend); + } + } + + rle->res = BUS_ALLOC_RESOURCE(device_get_parent(bus), child, + ptype, rid, nstart, nend, count, flags); + + return (rle->res); + +} + +static struct resource_list * +ebus_get_resource_list(device_t dev, device_t child) +{ + struct ebus_devinfo *edi; + + edi = device_get_ivars(child); + return (&edi->edi_rl); +} + +static struct ebus_devinfo * +ebus_setup_dinfo(struct ebus_softc *sc, phandle_t node, char *name) +{ + struct ebus_devinfo *edi; + struct isa_regs *reg; + u_int32_t *intrs; + u_int64_t start; + int nreg, nintr, i, intr; + + edi = malloc(sizeof(*edi), M_DEVBUF, M_ZERO | M_WAITOK); + if (edi == NULL) + return (NULL); + resource_list_init(&edi->edi_rl); + edi->edi_name = name; + edi->edi_node = node; + + OF_getprop_alloc(node, "compat", 1, (void **)&edi->edi_compat); + nreg = OF_getprop_alloc(node, "reg", sizeof(*reg), (void **)®); + if (nreg == -1) { + ebus_destroy_dinfo(edi); + return (NULL); + } + for (i = 0; i < nreg; i++) { + start = ISA_REG_PHYS(reg + i); + /* + * XXX: use SYS_RES_IOPORT for compatability with ISA drivers - + * probably, SYS_RES_MEMORY would be more apporpriate, although + * that does not really matter. + */ + resource_list_add(&edi->edi_rl, SYS_RES_IOPORT, i, + start, start + reg[i].size, reg[i].size); + } + + nintr = OF_getprop_alloc(node, "interrupts", sizeof(*intrs), + (void **)&intrs); + for (i = 0; i < nintr; i++) { + intr = ebus_map_intr(sc, intrs[i], reg, nreg); + if (intr == -1) + panic("ebus_setup_dinfo: could not map ebus " + "interrupt %d", intrs[i]); + resource_list_add(&edi->edi_rl, SYS_RES_IRQ, i, + intr, intr, 1); + } + + return (edi); +} + +/* + * NOTE: This does not free the name member (it is needed afterwars in some + * cases). + */ +static void +ebus_destroy_dinfo(struct ebus_devinfo *edi) +{ + + resource_list_free(&edi->edi_rl); + free(edi, M_DEVBUF); +} + + +static int +ebus_print_res(struct ebus_devinfo *edi) +{ + int retval; + + retval = 0; + retval += resource_list_print_type(&edi->edi_rl, "addr", SYS_RES_IOPORT, + "%#lx"); + retval += resource_list_print_type(&edi->edi_rl, "irq", SYS_RES_IRQ, + "%ld"); + return (retval); +} + +static int +ebus_map_intr(struct ebus_softc *sc, int intr, struct isa_regs *regs, + int nregs) +{ + int rv; + + if (sc->sc_imap_type == EBUS_IT_PCI) { + rv = ofw_pci_route_intr2(intr, sc->sc_reg, sc->sc_pci_imap, + sc->sc_nimap, &sc->sc_pci_imapmsk); + if (rv == 255) + return (-1); + return (rv); + } + return (ofw_isa_map_intr(sc->sc_ebus_imap, sc->sc_nimap, + &sc->sc_ebus_imapmsk, intr, regs, nregs)); +} diff --git a/sys/sparc64/ebus/ebusvar.h b/sys/sparc64/ebus/ebusvar.h new file mode 100644 index 0000000..69cecc7 --- /dev/null +++ b/sys/sparc64/ebus/ebusvar.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 1999, 2000 Matthew R. Green + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: NetBSD: ebusvar.h,v 1.5 2001/07/20 00:07:13 eeh Exp + * and + * from: FreeBSD: src/sys/dev/pci/pcivar.h,v 1.51 2001/02/27 + * + * $FreeBSD$ + */ + +#ifndef _SPARC64_EBUS_EBUSVAR_H_ +#define _SPARC64_EBUS_EBUSVAR_H_ + +/* + * Interrupt map type (for the sc_imap_type softc member): + * If the ebus node has an interrupt map, this is set to EBUS_IT_EBUS (and the + * relevant sotfc members are initialized with the EBUS types), otherwise, + * it is EBUS_IT_PCI, in which case the standard PCI functions are used to + * map the interrupt (this is needed because the the maps and masks are + * different). + */ +#define EBUS_IT_EBUS 1 +#define EBUS_IT_PCI 2 + +enum ebus_device_ivars { + EBUS_IVAR_COMPAT, + EBUS_IVAR_NAME, + EBUS_IVAR_NODE, +}; + +/* + * Simplified accessors for ebus devices + */ +#define EBUS_ACCESSOR(var, ivar, type) \ + __BUS_ACCESSOR(ebus, var, EBUS, ivar, type) + +EBUS_ACCESSOR(compat, COMPAT, char *) +EBUS_ACCESSOR(name, NAME, char *) +EBUS_ACCESSOR(node, NODE, phandle_t) + +#undef EBUS_ACCESSOR + +#endif /* !_SPARC64_EBUS_EBUSVAR_H_ */ |