view usr/src/uts/sun4u/io/pmubus.c @ 13630:76b4d0b12a17

998 obsolete DMA driver interfaces should be removed Reviewed by: Igor Khozhukhov <igor.khozhukhov@nexenta.com> Reviewed by: Albert Lee <trisk@nexenta.com> Reviewed by: Robert Mustacchi <rm@joyent.com> Approved by: Richard Lowe <richlowe@richlowe.net>
author Garrett D'Amore <garrett@damore.org>
date Thu, 01 Mar 2012 15:25:51 -0800
parents 2621e50fdf4a
children 1e93e74c836a
line wrap: on
line source

/*
 * 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));
}