summaryrefslogtreecommitdiff
path: root/usr/src/uts/sun4u/io/pmubus.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/sun4u/io/pmubus.c')
-rw-r--r--usr/src/uts/sun4u/io/pmubus.c894
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,
+ &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));
+}