root/usr/src/uts/sun4u/io/pmubus.c
/*
 * 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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
/*
 * Copyright 2012 Garrett D'Amore <garrett@damore.org>.  All rights reserved.
 */


#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,
        NULL,
        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,
        NULL,
        ddi_quiesce_not_needed,         /* quiesce */
};

/*
 * 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_ANY, 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_ANY, 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=%lx value=%x "
            "mask=%x\n", (void *)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=%lx value=%x "
            "mask=%x\n", (void *)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=%lx "
                    "value=%x mask=%lx\n", (void *)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=%lx "
                    "value=%x\n", (void *)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=%lx "
                    "value=%x mask=%lx\n", (void *)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=%lx "
                    "value=%x\n", (void *)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=%lx len=%lx\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=%p\n", ddi_get_name(rdip), ddi_get_instance(rdip),
                    rnumber, (void *)mp->map_handlep));

                if (ddi_getlongprop(DDI_DEV_T_ANY, 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_ANY, 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, (void *)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=%lx\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_ANY, child,
                    DDI_PROP_DONTPASS, "reg", (caddr_t)&pmubus_rp,
                    &reglen) != 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));
}