diff options
Diffstat (limited to 'usr/src/uts/sun4u/io/pmubus.c')
| -rw-r--r-- | usr/src/uts/sun4u/io/pmubus.c | 894 |
1 files changed, 894 insertions, 0 deletions
diff --git a/usr/src/uts/sun4u/io/pmubus.c b/usr/src/uts/sun4u/io/pmubus.c new file mode 100644 index 0000000000..a764b5a8b0 --- /dev/null +++ b/usr/src/uts/sun4u/io/pmubus.c @@ -0,0 +1,894 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (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/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/pmubus.h> + +#include <sys/nexusdebug.h> +/* Bitfield debugging definitions for this file */ +#define PMUBUS_MAP_DEBUG 0x1 +#define PMUBUS_REGACCESS_DEBUG 0x2 +#define PMUBUS_RW_DEBUG 0x4 + +/* + * The pmubus nexus is used to manage a shared register space. Rather + * than having several driver's physically alias register mappings and + * have potential problems with register collisions, this nexus will + * serialize the access to this space. + * + * There are two types of sharing going on here: + * 1) Registers within the address space may be shared, however the registers + * themselves are unique. The upper bit of the child's high address being zero + * signifies this register type. + * + * 2) The second type of register is one where a device may only own a few + * bits in the register. I'll term this as "bit lane" access. This is a more + * complicated scenario. The drivers themselves are responsible for knowing + * which bit lanes in the register they own. The read of a register only + * guarantees that those bits the driver is interested in are valid. If a + * driver needs to set bits in a register, a read must be done first to + * identify the state of the drivers bits. Depending on which way a bit needs + * to be driven, the driver will write a 1 to the bit to toggle it. If a bit + * is to remain unchanged, a 0 is written to the bit. So the access to the + * bit lane is an xor operation. + */ +/* + * Function prototypes for busops routines: + */ +static int pmubus_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp, + off_t off, off_t len, caddr_t *addrp); +static int pmubus_ctlops(dev_info_t *dip, dev_info_t *rdip, + ddi_ctl_enum_t op, void *arg, void *result); + +/* + * function prototypes for dev ops routines: + */ +static int pmubus_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); +static int pmubus_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); + +/* + * general function prototypes: + */ + +/* + * bus ops and dev ops structures: + */ +static struct bus_ops pmubus_bus_ops = { + BUSO_REV, + pmubus_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, + pmubus_ctlops, + ddi_bus_prop_op, + 0, /* (*bus_get_eventcookie)(); */ + 0, /* (*bus_add_eventcall)(); */ + 0, /* (*bus_remove_eventcall)(); */ + 0, /* (*bus_post_event)(); */ + 0, /* interrupt control */ + 0, /* bus_config */ + 0, /* bus_unconfig */ + 0, /* bus_fm_init */ + 0, /* bus_fm_fini */ + 0, /* bus_fm_access_enter */ + 0, /* bus_fm_access_exit */ + 0, /* bus_power */ + i_ddi_intr_ops /* bus_intr_op */ +}; + +static struct dev_ops pmubus_ops = { + DEVO_REV, + 0, + ddi_no_info, + nulldev, + 0, + pmubus_attach, + pmubus_detach, + nodev, + (struct cb_ops *)0, + &pmubus_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 */ + "pmubus nexus driver", /* Name of module. */ + &pmubus_ops, /* driver ops */ +}; + +static struct modlinkage modlinkage = { + MODREV_1, (void *)&modldrv, NULL +}; + +/* + * driver global data: + */ +static void *per_pmubus_state; /* per-pmubus soft state pointer */ + +int +_init(void) +{ + int e; + + /* + * Initialize per-pmubus soft state pointer. + */ + e = ddi_soft_state_init(&per_pmubus_state, + sizeof (pmubus_devstate_t), 1); + if (e != 0) + return (e); + + /* + * Install the module. + */ + e = mod_install(&modlinkage); + if (e != 0) + ddi_soft_state_fini(&per_pmubus_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_pmubus_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 +pmubus_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) +{ + pmubus_devstate_t *pmubusp; /* per pmubus state pointer */ + int32_t instance; + + switch (cmd) { + case DDI_ATTACH: + /* + * Allocate soft state for this instance. + */ + instance = ddi_get_instance(dip); + if (ddi_soft_state_zalloc(per_pmubus_state, instance) != + DDI_SUCCESS) { + cmn_err(CE_WARN, "pmubus_attach: Can't allocate soft " + "state.\n"); + goto fail_exit; + } + + pmubusp = ddi_get_soft_state(per_pmubus_state, instance); + pmubusp->pmubus_dip = dip; + + /* Cache our register property */ + if (ddi_getlongprop(DDI_DEV_T_NONE, dip, DDI_PROP_DONTPASS, + "reg", (caddr_t)&pmubusp->pmubus_regp, + &pmubusp->pmubus_reglen) != DDI_SUCCESS) { + cmn_err(CE_WARN, "pmubus_attach: Can't acquire reg " + "property.\n"); + goto fail_get_regs; + } + + /* Cache our ranges property */ + if (ddi_getlongprop(DDI_DEV_T_NONE, dip, DDI_PROP_DONTPASS, + "ranges", (caddr_t)&pmubusp->pmubus_rangep, + &pmubusp->pmubus_rnglen) != DDI_SUCCESS) { + cmn_err(CE_WARN, "pmubus_attach: Can't acquire the " + "ranges property.\n"); + goto fail_get_ranges; + + } + + /* Calculate the number of ranges */ + pmubusp->pmubus_nranges = + pmubusp->pmubus_rnglen / sizeof (pmu_rangespec_t); + + /* Set up the mapping to our registers */ + if (pci_config_setup(dip, &pmubusp->pmubus_reghdl) != + DDI_SUCCESS) { + cmn_err(CE_WARN, "pmubus_attach: Can't map in " + "register space.\n"); + goto fail_map_regs; + } + + /* Initialize our register access mutex */ + mutex_init(&pmubusp->pmubus_reg_access_lock, NULL, + MUTEX_DRIVER, NULL); + + ddi_report_dev(dip); + return (DDI_SUCCESS); + + case DDI_RESUME: + return (DDI_SUCCESS); + } + +fail_map_regs: + kmem_free(pmubusp->pmubus_rangep, pmubusp->pmubus_rnglen); + +fail_get_ranges: + kmem_free(pmubusp->pmubus_regp, pmubusp->pmubus_reglen); + +fail_get_regs: + ddi_soft_state_free(per_pmubus_state, instance); + +fail_exit: + return (DDI_FAILURE); +} + +/* + * detach entry point: + */ +static int +pmubus_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) +{ + int instance = ddi_get_instance(dip); + pmubus_devstate_t *pmubusp = ddi_get_soft_state(per_pmubus_state, + instance); + + switch (cmd) { + case DDI_DETACH: + mutex_destroy(&pmubusp->pmubus_reg_access_lock); + + /* Tear down our register mappings */ + pci_config_teardown(&pmubusp->pmubus_reghdl); + + /* Free our ranges property */ + kmem_free(pmubusp->pmubus_rangep, pmubusp->pmubus_rnglen); + + /* Free the register property */ + kmem_free(pmubusp->pmubus_regp, pmubusp->pmubus_reglen); + + ddi_soft_state_free(per_pmubus_state, instance); + break; + + case DDI_SUSPEND: + default: + break; + } + + return (DDI_SUCCESS); +} + +/*ARGSUSED*/ +void +pmubus_norep_get8(ddi_acc_impl_t *handle, uint8_t *host_addr, + uint8_t *dev_addr, size_t repcount, uint_t flags) +{ +} + +/*ARGSUSED*/ +void +pmubus_norep_get16(ddi_acc_impl_t *handle, uint16_t *host_addr, + uint16_t *dev_addr, size_t repcount, uint_t flags) +{ +} + +/*ARGSUSED*/ +void +pmubus_norep_get32(ddi_acc_impl_t *handle, uint32_t *host_addr, + uint32_t *dev_addr, size_t repcount, uint_t flags) +{ +} + +/*ARGSUSED*/ +void +pmubus_norep_get64(ddi_acc_impl_t *handle, uint64_t *host_addr, + uint64_t *dev_addr, size_t repcount, uint_t flags) +{ +} + +/*ARGSUSED*/ +void +pmubus_norep_put8(ddi_acc_impl_t *handle, uint8_t *host_addr, + uint8_t *dev_addr, size_t repcount, uint_t flags) +{ +} + +/*ARGSUSED*/ +void +pmubus_norep_put16(ddi_acc_impl_t *handle, uint16_t *host_addr, + uint16_t *dev_addr, size_t repcount, uint_t flags) +{ +} + +/*ARGSUSED*/ +void +pmubus_norep_put32(ddi_acc_impl_t *handle, uint32_t *host_addr, + uint32_t *dev_addr, size_t repcount, uint_t flags) +{ +} + +/*ARGSUSED*/ +void +pmubus_norep_put64(ddi_acc_impl_t *handle, uint64_t *host_addr, + uint64_t *dev_addr, size_t repcount, uint_t flags) +{ +} + +/*ARGSUSED*/ +uint8_t +pmubus_get8(ddi_acc_impl_t *hdlp, uint8_t *addr) +{ + ddi_acc_hdl_t *hp = (ddi_acc_hdl_t *)hdlp; + pmubus_mapreq_t *pmubus_mapreqp = hp->ah_bus_private; + pmubus_devstate_t *softsp = pmubus_mapreqp->mapreq_softsp; + off_t offset; + uint8_t value; + uint8_t mask; + + offset = pmubus_mapreqp->mapreq_addr + (uintptr_t)addr; + offset &= PMUBUS_REGOFFSET; + + if ((pmubus_mapreqp->mapreq_flags) & MAPREQ_SHARED_BITS) { + if (addr != 0 || + pmubus_mapreqp->mapreq_size != sizeof (value)) { + cmn_err(CE_WARN, "pmubus_get8: load discarded, " + "incorrect access addr/size"); + return ((uint8_t)-1); + } + mask = pmubus_mapreqp->mapreq_mask; + } else { + mask = (uint8_t)-1; + } + + /* gets are simple, we just issue them no locking necessary */ + value = pci_config_get8(softsp->pmubus_reghdl, offset) & mask; + + DPRINTF(PMUBUS_RW_DEBUG, ("pmubus_get8: addr=%p offset=%x value=%x " + "mask=%lx\n", addr, offset, value, mask)); + + return (value); +} + + +/*ARGSUSED*/ +uint16_t +pmubus_noget16(ddi_acc_impl_t *hdlp, uint16_t *addr) +{ + return ((uint16_t)-1); +} + +/*ARGSUSED*/ +uint32_t +pmubus_get32(ddi_acc_impl_t *hdlp, uint32_t *addr) +{ + ddi_acc_hdl_t *hp = (ddi_acc_hdl_t *)hdlp; + pmubus_mapreq_t *pmubus_mapreqp = hp->ah_bus_private; + pmubus_devstate_t *softsp = pmubus_mapreqp->mapreq_softsp; + off_t offset = (uintptr_t)addr & PMUBUS_REGOFFSET; + uint32_t value; + uint32_t mask; + + offset = pmubus_mapreqp->mapreq_addr + (uintptr_t)addr; + offset &= PMUBUS_REGOFFSET; + + if ((pmubus_mapreqp->mapreq_flags) & MAPREQ_SHARED_BITS) { + if (addr != 0 || + pmubus_mapreqp->mapreq_size != sizeof (value)) { + cmn_err(CE_WARN, "pmubus_get32: load discarded, " + "incorrect access addr/size"); + return ((uint32_t)-1); + } + mask = pmubus_mapreqp->mapreq_mask; + } else { + mask = (uint32_t)-1; + } + + /* gets are simple, we just issue them no locking necessary */ + value = pci_config_get32(softsp->pmubus_reghdl, offset) & mask; + + DPRINTF(PMUBUS_RW_DEBUG, ("pmubus_get32: addr=%p offset=%x value=%x " + "mask=%lx\n", addr, offset, value, mask)); + + return (value); +} + +/*ARGSUSED*/ +uint64_t +pmubus_noget64(ddi_acc_impl_t *hdlp, uint64_t *addr) +{ + return ((uint64_t)-1); +} + +/*ARGSUSED*/ +void +pmubus_put8(ddi_acc_impl_t *hdlp, uint8_t *addr, uint8_t value) +{ + ddi_acc_hdl_t *hp = (ddi_acc_hdl_t *)hdlp; + pmubus_mapreq_t *pmubus_mapreqp = hp->ah_bus_private; + pmubus_devstate_t *softsp = pmubus_mapreqp->mapreq_softsp; + off_t offset; + uint8_t tmp; + + offset = pmubus_mapreqp->mapreq_addr + (uintptr_t)addr; + offset &= PMUBUS_REGOFFSET; + + if ((pmubus_mapreqp->mapreq_flags) & MAPREQ_SHARED_BITS) { + /* + * Process "bit lane" register + */ + DPRINTF(PMUBUS_RW_DEBUG, ("pmubus_put8: addr=%p offset=%x " + "value=%x mask=%lx\n", addr, offset, value, + pmubus_mapreqp->mapreq_mask)); + + if (addr != 0 || + pmubus_mapreqp->mapreq_size != sizeof (value)) { + cmn_err(CE_WARN, "pmubus_put8: store discarded, " + "incorrect access addr/size"); + return; + } + + mutex_enter(&softsp->pmubus_reg_access_lock); + tmp = pci_config_get8(softsp->pmubus_reghdl, offset); + tmp &= ~pmubus_mapreqp->mapreq_mask; + value &= pmubus_mapreqp->mapreq_mask; + tmp |= value; + pci_config_put8(softsp->pmubus_reghdl, offset, tmp); + mutex_exit(&softsp->pmubus_reg_access_lock); + } else { + /* + * Process shared register + */ + DPRINTF(PMUBUS_RW_DEBUG, ("pmubus_put8: addr=%p offset=%x " + "value=%x\n", addr, offset, value)); + pci_config_put8(softsp->pmubus_reghdl, offset, value); + } + + /* Flush store buffers XXX Should let drivers do this. */ + tmp = pci_config_get8(softsp->pmubus_reghdl, offset); +} + +/*ARGSUSED*/ +void +pmubus_noput16(ddi_acc_impl_t *hdlp, uint16_t *addr, uint16_t value) +{ +} + +/*ARGSUSED*/ +void +pmubus_put32(ddi_acc_impl_t *hdlp, uint32_t *addr, uint32_t value) +{ + ddi_acc_hdl_t *hp = (ddi_acc_hdl_t *)hdlp; + pmubus_mapreq_t *pmubus_mapreqp = hp->ah_bus_private; + pmubus_devstate_t *softsp = pmubus_mapreqp->mapreq_softsp; + off_t offset; + uint32_t tmp; + + offset = pmubus_mapreqp->mapreq_addr + (uintptr_t)addr; + offset &= PMUBUS_REGOFFSET; + + if ((pmubus_mapreqp->mapreq_flags) & MAPREQ_SHARED_BITS) { + /* + * Process "bit lane" register + */ + DPRINTF(PMUBUS_RW_DEBUG, ("pmubus_put32: addr=%p offset=%x " + "value=%x mask=%lx\n", addr, offset, value, + pmubus_mapreqp->mapreq_mask)); + + if (addr != 0 || + pmubus_mapreqp->mapreq_size != sizeof (value)) { + cmn_err(CE_WARN, "pmubus_put32: store discarded, " + "incorrect access addr/size"); + return; + } + + mutex_enter(&softsp->pmubus_reg_access_lock); + tmp = pci_config_get32(softsp->pmubus_reghdl, offset); + tmp &= ~pmubus_mapreqp->mapreq_mask; + value &= pmubus_mapreqp->mapreq_mask; + tmp |= value; + pci_config_put32(softsp->pmubus_reghdl, offset, tmp); + mutex_exit(&softsp->pmubus_reg_access_lock); + } else { + /* + * Process shared register + */ + DPRINTF(PMUBUS_RW_DEBUG, ("pmubus_put32: addr=%p offset=%x " + "value=%x\n", addr, offset, value)); + pci_config_put32(softsp->pmubus_reghdl, offset, value); + } + + /* Flush store buffers XXX Should let drivers do this. */ + tmp = pci_config_get32(softsp->pmubus_reghdl, offset); +} + +/*ARGSUSED*/ +void +pmubus_noput64(ddi_acc_impl_t *hdlp, uint64_t *addr, uint64_t value) +{ +} + +/* + * This routine is used to translate our children's register properties. + * The return value specifies which type of register has been translated. + */ +/*ARGSUSED*/ +int +pmubus_apply_range(pmubus_devstate_t *pmubusp, dev_info_t *rdip, + pmubus_regspec_t *regp, pci_regspec_t *pci_regp) +{ + pmu_rangespec_t *rangep; + int nranges = pmubusp->pmubus_nranges; + int i; + off_t offset; + int ret = DDI_ME_REGSPEC_RANGE; + uint64_t addr; + + addr = regp->reg_addr & ~MAPPING_SHARED_BITS_MASK; + + /* Scan the ranges for a match */ + for (i = 0, rangep = pmubusp->pmubus_rangep; i < nranges; i++, rangep++) + if ((rangep->rng_child <= addr) && + ((addr + regp->reg_size) <= + (rangep->rng_child + rangep->rng_size))) { + ret = DDI_SUCCESS; + break; + } + + if (ret != DDI_SUCCESS) + return (ret); + + /* Get the translated register */ + offset = addr - rangep->rng_child; + pci_regp->pci_phys_hi = rangep->rng_parent_hi; + pci_regp->pci_phys_mid = rangep->rng_parent_mid; + pci_regp->pci_phys_low = rangep->rng_parent_low + offset; + pci_regp->pci_size_hi = 0; + pci_regp->pci_size_low = MIN(regp->reg_size, rangep->rng_size); + + /* Figure out the type of reg space we have */ + if (pci_regp->pci_phys_hi == pmubusp->pmubus_regp->pci_phys_hi) { + ret = MAPREQ_SHARED_REG; + if (regp->reg_addr & MAPPING_SHARED_BITS_MASK) + ret |= MAPREQ_SHARED_BITS; + } + + return (ret); +} + +static uint64_t +pmubus_mask(pmubus_obpregspec_t *regs, int32_t rnumber, + uint64_t *masks) +{ + int i; + long n = -1; + + for (i = 0; i <= rnumber; i++) + if (regs[i].reg_addr_hi & 0x80000000) + n++; + + if (n == -1) { + cmn_err(CE_WARN, "pmubus_mask: missing mask"); + return (0); + } + + return (masks[n]); +} + +/* + * The pmubus_map routine determines if it's child is attempting to map a + * shared reg. If it is, it installs it's own vectors and bus private pointer. + */ +static int +pmubus_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp, + off_t off, off_t len, caddr_t *addrp) +{ + pmubus_devstate_t *pmubusp = ddi_get_soft_state(per_pmubus_state, + ddi_get_instance(dip)); + dev_info_t *pdip = (dev_info_t *)DEVI(dip)->devi_parent; + pmubus_regspec_t pmubus_rp; + pmubus_obpregspec_t *pmubus_regs = NULL; + int pmubus_regs_size; + uint64_t *pmubus_regmask = NULL; + int pmubus_regmask_size; + pci_regspec_t pci_reg; + int32_t rnumber = mp->map_obj.rnumber; + pmubus_mapreq_t *pmubus_mapreqp; + int ret = DDI_SUCCESS; + char *map_fail1 = "Map Type Unknown"; + char *map_fail2 = "DDI_MT_REGSPEC"; + char *s = map_fail1; + + *addrp = NULL; + + /* + * Handle the mapping according to its type. + */ + DPRINTF(PMUBUS_MAP_DEBUG, ("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_RNUMBER: { + int n; + + /* + * Get the "reg" property from the device node and convert + * it to our parent's format. + */ + rnumber = mp->map_obj.rnumber; + DPRINTF(PMUBUS_MAP_DEBUG, ("rdip=%s%d: rnumber=%x " + "handlep=%x\n", ddi_get_name(rdip), ddi_get_instance(rdip), + rnumber, mp->map_handlep)); + + if (ddi_getlongprop(DDI_DEV_T_NONE, rdip, DDI_PROP_DONTPASS, + "reg", (caddr_t)&pmubus_regs, &pmubus_regs_size) != + DDI_SUCCESS) { + DPRINTF(PMUBUS_MAP_DEBUG, ("can't get reg " + "property\n")); + ret = DDI_ME_RNUMBER_RANGE; + goto done; + } + n = pmubus_regs_size / sizeof (pmubus_obpregspec_t); + + if (rnumber < 0 || rnumber >= n) { + DPRINTF(PMUBUS_MAP_DEBUG, ("rnumber out of range\n")); + ret = DDI_ME_RNUMBER_RANGE; + goto done; + } + + pmubus_rp.reg_addr = ((uint64_t) + pmubus_regs[rnumber].reg_addr_hi << 32) | + (uint64_t)pmubus_regs[rnumber].reg_addr_lo; + pmubus_rp.reg_size = pmubus_regs[rnumber].reg_size; + + (void) ddi_getlongprop(DDI_DEV_T_NONE, rdip, DDI_PROP_DONTPASS, + "register-mask", (caddr_t)&pmubus_regmask, + &pmubus_regmask_size); + + /* Create our own mapping private structure */ + break; + + } + case DDI_MT_REGSPEC: + /* + * This bus has no bus children that have to map in an address + * space, so we can assume that we'll never see an + * DDI_MT_REGSPEC request + */ + s = map_fail2; + ret = DDI_ME_REGSPEC_RANGE; + /*FALLTHROUGH*/ + + default: + if (ret == DDI_SUCCESS) + ret = DDI_ME_INVAL; + DPRINTF(PMUBUS_MAP_DEBUG, ("rdip=%s%d: pmubus_map: " + "%s is an invalid map type.\nmap request handlep=0x%p\n", + ddi_get_name(rdip), ddi_get_instance(rdip), s, mp)); + + ret = DDI_ME_RNUMBER_RANGE; + goto done; + } + + /* Adjust our reg property with offset and length */ + if ((pmubus_rp.reg_addr + off) > + (pmubus_rp.reg_addr + pmubus_rp.reg_size)) { + ret = DDI_ME_INVAL; + goto done; + } + + pmubus_rp.reg_addr += off; + if (len && (len < pmubus_rp.reg_size)) + pmubus_rp.reg_size = len; + + /* Translate our child regspec into our parents address domain */ + ret = pmubus_apply_range(pmubusp, rdip, &pmubus_rp, &pci_reg); + + /* Check if the apply range failed */ + if (ret < DDI_SUCCESS) + goto done; + + /* + * If our childs xlated address falls into our shared address range, + * setup our mapping handle. + */ + if (ret > DDI_SUCCESS) { + /* Figure out if we're mapping or unmapping */ + switch (mp->map_op) { + case DDI_MO_MAP_LOCKED: { + ddi_acc_impl_t *hp = (ddi_acc_impl_t *)mp->map_handlep; + + pmubus_mapreqp = kmem_alloc(sizeof (*pmubus_mapreqp), + KM_SLEEP); + + pmubus_mapreqp->mapreq_flags = ret; + pmubus_mapreqp->mapreq_softsp = pmubusp; + pmubus_mapreqp->mapreq_addr = pmubus_rp.reg_addr; + pmubus_mapreqp->mapreq_size = pmubus_rp.reg_size; + + if (ret & MAPREQ_SHARED_BITS) { + pmubus_mapreqp->mapreq_mask = + pmubus_mask(pmubus_regs, rnumber, + pmubus_regmask); + DPRINTF(PMUBUS_MAP_DEBUG, ("rnumber=%d " + "mask=%llx\n", rnumber, + pmubus_mapreqp->mapreq_mask)); + if (pmubus_mapreqp->mapreq_mask == 0) { + kmem_free(pmubus_mapreqp, + sizeof (pmubus_mapreq_t)); + ret = DDI_ME_INVAL; + break; + } + } + + hp->ahi_common.ah_bus_private = pmubus_mapreqp; + + /* Initialize the access vectors */ + hp->ahi_get8 = pmubus_get8; + hp->ahi_get16 = pmubus_noget16; + hp->ahi_get32 = pmubus_get32; + hp->ahi_get64 = pmubus_noget64; + hp->ahi_put8 = pmubus_put8; + hp->ahi_put16 = pmubus_noput16; + hp->ahi_put32 = pmubus_put32; + hp->ahi_put64 = pmubus_noput64; + hp->ahi_rep_get8 = pmubus_norep_get8; + hp->ahi_rep_get16 = pmubus_norep_get16; + hp->ahi_rep_get32 = pmubus_norep_get32; + hp->ahi_rep_get64 = pmubus_norep_get64; + hp->ahi_rep_put8 = pmubus_norep_put8; + hp->ahi_rep_put16 = pmubus_norep_put16; + hp->ahi_rep_put32 = pmubus_norep_put32; + hp->ahi_rep_put64 = pmubus_norep_put64; + + ret = DDI_SUCCESS; + break; + } + + case DDI_MO_UNMAP: { + ddi_acc_impl_t *hp = (ddi_acc_impl_t *)mp->map_handlep; + + pmubus_mapreqp = hp->ahi_common.ah_bus_private; + + /* Free the our map request struct */ + kmem_free(pmubus_mapreqp, sizeof (pmubus_mapreq_t)); + + ret = DDI_SUCCESS; + break; + } + + default: + ret = DDI_ME_UNSUPPORTED; + } + } else { + /* Prepare the map request struct for a call to our parent */ + mp->map_type = DDI_MT_REGSPEC; + mp->map_obj.rp = (struct regspec *)&pci_reg; + + /* Pass the mapping operation up the device tree */ + ret = (DEVI(pdip)->devi_ops->devo_bus_ops->bus_map) + (pdip, rdip, mp, off, len, addrp); + } + +done: + if (pmubus_regs != NULL) + kmem_free(pmubus_regs, pmubus_regs_size); + if (pmubus_regmask != NULL) + kmem_free(pmubus_regmask, pmubus_regmask_size); + return (ret); +} + +static int +pmubus_ctlops(dev_info_t *dip, dev_info_t *rdip, + ddi_ctl_enum_t op, void *arg, void *result) +{ + dev_info_t *child = (dev_info_t *)arg; + pmubus_obpregspec_t *pmubus_rp; + char name[9]; + int reglen; + + switch (op) { + case DDI_CTLOPS_INITCHILD: + + if (ddi_getlongprop(DDI_DEV_T_NONE, child, + DDI_PROP_DONTPASS, "reg", (caddr_t)&pmubus_rp, + ®len) != DDI_SUCCESS) { + + return (DDI_FAILURE); + } + + if ((reglen % sizeof (pmubus_obpregspec_t)) != 0) { + cmn_err(CE_WARN, + "pmubus: reg property not well-formed for " + "%s size=%d\n", ddi_node_name(child), reglen); + kmem_free(pmubus_rp, reglen); + + return (DDI_FAILURE); + } + (void) snprintf(name, sizeof (name), "%x,%x", + pmubus_rp->reg_addr_hi, pmubus_rp->reg_addr_lo); + ddi_set_name_addr(child, name); + kmem_free(pmubus_rp, reglen); + + return (DDI_SUCCESS); + + case DDI_CTLOPS_UNINITCHILD: + + ddi_set_name_addr(child, NULL); + ddi_remove_minor_node(child, NULL); + impl_rem_dev_props(child); + + return (DDI_SUCCESS); + default: + break; + } + + return (ddi_ctlops(dip, rdip, op, arg, result)); +} |
