diff options
Diffstat (limited to 'usr/src/uts/sun4u/montecarlo/io/acebus.c')
| -rw-r--r-- | usr/src/uts/sun4u/montecarlo/io/acebus.c | 1111 |
1 files changed, 1111 insertions, 0 deletions
diff --git a/usr/src/uts/sun4u/montecarlo/io/acebus.c b/usr/src/uts/sun4u/montecarlo/io/acebus.c new file mode 100644 index 0000000000..6608cbf3de --- /dev/null +++ b/usr/src/uts/sun4u/montecarlo/io/acebus.c @@ -0,0 +1,1111 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2005 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/types.h> +#include <sys/conf.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/ddi_impldefs.h> +#include <sys/ddi_subrdefs.h> +#include <sys/pci.h> +#include <sys/pci/pci_nexus.h> +#include <sys/autoconf.h> +#include <sys/cmn_err.h> +#include <sys/errno.h> +#include <sys/kmem.h> +#include <sys/debug.h> +#include <sys/sysmacros.h> +#include <sys/acebus.h> + +#ifdef DEBUG +static uint_t acebus_debug_flags = 0; +#endif + +/* + * The values of the following variables are used to initialize + * the cache line size and latency timer registers in the ebus + * configuration header. Variables are used instead of constants + * to allow tuning from the /etc/system file. + */ +static uint8_t acebus_cache_line_size = 0x10; /* 64 bytes */ +static uint8_t acebus_latency_timer = 0x40; /* 64 PCI cycles */ + +/* + * function prototypes for bus ops routines: + */ +static int +acebus_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp, + off_t offset, off_t len, caddr_t *addrp); +static int +acebus_ctlops(dev_info_t *dip, dev_info_t *rdip, + ddi_ctl_enum_t op, void *arg, void *result); +static int +acebus_intr_ops(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op, + ddi_intr_handle_impl_t *hdlp, void *result); + +/* + * function prototypes for dev ops routines: + */ +static int acebus_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); +static int acebus_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); + +/* + * general function prototypes: + */ +static int acebus_config(ebus_devstate_t *ebus_p); +static int acebus_apply_range(ebus_devstate_t *ebus_p, dev_info_t *rdip, + ebus_regspec_t *ebus_rp, pci_regspec_t *rp); +static int acebus_get_ranges_prop(ebus_devstate_t *ebus_p); +#ifdef ACEBUS_HOTPLUG +static int acebus_update_props(ebus_devstate_t *ebus_p); +static int acebus_set_imap(dev_info_t *dip); +#endif + +#define getprop(dip, name, addr, intp) \ + ddi_getlongprop(DDI_DEV_T_ANY, (dip), DDI_PROP_DONTPASS, \ + (name), (caddr_t)(addr), (intp)) + +/* + * bus ops and dev ops structures: + */ +static struct bus_ops acebus_bus_ops = { + BUSO_REV, + acebus_map, + NULL, + NULL, + NULL, + i_ddi_map_fault, + ddi_dma_map, + ddi_dma_allochdl, + ddi_dma_freehdl, + ddi_dma_bindhdl, + ddi_dma_unbindhdl, + ddi_dma_flush, + ddi_dma_win, + ddi_dma_mctl, + acebus_ctlops, + ddi_bus_prop_op, + 0, /* (*bus_get_eventcookie)(); */ + 0, /* (*bus_add_eventcall)(); */ + 0, /* (*bus_remove_eventcall)(); */ + 0, /* (*bus_post_event)(); */ + 0, /* (*bus_intr_ctl)(); */ + NULL, /* (*bus_config)(); */ + NULL, /* (*bus_unconfig)(); */ + NULL, /* (*bus_fm_init)(); */ + NULL, /* (*bus_fm_fini)(); */ + NULL, /* (*bus_fm_access_enter)(); */ + NULL, /* (*bus_fm_access_fini)(); */ + NULL, /* (*bus_power)(); */ + acebus_intr_ops /* (*bus_intr_op)(); */ +}; + +static struct dev_ops acebus_ops = { + DEVO_REV, + 0, + ddi_no_info, + nulldev, + nulldev, + acebus_attach, + acebus_detach, + nodev, + (struct cb_ops *)0, + &acebus_bus_ops +}; + +/* + * module definitions: + */ +#include <sys/modctl.h> +extern struct mod_ops mod_driverops; + +static struct modldrv modldrv = { + &mod_driverops, /* Type of module. This one is a driver */ + "Alarm Card ebus nexus v%I%", /* Name of module. */ + &acebus_ops, /* driver ops */ +}; + +static struct modlinkage modlinkage = { + MODREV_1, (void *)&modldrv, NULL +}; + +/* + * driver global data: + */ +static void *per_acebus_state; /* per-ebus soft state pointer */ + + +int +_init(void) +{ + int e; + + /* + * Initialize per-ebus soft state pointer. + */ + e = ddi_soft_state_init(&per_acebus_state, sizeof (ebus_devstate_t), 1); + if (e != 0) + return (e); + + /* + * Install the module. + */ + e = mod_install(&modlinkage); + if (e != 0) + ddi_soft_state_fini(&per_acebus_state); + return (e); +} + +int +_fini(void) +{ + int e; + + /* + * Remove the module. + */ + e = mod_remove(&modlinkage); + if (e != 0) + return (e); + + /* + * Free the soft state info. + */ + ddi_soft_state_fini(&per_acebus_state); + return (e); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} + +/* device driver entry points */ + +/* + * attach entry point: + * + * normal attach: + * + * create soft state structure (dip, reg, nreg and state fields) + * map in configuration header + * make sure device is properly configured + * report device + */ +static int +acebus_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) +{ + ebus_devstate_t *ebus_p; /* per ebus state pointer */ + int instance; + + DBG1(D_ATTACH, NULL, "dip=%x\n", dip); + switch (cmd) { + case DDI_ATTACH: + + /* + * Allocate soft state for this instance. + */ + instance = ddi_get_instance(dip); + if (ddi_soft_state_zalloc(per_acebus_state, instance) + != DDI_SUCCESS) { + DBG(D_ATTACH, NULL, "failed to alloc soft state\n"); + return (DDI_FAILURE); + } + ebus_p = get_acebus_soft_state(instance); + ebus_p->dip = dip; + + /* + * Make sure the master enable and memory access enable + * bits are set in the config command register. + */ + if (!acebus_config(ebus_p)) { + free_acebus_soft_state(instance); + return (DDI_FAILURE); + } + + (void) ddi_prop_create(DDI_DEV_T_NONE, dip, + DDI_PROP_CANSLEEP, "no-dma-interrupt-sync", NULL, 0); + /* Get our ranges property for mapping child registers. */ + if (acebus_get_ranges_prop(ebus_p) != DDI_SUCCESS) { + free_acebus_soft_state(instance); + return (DDI_FAILURE); + } + + /* + * Make the state as attached and report the device. + */ + ebus_p->state = ATTACHED; + ddi_report_dev(dip); + DBG(D_ATTACH, ebus_p, "returning\n"); + return (DDI_SUCCESS); + + case DDI_RESUME: + + instance = ddi_get_instance(dip); + ebus_p = get_acebus_soft_state(instance); + + /* + * Make sure the master enable and memory access enable + * bits are set in the config command register. + */ + if (!acebus_config(ebus_p)) { + free_acebus_soft_state(instance); + return (DDI_FAILURE); + } + + ebus_p->state = RESUMED; + return (DDI_SUCCESS); + } + return (DDI_FAILURE); +} + +/* + * detach entry point: + */ +static int +acebus_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) +{ + int instance = ddi_get_instance(dip); + ebus_devstate_t *ebus_p = get_acebus_soft_state(instance); + + switch (cmd) { + case DDI_DETACH: + DBG1(D_DETACH, ebus_p, "DDI_DETACH dip=%p\n", dip); + ddi_prop_remove_all(dip); + kmem_free(ebus_p->rangep, ebus_p->range_cnt * + sizeof (struct ebus_pci_rangespec)); + free_acebus_soft_state(instance); + return (DDI_SUCCESS); + + case DDI_SUSPEND: + DBG1(D_DETACH, ebus_p, "DDI_SUSPEND dip=%p\n", dip); + ebus_p->state = SUSPENDED; + return (DDI_SUCCESS); + } + return (DDI_FAILURE); +} + + +static int +acebus_get_ranges_prop(ebus_devstate_t *ebus_p) +{ + struct ebus_pci_rangespec *rangep; + int nrange, range_len; + + if (ddi_getlongprop(DDI_DEV_T_ANY, ebus_p->dip, DDI_PROP_DONTPASS, + "ranges", (caddr_t)&rangep, &range_len) != DDI_SUCCESS) { + + cmn_err(CE_WARN, "%s%d: can't get ranges property", + ddi_get_name(ebus_p->dip), ddi_get_instance(ebus_p->dip)); + return (DDI_ME_REGSPEC_RANGE); + } + + nrange = range_len / sizeof (struct ebus_pci_rangespec); + + if (nrange == 0) { + kmem_free(rangep, range_len); + return (DDI_FAILURE); + } + +#ifdef DEBUG + /* */ { + int i; + + for (i = 0; i < nrange; i++) { + DBG5(D_MAP, ebus_p, "ebus range addr 0x%x.0x%x PCI range " + "addr 0x%x.0x%x.0x%x ", rangep[i].ebus_phys_hi, + rangep[i].ebus_phys_low, rangep[i].pci_phys_hi, + rangep[i].pci_phys_mid, rangep[i].pci_phys_low); + DBG1(D_MAP, ebus_p, "Size 0x%x\n", rangep[i].rng_size); + } + } +#endif /* DEBUG */ + + ebus_p->rangep = rangep; + ebus_p->range_cnt = nrange; + + return (DDI_SUCCESS); +} + + +/* bus driver entry points */ + +/* + * bus map entry point: + * + * if map request is for an rnumber + * get the corresponding regspec from device node + * build a new regspec in our parent's format + * build a new map_req with the new regspec + * call up the tree to complete the mapping + */ +static int +acebus_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp, + off_t off, off_t len, caddr_t *addrp) +{ + ebus_devstate_t *ebus_p = get_acebus_soft_state(ddi_get_instance(dip)); + ebus_regspec_t *ebus_rp, *ebus_regs; + pci_regspec_t pci_reg; + ddi_map_req_t p_map_request; + int rnumber, i, n; + int rval = DDI_SUCCESS; + + /* + * Handle the mapping according to its type. + */ + DBG4(D_MAP, ebus_p, "rdip=%s%d: off=%x len=%x\n", + ddi_get_name(rdip), ddi_get_instance(rdip), off, len); + switch (mp->map_type) { + case DDI_MT_REGSPEC: + + /* + * We assume the register specification is in ebus format. + * We must convert it into a PCI format regspec and pass + * the request to our parent. + */ + DBG3(D_MAP, ebus_p, "rdip=%s%d: REGSPEC - handlep=%x\n", + ddi_get_name(rdip), ddi_get_instance(rdip), + mp->map_handlep); + ebus_rp = (ebus_regspec_t *)mp->map_obj.rp; + break; + + case DDI_MT_RNUMBER: + + /* + * Get the "reg" property from the device node and convert + * it to our parent's format. + */ + rnumber = mp->map_obj.rnumber; + DBG4(D_MAP, ebus_p, "rdip=%s%d: rnumber=%x handlep=%x\n", + ddi_get_name(rdip), ddi_get_instance(rdip), + rnumber, mp->map_handlep); + + if (getprop(rdip, "reg", &ebus_regs, &i) != DDI_SUCCESS) { + DBG(D_MAP, ebus_p, "can't get reg property\n"); + return (DDI_ME_RNUMBER_RANGE); + } + n = i / sizeof (ebus_regspec_t); + + if (rnumber < 0 || rnumber >= n) { + DBG(D_MAP, ebus_p, "rnumber out of range\n"); + return (DDI_ME_RNUMBER_RANGE); + } + ebus_rp = &ebus_regs[rnumber]; + break; + + default: + return (DDI_ME_INVAL); + + } + + /* Adjust our reg property with offset and length */ + ebus_rp->addr_low += off; + if (len) + ebus_rp->size = len; + + /* + * Now we have a copy the "reg" entry we're attempting to map. + * Translate this into our parents PCI address using the ranges + * property. + */ + rval = acebus_apply_range(ebus_p, rdip, ebus_rp, &pci_reg); + + if (mp->map_type == DDI_MT_RNUMBER) + kmem_free((caddr_t)ebus_regs, i); + + if (rval != DDI_SUCCESS) + return (rval); + +#ifdef ACEBUS_HOTPLUG + /* + * The map operation provides a translated (not a re-assigned, or + * relocated) ebus address for the child in its address space(range). + * Ebus address space is relocatible but its child address space + * is not. As specified by their 'reg' properties, they reside + * at a fixed offset in their parent's (ebus's) space. + * + * By setting this bit, we will not run into HostPCI nexus + * trying to relocate a translated ebus address (which is already + * relocated) and failing the operation. + * The reason for doing this here is that the PCI hotplug configurator + * always marks the ebus space as relocatible (unlike OBP) and that + * information is implied for the child too, which is wrong. + */ + pci_reg.pci_phys_hi |= PCI_RELOCAT_B; +#endif +#ifdef DEBUG + DBG5(D_MAP, ebus_p, "(%x,%x,%x)(%x,%x)\n", + pci_reg.pci_phys_hi, + pci_reg.pci_phys_mid, + pci_reg.pci_phys_low, + pci_reg.pci_size_hi, + pci_reg.pci_size_low); +#endif + + p_map_request = *mp; + p_map_request.map_type = DDI_MT_REGSPEC; + p_map_request.map_obj.rp = (struct regspec *)&pci_reg; + rval = ddi_map(dip, &p_map_request, 0, 0, addrp); + DBG1(D_MAP, ebus_p, "parent returned %x\n", rval); + return (rval); +} + + +static int +acebus_apply_range(ebus_devstate_t *ebus_p, dev_info_t *rdip, + ebus_regspec_t *ebus_rp, pci_regspec_t *rp) +{ + int b; + int rval = DDI_SUCCESS; + struct ebus_pci_rangespec *rangep = ebus_p->rangep; + int nrange = ebus_p->range_cnt; + static const char out_of_range[] = + "Out of range register specification from device node <%s>"; + + DBG3(D_MAP, ebus_p, "Range Matching Addr 0x%x.%x size 0x%x\n", + ebus_rp->addr_hi, ebus_rp->addr_low, ebus_rp->size); + + for (b = 0; b < nrange; ++b, ++rangep) { + + /* Check for the correct space */ + if (ebus_rp->addr_hi == rangep->ebus_phys_hi) + /* See if we fit in this range */ + if ((ebus_rp->addr_low >= + rangep->ebus_phys_low) && + ((ebus_rp->addr_low + ebus_rp->size - 1) + <= (rangep->ebus_phys_low + + rangep->rng_size - 1))) { + uint_t addr_offset = ebus_rp->addr_low - + rangep->ebus_phys_low; + /* + * Use the range entry to translate + * the EBUS physical address into the + * parents PCI space. + */ + rp->pci_phys_hi = + rangep->pci_phys_hi; + rp->pci_phys_mid = rangep->pci_phys_mid; + rp->pci_phys_low = + rangep->pci_phys_low + addr_offset; + rp->pci_size_hi = 0; + rp->pci_size_low = + min(ebus_rp->size, (rangep->rng_size - + addr_offset)); + + DBG2(D_MAP, ebus_p, "Child hi0x%x lo0x%x ", + rangep->ebus_phys_hi, + rangep->ebus_phys_low); + DBG4(D_MAP, ebus_p, "Parent hi0x%x " + "mid0x%x lo0x%x size 0x%x\n", + rangep->pci_phys_hi, + rangep->pci_phys_mid, + rangep->pci_phys_low, + rangep->rng_size); + + break; + } + } + + if (b == nrange) { + cmn_err(CE_WARN, out_of_range, ddi_get_name(rdip)); + return (DDI_ME_REGSPEC_RANGE); + } + + return (rval); +} + + +/* + * control ops entry point: + * + * Requests handled completely: + * DDI_CTLOPS_INITCHILD + * DDI_CTLOPS_UNINITCHILD + * DDI_CTLOPS_REPORTDEV + * DDI_CTLOPS_REGSIZE + * DDI_CTLOPS_NREGS + * + * All others passed to parent. + */ +static int +acebus_ctlops(dev_info_t *dip, dev_info_t *rdip, + ddi_ctl_enum_t op, void *arg, void *result) +{ +#ifdef DEBUG + ebus_devstate_t *ebus_p = get_acebus_soft_state(ddi_get_instance(dip)); +#endif + ebus_regspec_t *ebus_rp; + int32_t reglen; + int i, n; + char name[10]; + + switch (op) { + case DDI_CTLOPS_INITCHILD: { + dev_info_t *child = (dev_info_t *)arg; + /* + * Set the address portion of the node name based on the + * address/offset. + */ + DBG2(D_CTLOPS, ebus_p, "DDI_CTLOPS_INITCHILD: rdip=%s%d\n", + ddi_get_name(child), ddi_get_instance(child)); + + if (ddi_getlongprop(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS, + "reg", (caddr_t)&ebus_rp, ®len) != DDI_SUCCESS) { + + DBG(D_CTLOPS, ebus_p, "can't get reg property\n"); + return (DDI_FAILURE); + + } + + (void) sprintf(name, "%x,%x", ebus_rp->addr_hi, + ebus_rp->addr_low); + ddi_set_name_addr(child, name); + kmem_free((caddr_t)ebus_rp, reglen); + + ddi_set_parent_data(child, NULL); + + return (DDI_SUCCESS); + + } + + case DDI_CTLOPS_UNINITCHILD: + DBG2(D_CTLOPS, ebus_p, "DDI_CTLOPS_UNINITCHILD: rdip=%s%d\n", + ddi_get_name((dev_info_t *)arg), + ddi_get_instance((dev_info_t *)arg)); + ddi_set_name_addr((dev_info_t *)arg, NULL); + ddi_remove_minor_node((dev_info_t *)arg, NULL); + impl_rem_dev_props((dev_info_t *)arg); + return (DDI_SUCCESS); + + case DDI_CTLOPS_REPORTDEV: + + DBG2(D_CTLOPS, ebus_p, "DDI_CTLOPS_REPORTDEV: rdip=%s%d\n", + ddi_get_name(rdip), ddi_get_instance(rdip)); + cmn_err(CE_CONT, "?%s%d at %s%d: offset %s\n", + ddi_driver_name(rdip), ddi_get_instance(rdip), + ddi_driver_name(dip), ddi_get_instance(dip), + ddi_get_name_addr(rdip)); + return (DDI_SUCCESS); + + case DDI_CTLOPS_REGSIZE: + + DBG2(D_CTLOPS, ebus_p, "DDI_CTLOPS_REGSIZE: rdip=%s%d\n", + ddi_get_name(rdip), ddi_get_instance(rdip)); + if (getprop(rdip, "reg", &ebus_rp, &i) != DDI_SUCCESS) { + DBG(D_CTLOPS, ebus_p, "can't get reg property\n"); + return (DDI_FAILURE); + } + n = i / sizeof (ebus_regspec_t); + if (*(int *)arg < 0 || *(int *)arg >= n) { + DBG(D_MAP, ebus_p, "rnumber out of range\n"); + kmem_free((caddr_t)ebus_rp, i); + return (DDI_FAILURE); + } + *((off_t *)result) = ebus_rp[*(int *)arg].size; + kmem_free((caddr_t)ebus_rp, i); + return (DDI_SUCCESS); + + case DDI_CTLOPS_NREGS: + + DBG2(D_CTLOPS, ebus_p, "DDI_CTLOPS_NREGS: rdip=%s%d\n", + ddi_get_name(rdip), ddi_get_instance(rdip)); + if (getprop(rdip, "reg", &ebus_rp, &i) != DDI_SUCCESS) { + DBG(D_CTLOPS, ebus_p, "can't get reg property\n"); + return (DDI_FAILURE); + } + *((uint_t *)result) = i / sizeof (ebus_regspec_t); + kmem_free((caddr_t)ebus_rp, i); + return (DDI_SUCCESS); + } + + /* + * Now pass the request up to our parent. + */ + DBG2(D_CTLOPS, ebus_p, "passing request to parent: rdip=%s%d\n", + ddi_get_name(rdip), ddi_get_instance(rdip)); + return (ddi_ctlops(dip, rdip, op, arg, result)); +} + +struct ebus_string_to_pil { + int8_t *string; + uint32_t pil; +}; + +static struct ebus_string_to_pil acebus_name_to_pil[] = {{"SUNW,CS4231", 9}, + {"fdthree", 8}, + {"ecpp", 3}, + {"su", 12}, + {"se", 12}, + {"power", 14}}; + +static struct ebus_string_to_pil acebus_device_type_to_pil[] = {{"serial", 12}, + {"block", 8}}; + +static int +acebus_intr_ops(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op, + ddi_intr_handle_impl_t *hdlp, void *result) +{ +#ifdef DEBUG + ebus_devstate_t *ebus_p = get_acebus_soft_state(ddi_get_instance(dip)); +#endif + int8_t *name, *device_type; + int32_t i, max_children, max_device_types, len; + + /* + * NOTE: These ops below will never be supported in this nexus + * driver, hence they always return immediately. + */ + switch (intr_op) { + case DDI_INTROP_GETCAP: + *(int *)result = DDI_INTR_FLAG_LEVEL; + return (DDI_SUCCESS); + case DDI_INTROP_SETCAP: + case DDI_INTROP_SETMASK: + case DDI_INTROP_CLRMASK: + case DDI_INTROP_GETPENDING: + return (DDI_ENOTSUP); + default: + break; + } + + if ((intr_op == DDI_INTROP_SUPPORTED_TYPES) || hdlp->ih_pri) + goto done; + + /* + * This is a hack to set the PIL for the devices under ebus. + * We first look up a device by it's specific name, if we can't + * match the name, we try and match it's device_type property. + * Lastly we default a PIL level of 1. + */ + DBG1(D_INTR, ebus_p, "ebus_p %p\n", ebus_p); + + name = ddi_get_name(rdip); + max_children = sizeof (acebus_name_to_pil) / + sizeof (struct ebus_string_to_pil); + + for (i = 0; i < max_children; i++) { + if (strcmp(acebus_name_to_pil[i].string, name) == 0) { + DBG2(D_INTR, ebus_p, "child name %s; match PIL %d\n", + acebus_name_to_pil[i].string, + acebus_name_to_pil[i].pil); + + hdlp->ih_pri = acebus_name_to_pil[i].pil; + goto done; + } + } + + if (ddi_getlongprop(DDI_DEV_T_ANY, rdip, DDI_PROP_DONTPASS, + "device_type", (caddr_t)&device_type, &len) == DDI_SUCCESS) { + + max_device_types = sizeof (acebus_device_type_to_pil) / + sizeof (struct ebus_string_to_pil); + + for (i = 0; i < max_device_types; i++) { + if (strcmp(acebus_device_type_to_pil[i].string, + device_type) == 0) { + DBG2(D_INTR, ebus_p, + "Device type %s; match PIL %d\n", + acebus_device_type_to_pil[i].string, + acebus_device_type_to_pil[i].pil); + + hdlp->ih_pri = acebus_device_type_to_pil[i].pil; + break; + } + } + + kmem_free(device_type, len); + } + + /* + * If we get here, we need to set a default value + * for the PIL. + */ + if (hdlp->ih_pri == 0) { + hdlp->ih_pri = 1; + cmn_err(CE_WARN, "%s%d assigning default interrupt level %d " + "for device %s%d", ddi_driver_name(dip), + ddi_get_instance(dip), hdlp->ih_pri, ddi_driver_name(rdip), + ddi_get_instance(rdip)); + } + +done: + /* Pass up the request to our parent. */ + return (i_ddi_intr_ops(dip, rdip, intr_op, hdlp, result)); +} + + +static int +acebus_config(ebus_devstate_t *ebus_p) +{ + ddi_acc_handle_t conf_handle; + uint16_t comm; +#ifdef ACEBUS_HOTPLUG + int tcr_reg; + caddr_t csr_io; + ddi_device_acc_attr_t csr_attr = { /* CSR map attributes */ + DDI_DEVICE_ATTR_V0, + DDI_STRUCTURE_LE_ACC, + DDI_STRICTORDER_ACC + }; + ddi_acc_handle_t csr_handle; +#endif + + /* + * Make sure the master enable and memory access enable + * bits are set in the config command register. + */ + if (pci_config_setup(ebus_p->dip, &conf_handle) != DDI_SUCCESS) + return (0); + + comm = pci_config_get16(conf_handle, PCI_CONF_COMM), +#ifdef DEBUG + DBG1(D_ATTACH, ebus_p, "command register was 0x%x\n", comm); +#endif + comm |= (PCI_COMM_ME|PCI_COMM_MAE|PCI_COMM_SERR_ENABLE| + PCI_COMM_PARITY_DETECT); + pci_config_put16(conf_handle, PCI_CONF_COMM, comm), +#ifdef DEBUG + DBG1(D_MAP, ebus_p, "command register is now 0x%x\n", + pci_config_get16(conf_handle, PCI_CONF_COMM)); +#endif + pci_config_put8(conf_handle, PCI_CONF_CACHE_LINESZ, + (uchar_t)acebus_cache_line_size); + pci_config_put8(conf_handle, PCI_CONF_LATENCY_TIMER, + (uchar_t)acebus_latency_timer); + pci_config_teardown(&conf_handle); + +#ifdef ACEBUS_HOTPLUG + if (acebus_update_props(ebus_p) != DDI_SUCCESS) { + cmn_err(CE_WARN, "%s%d: Could not update special properties.", + ddi_driver_name(ebus_p->dip), + ddi_get_instance(ebus_p->dip)); + return (0); + } + + if (ddi_regs_map_setup(ebus_p->dip, CSR_IO_RINDEX, + (caddr_t *)&csr_io, 0, CSR_SIZE, &csr_attr, + &csr_handle) != DDI_SUCCESS) { + cmn_err(CE_WARN, "%s%d: Could not map Ebus CSR.", + ddi_driver_name(ebus_p->dip), + ddi_get_instance(ebus_p->dip)); + } +#ifdef DEBUG + if (acebus_debug_flags) { + DBG3(D_ATTACH, ebus_p, "tcr[123] = %x,%x,%x\n", + ddi_get32(csr_handle, (uint32_t *)((caddr_t)csr_io + + TCR1_OFF)), + ddi_get32(csr_handle, (uint32_t *)((caddr_t)csr_io + + TCR2_OFF)), + ddi_get32(csr_handle, (uint32_t *)((caddr_t)csr_io + + TCR3_OFF))); + DBG2(D_ATTACH, ebus_p, "pmd-aux=%x, freq-aux=%x\n", + ddi_get32(csr_handle, (uint32_t *)((caddr_t)csr_io + + PMD_AUX_OFF)), + ddi_get32(csr_handle, (uint32_t *)((caddr_t)csr_io + + FREQ_AUX_OFF))); +#ifdef ACEBUS_DEBUG + for (comm = 0; comm < 4; comm++) + prom_printf("dcsr%d=%x, dacr%d=%x, dbcr%d=%x\n", comm, + ddi_get32(csr_handle, (uint32_t *)((caddr_t)csr_io + + 0x700000+(0x2000*comm))), comm, + ddi_get32(csr_handle, (uint32_t *)((caddr_t)csr_io + + 0x700000+(0x2000*comm)+4)), comm, + ddi_get32(csr_handle, (uint32_t *)((caddr_t)csr_io + + 0x700000+(0x2000*comm)+8))); +#endif + } /* acebus_debug_flags */ +#endif + /* If TCR registers are not initialized, initialize them here */ + tcr_reg = ddi_get32(csr_handle, (uint32_t *)((caddr_t)csr_io + + TCR1_OFF)); + if ((tcr_reg == 0) || (tcr_reg == -1)) + ddi_put32(csr_handle, (uint32_t *)((caddr_t)csr_io + TCR1_OFF), + TCR1_REGVAL); + tcr_reg = ddi_get32(csr_handle, (uint32_t *)((caddr_t)csr_io + + TCR2_OFF)); + if ((tcr_reg == 0) || (tcr_reg == -1)) + ddi_put32(csr_handle, (uint32_t *)((caddr_t)csr_io + TCR2_OFF), + TCR2_REGVAL); + tcr_reg = ddi_get32(csr_handle, (uint32_t *)((caddr_t)csr_io + + TCR3_OFF)); + if ((tcr_reg == 0) || (tcr_reg == -1)) + ddi_put32(csr_handle, (uint32_t *)((caddr_t)csr_io + TCR3_OFF), + TCR3_REGVAL); +#ifdef DEBUG + if (acebus_debug_flags) { + DBG3(D_ATTACH, ebus_p, "wrote tcr[123] = %x,%x,%x\n", + ddi_get32(csr_handle, (uint32_t *)((caddr_t)csr_io + + TCR1_OFF)), + ddi_get32(csr_handle, (uint32_t *)((caddr_t)csr_io + + TCR2_OFF)), + ddi_get32(csr_handle, (uint32_t *)((caddr_t)csr_io + + TCR3_OFF))); + } +#endif + + ddi_regs_map_free(&csr_handle); +#endif /* ACEBUS_HOTPLUG */ + return (1); /* return success */ +} + +#ifdef DEBUG +extern void prom_printf(const char *, ...); + +static void +acebus_debug(uint_t flag, ebus_devstate_t *ebus_p, char *fmt, + uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5) +{ + char *s; + + if (acebus_debug_flags & flag) { + switch (flag) { + case D_ATTACH: + s = "attach"; break; + case D_DETACH: + s = "detach"; break; + case D_MAP: + s = "map"; break; + case D_CTLOPS: + s = "ctlops"; break; + case D_INTR: + s = "intr"; break; + } + if (ebus_p) + cmn_err(CE_CONT, "%s%d: %s: ", + ddi_get_name(ebus_p->dip), + ddi_get_instance(ebus_p->dip), s); + else + cmn_err(CE_CONT, "ebus: "); + cmn_err(CE_CONT, fmt, a1, a2, a3, a4, a5); + } +} +#endif + +#ifdef ACEBUS_HOTPLUG +#define EBUS_CHILD_PHYS_LOW_RANGE 0x10 +#define EBUS_CHILD_PHYS_HI_RANGE 0x14 + +static int +acebus_update_props(ebus_devstate_t *ebus_p) +{ + dev_info_t *dip = ebus_p->dip; + struct ebus_pci_rangespec er[2], *erp; + pci_regspec_t *pci_rp, *prp; + int length, rnums, imask[3], i, found = 0; + + /* + * If "ranges" property is found, then the device is initialized + * by OBP, hence simply return. + * Otherwise we create all the properties here. + */ + if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, + "ranges", (int **)&erp, (uint_t *)&length) == DDI_PROP_SUCCESS) { + ddi_prop_free(erp); + return (DDI_SUCCESS); + } + + /* + * interrupt-map is the only property that comes from a .conf file. + * Since it doesn't have the nodeid field set, it must be done here. + * Other properties can come from OBP or created here. + */ + if (acebus_set_imap(dip) != DDI_SUCCESS) { + return (DDI_FAILURE); + } + + /* + * Create the "ranges" property. + * Ebus has BAR0 and BAR1 allocated (both in memory space). + * Other BARs are 0. + * Hence there are 2 memory ranges it operates in. (one for each BAR). + * ie. there are 2 entries in its ranges property. + */ + if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, + DDI_PROP_DONTPASS, "assigned-addresses", + (int **)&pci_rp, (uint_t *)&length) + != DDI_PROP_SUCCESS) { + cmn_err(CE_WARN, "%s%d: Could not get assigned-addresses", + ddi_driver_name(dip), ddi_get_instance(dip)); + return (DDI_FAILURE); + } + /* + * Create the 1st mem range in which it operates corresponding + * to BAR0 + */ + er[0].ebus_phys_hi = EBUS_CHILD_PHYS_LOW_RANGE; + rnums = (length * sizeof (int))/sizeof (pci_regspec_t); + for (i = 0; i < rnums; i++) { + prp = pci_rp + i; + if (PCI_REG_REG_G(prp->pci_phys_hi) == er[0].ebus_phys_hi) { + found = 1; + break; + } + } + if (!found) { + cmn_err(CE_WARN, "No assigned space for memory range 0."); + ddi_prop_free(pci_rp); + return (DDI_FAILURE); + } + found = 0; + er[0].ebus_phys_low = 0; + er[0].pci_phys_hi = prp->pci_phys_hi; + er[0].pci_phys_mid = prp->pci_phys_mid; + er[0].pci_phys_low = prp->pci_phys_low; + er[0].rng_size = prp->pci_size_low; + + /* + * Create the 2nd mem range in which it operates corresponding + * to BAR1 + */ + er[1].ebus_phys_hi = EBUS_CHILD_PHYS_HI_RANGE; + for (i = 0; i < rnums; i++) { + prp = pci_rp + i; + if (PCI_REG_REG_G(prp->pci_phys_hi) == er[1].ebus_phys_hi) { + found = 1; + break; + } + } + if (!found) { + cmn_err(CE_WARN, "No assigned space for memory range 1."); + ddi_prop_free(pci_rp); + return (DDI_FAILURE); + } + er[1].ebus_phys_low = 0; + er[1].pci_phys_hi = prp->pci_phys_hi; + er[1].pci_phys_mid = prp->pci_phys_mid; + er[1].pci_phys_low = prp->pci_phys_low; + er[1].rng_size = prp->pci_size_low; + + ddi_prop_free(pci_rp); + length = sizeof (er) / sizeof (int); + if (ddi_prop_update_int_array(DDI_DEV_T_NONE, dip, + "ranges", (int *)er, length) != DDI_PROP_SUCCESS) { + cmn_err(CE_WARN, "%s%d: Could not create ranges property", + ddi_driver_name(dip), ddi_get_instance(dip)); + return (DDI_FAILURE); + } + /* The following properties are as defined by PCI 1275 bindings. */ + if (ddi_prop_update_int(DDI_DEV_T_NONE, dip, + "#address-cells", 2) != DDI_PROP_SUCCESS) + return (DDI_FAILURE); + if (ddi_prop_update_int(DDI_DEV_T_NONE, dip, + "#size-cells", 1) != DDI_PROP_SUCCESS) + return (DDI_FAILURE); + if (ddi_prop_update_int(DDI_DEV_T_NONE, dip, + "#interrupt-cells", 1) != DDI_PROP_SUCCESS) + return (DDI_FAILURE); + + imask[0] = 0x1f; + imask[1] = 0x00ffffff; + imask[2] = 0x00000003; + length = sizeof (imask) / sizeof (int); + if (ddi_prop_update_int_array(DDI_DEV_T_NONE, dip, + "interrupt-map-mask", (int *)imask, length) != DDI_PROP_SUCCESS) { + cmn_err(CE_WARN, "%s%d: Could not update imap mask property", + ddi_driver_name(dip), ddi_get_instance(dip)); + return (DDI_FAILURE); + } + + return (DDI_SUCCESS); +} + +/* + * This function takes in the ac-interrupt-map property from the .conf file, + * fills in the 'nodeid' information and then creates the 'interrupt-map' + * property. + */ +static int +acebus_set_imap(dev_info_t *dip) +{ + int *imapp, *timapp, length, num, i, default_ival = 0; + dev_info_t *tdip = dip; + int *port_id, imap_ok = 1; + int ilength; + int acebus_default_se_imap[5]; + + /* + * interrupt-map is specified via .conf file in hotplug mode, + * since the child configuration is static. + * It could even be hardcoded in the driver. + */ + if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, + "ac-interrupt-map", (int **)&imapp, (uint_t *)&ilength) != + DDI_PROP_SUCCESS) { + /* assume default implementation */ + acebus_default_se_imap[0] = 0x14; + acebus_default_se_imap[1] = 0x400000; + acebus_default_se_imap[2] = 1; + acebus_default_se_imap[3] = 0; + acebus_default_se_imap[4] = 2; + imapp = acebus_default_se_imap; + ilength = 5; + default_ival = 1; + } + num = ilength / 5; /* there are 5 integer cells in our property */ + timapp = imapp; + for (i = 0; i < num; i++) { + if (*(timapp+i*5+3) == 0) + imap_ok = 0; + } + if (imap_ok) { + if (!default_ival) + ddi_prop_free(imapp); + return (DDI_SUCCESS); + } + + while (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, tdip, + DDI_PROP_DONTPASS, "upa-portid", (int **)&port_id, + (uint_t *)&length) != DDI_PROP_SUCCESS) { + tdip = ddi_get_parent(tdip); + if (tdip == NULL) { + cmn_err(CE_WARN, "%s%d: Could not get imap parent", + ddi_driver_name(dip), ddi_get_instance(dip)); + if (!default_ival) + ddi_prop_free(imapp); + return (DDI_FAILURE); + } + } + timapp = imapp; + for (i = 0; i < num; i++) { + *(timapp+i*5+3) = ddi_get_nodeid(tdip); + } + + if (ddi_prop_update_int_array(DDI_DEV_T_NONE, dip, + "interrupt-map", imapp, ilength) != DDI_PROP_SUCCESS) { + cmn_err(CE_WARN, "%s%d: Could not update AC imap property", + ddi_driver_name(dip), ddi_get_instance(dip)); + if (!default_ival) + ddi_prop_free(imapp); + return (DDI_FAILURE); + } + if (!default_ival) + ddi_prop_free(imapp); + return (DDI_SUCCESS); +} +#endif /* ACEBUS_HOTPLUG */ |
