summaryrefslogtreecommitdiff
path: root/usr/src/uts/sun4u/io/pci/pci_pwr.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/sun4u/io/pci/pci_pwr.c')
-rw-r--r--usr/src/uts/sun4u/io/pci/pci_pwr.c765
1 files changed, 765 insertions, 0 deletions
diff --git a/usr/src/uts/sun4u/io/pci/pci_pwr.c b/usr/src/uts/sun4u/io/pci/pci_pwr.c
new file mode 100644
index 0000000000..a46658faf1
--- /dev/null
+++ b/usr/src/uts/sun4u/io/pci/pci_pwr.c
@@ -0,0 +1,765 @@
+/*
+ * 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 2004 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/kmem.h>
+#include <sys/async.h>
+#include <sys/sysmacros.h>
+#include <sys/sunddi.h>
+#include <sys/sunndi.h>
+#include <sys/ddi_impldefs.h>
+#include <sys/ddi_implfuncs.h>
+#include <sys/pci/pci_obj.h>
+#include <sys/pci/pci_pwr.h>
+#include <sys/pci.h>
+
+static void pci_pwr_update_comp(pci_pwr_t *pwr_p, pci_pwr_chld_t *p, int comp,
+ int lvl);
+
+#ifdef DEBUG
+static char *pci_pwr_bus_label[] = {"PM_LEVEL_B3", "PM_LEVEL_B2", \
+ "PM_LEVEL_B1", "PM_LEVEL_B0"};
+#endif
+
+/*LINTLIBRARY*/
+
+/*
+ * Retreive the pci_pwr_chld_t structure for a given devinfo node.
+ */
+pci_pwr_chld_t *
+pci_pwr_get_info(pci_pwr_t *pwr_p, dev_info_t *dip)
+{
+ pci_pwr_chld_t *p;
+
+ ASSERT(PM_CAPABLE(pwr_p));
+ ASSERT(MUTEX_HELD(&pwr_p->pwr_mutex));
+
+ for (p = pwr_p->pwr_info; p != NULL; p = p->next) {
+ if (p->dip == dip) {
+
+ return (p);
+ }
+ }
+
+ cmn_err(CE_PANIC, "unable to find pwr info data for %s@%s",
+ ddi_node_name(dip), ddi_get_name_addr(dip));
+
+ /*NOTREACHED*/
+}
+
+/*
+ * Create a pci_pwr_chld_t structure for a given devinfo node.
+ */
+void
+pci_pwr_create_info(pci_pwr_t *pwr_p, dev_info_t *dip)
+{
+ pci_pwr_chld_t *p;
+
+ ASSERT(PM_CAPABLE(pwr_p));
+
+ DEBUG2(DBG_PWR, ddi_get_parent(dip), "ADDING NEW PWR_INFO %s@%s\n",
+ ddi_node_name(dip), ddi_get_name_addr(dip));
+
+ p = kmem_zalloc(sizeof (struct pci_pwr_chld), KM_SLEEP);
+ p->dip = dip;
+
+ mutex_enter(&pwr_p->pwr_mutex);
+
+ /*
+ * Until components are created for this device, bus
+ * should be at full power since power of child device
+ * is unknown. Increment # children requiring "full power"
+ */
+ p->flags |= PWR_FP_HOLD;
+ pwr_p->pwr_fp++;
+
+ p->next = pwr_p->pwr_info;
+ pwr_p->pwr_info = p;
+
+ pci_pwr_change(pwr_p, pwr_p->current_lvl, pci_pwr_new_lvl(pwr_p));
+
+ mutex_exit(&pwr_p->pwr_mutex);
+}
+
+void
+pci_pwr_rm_info(pci_pwr_t *pwr_p, dev_info_t *cdip)
+{
+ pci_pwr_chld_t **prev_infop;
+ pci_pwr_chld_t *infop = NULL;
+ int i;
+
+ ASSERT(PM_CAPABLE(pwr_p));
+
+ mutex_enter(&pwr_p->pwr_mutex);
+
+ for (prev_infop = &pwr_p->pwr_info; *prev_infop != NULL;
+ prev_infop = &((*prev_infop)->next)) {
+ if ((*prev_infop)->dip == cdip) {
+ infop = *prev_infop;
+ break;
+ }
+ }
+
+ if (infop == NULL) {
+
+ mutex_exit(&pwr_p->pwr_mutex);
+ return;
+ }
+
+ *prev_infop = infop->next;
+
+ /*
+ * Remove any reference counts for this child.
+ */
+ if (infop->comp_pwr != NULL) {
+ for (i = 0; i < infop->num_comps; i++) {
+ pci_pwr_update_comp(pwr_p, infop, i, PM_LEVEL_NOLEVEL);
+ }
+
+ kmem_free(infop->comp_pwr, sizeof (int) * infop->num_comps);
+ }
+
+ if (infop->flags & PWR_FP_HOLD) {
+ pwr_p->pwr_fp--;
+ }
+
+ pci_pwr_change(pwr_p, pwr_p->current_lvl, pci_pwr_new_lvl(pwr_p));
+ mutex_exit(&pwr_p->pwr_mutex);
+ kmem_free(infop, sizeof (struct pci_pwr_chld));
+}
+
+/*
+ * Allocate space for component state information in pci_pwr_chld_t
+ */
+void
+pci_pwr_add_components(pci_pwr_t *pwr_p, dev_info_t *cdip, pci_pwr_chld_t *p)
+{
+ int num_comps = PM_NUMCMPTS(cdip);
+ int i;
+
+ ASSERT(MUTEX_HELD(&pwr_p->pwr_mutex));
+ /*
+ * Assume the power level of a component is UNKNOWN until
+ * notified otherwise.
+ */
+ if (num_comps > 0) {
+ p->comp_pwr =
+ kmem_alloc(sizeof (int) * num_comps, KM_SLEEP);
+ p->num_comps = num_comps;
+
+ DEBUG3(DBG_PWR, ddi_get_parent(cdip),
+ "ADDING %d COMPONENTS FOR %s@%s\n", num_comps,
+ ddi_node_name(cdip), ddi_get_name_addr(cdip));
+ } else {
+ cmn_err(CE_WARN, "%s%d device has %d components",
+ ddi_driver_name(cdip), ddi_get_instance(cdip),
+ num_comps);
+
+ return;
+ }
+
+ /*
+ * Release the fp hold that was made when the device
+ * was created.
+ */
+ ASSERT((p->flags & PWR_FP_HOLD) == PWR_FP_HOLD);
+ p->flags &= ~PWR_FP_HOLD;
+ pwr_p->pwr_fp--;
+
+ for (i = 0; i < num_comps; i++) {
+ /*
+ * Initialize the component lvl so that the
+ * state reference counts will be updated correctly.
+ */
+ p->comp_pwr[i] = PM_LEVEL_NOLEVEL;
+ pci_pwr_update_comp(pwr_p, p, i, PM_LEVEL_UNKNOWN);
+ }
+}
+
+/*
+ * Update the current power level for component. Then adjust the
+ * bus reference counter for given state.
+ */
+static void
+pci_pwr_update_comp(pci_pwr_t *pwr_p, pci_pwr_chld_t *p, int comp,
+ int lvl)
+{
+ ASSERT(MUTEX_HELD(&pwr_p->pwr_mutex));
+
+ /*
+ * Remove old pwr state count for old PM level.
+ */
+ switch (p->comp_pwr[comp]) {
+ case PM_LEVEL_UNKNOWN:
+ pwr_p->pwr_uk--;
+ p->u01--;
+ ASSERT(pwr_p->pwr_uk >= 0);
+ break;
+ case PM_LEVEL_D0:
+ pwr_p->pwr_d0--;
+ p->u01--;
+ ASSERT(pwr_p->pwr_d0 >= 0);
+ break;
+ case PM_LEVEL_D1:
+ pwr_p->pwr_d1--;
+ p->u01--;
+ ASSERT(pwr_p->pwr_d1 >= 0);
+ break;
+ case PM_LEVEL_D2:
+ pwr_p->pwr_d2--;
+ ASSERT(pwr_p->pwr_d2 >= 0);
+ break;
+ case PM_LEVEL_D3:
+ pwr_p->pwr_d3--;
+ ASSERT(pwr_p->pwr_d3 >= 0);
+ break;
+ default:
+ break;
+ }
+
+ p->comp_pwr[comp] = lvl;
+ /*
+ * Add new pwr state count for the new PM level.
+ */
+ switch (lvl) {
+ case PM_LEVEL_UNKNOWN:
+ pwr_p->pwr_uk++;
+ p->u01++;
+ break;
+ case PM_LEVEL_D0:
+ pwr_p->pwr_d0++;
+ p->u01++;
+ break;
+ case PM_LEVEL_D1:
+ pwr_p->pwr_d1++;
+ p->u01++;
+ break;
+ case PM_LEVEL_D2:
+ pwr_p->pwr_d2++;
+ break;
+ case PM_LEVEL_D3:
+ pwr_p->pwr_d3++;
+ break;
+ default:
+ break;
+ }
+
+}
+
+/*
+ * Knowing the current state of all devices on the bus, return the
+ * appropriate supported bus speed.
+ */
+int
+pci_pwr_new_lvl(pci_pwr_t *pwr_p)
+{
+ int b_lvl;
+
+ ASSERT(MUTEX_HELD(&pwr_p->pwr_mutex));
+
+ if (pwr_p->pwr_fp > 0) {
+ DEBUG1(DBG_PWR, pwr_p->pwr_dip, "new_lvl: "
+ "returning PM_LEVEL_B0 pwr_fp = %d\n", pwr_p->pwr_fp);
+
+ return (PM_LEVEL_B0);
+ }
+
+ /*
+ * If any components are at unknown power levels, the
+ * highest power level has to be assumed for the device (D0).
+ */
+ if (pwr_p->pwr_uk > 0) {
+ DEBUG1(DBG_PWR, pwr_p->pwr_dip, "new_lvl: unknown "
+ "count is %d. returning PM_LEVEL_B0\n", pwr_p->pwr_uk);
+
+ return (PM_LEVEL_B0);
+ }
+
+ /*
+ * Find the lowest theoretical level
+ * the bus can operate at.
+ */
+ if (pwr_p->pwr_d0 > 0) {
+ b_lvl = PM_LEVEL_B0;
+ DEBUG1(DBG_PWR, pwr_p->pwr_dip,
+ "new_lvl: PM_LEVEL_B0 d0 count = %d\n",
+ pwr_p->pwr_d0);
+ } else if (pwr_p->pwr_d1 > 0) {
+ b_lvl = PM_LEVEL_B1;
+ DEBUG1(DBG_PWR, pwr_p->pwr_dip,
+ "new_lvl: PM_LEVEL_B1 d1 count = %d\n",
+ pwr_p->pwr_d1);
+ } else if (pwr_p->pwr_d2 > 0) {
+ b_lvl = PM_LEVEL_B2;
+ DEBUG1(DBG_PWR, pwr_p->pwr_dip,
+ "new_lvl: PM_LEVEL_B2 d2 count = %d\n",
+ pwr_p->pwr_d2);
+ } else if (pwr_p->pwr_d3 > 0) {
+ b_lvl = PM_LEVEL_B3;
+ DEBUG1(DBG_PWR, pwr_p->pwr_dip,
+ "new_lvl: PM_LEVEL_B3 d3 count = %d\n",
+ pwr_p->pwr_d3);
+ } else {
+ DEBUG0(DBG_PWR, pwr_p->pwr_dip,
+ "new_lvl: PM_LEVEL_B3: all counts are 0\n");
+ b_lvl = PM_LEVEL_B3;
+ }
+
+ /*
+ * Now find the closest supported level available.
+ * If the level isn't available, have to find the
+ * next highest power level (or lowest in B# terms).
+ */
+ switch (b_lvl) {
+ case PM_LEVEL_B3:
+ if (pwr_p->pwr_flags & PCI_PWR_B3_CAPABLE) {
+ break;
+ }
+ /*FALLTHROUGH*/
+ case PM_LEVEL_B2:
+ if (pwr_p->pwr_flags & PCI_PWR_B2_CAPABLE) {
+ b_lvl = PM_LEVEL_B2;
+ break;
+ }
+ /*FALLTHROUGH*/
+ case PM_LEVEL_B1:
+ if (pwr_p->pwr_flags & PCI_PWR_B1_CAPABLE) {
+ b_lvl = PM_LEVEL_B1;
+ break;
+ }
+ /*FALLTHROUGH*/
+ case PM_LEVEL_B0:
+ /*
+ * This level always supported
+ */
+ b_lvl = PM_LEVEL_B0;
+ break;
+ }
+ DEBUG1(DBG_PWR, pwr_p->pwr_dip,
+ "new_lvl: Adjusted Level is %s\n",
+ pci_pwr_bus_label[b_lvl]);
+
+ return (b_lvl);
+
+}
+
+int
+pci_raise_power(pci_pwr_t *pwr_p, int current, int new, void *impl_arg,
+ pm_bp_nexus_pwrup_t bpn)
+{
+ int ret = DDI_SUCCESS, pwrup_res;
+
+ ASSERT(MUTEX_HELD(&pwr_p->pwr_mutex));
+
+ pci_pwr_component_busy(pwr_p);
+ mutex_exit(&pwr_p->pwr_mutex);
+ ret = pm_busop_bus_power(pwr_p->pwr_dip, impl_arg,
+ BUS_POWER_NEXUS_PWRUP, (void *) &bpn,
+ (void *) &pwrup_res);
+ if (ret != DDI_SUCCESS || pwrup_res != DDI_SUCCESS) {
+ mutex_enter(&pwr_p->pwr_mutex);
+ pci_pwr_component_idle(pwr_p);
+ mutex_exit(&pwr_p->pwr_mutex);
+ cmn_err(CE_WARN, "%s%d pci_raise_power failed",
+ ddi_driver_name(pwr_p->pwr_dip),
+ ddi_get_instance(pwr_p->pwr_dip));
+ }
+
+ return (ret);
+}
+
+int
+pci_pwr_ops(pci_pwr_t *pwr_p, dev_info_t *dip, void *impl_arg,
+ pm_bus_power_op_t op, void *arg, void *result)
+{
+ pci_pwr_chld_t *p_chld;
+ pm_bp_nexus_pwrup_t bpn;
+ pm_bp_child_pwrchg_t *bpc = (pm_bp_child_pwrchg_t *)arg;
+ dev_info_t *rdip = bpc->bpc_dip;
+ int new_level, *res = (int *)result, ret = DDI_SUCCESS;
+
+ mutex_enter(&pwr_p->pwr_mutex);
+ switch (op) {
+ case BUS_POWER_HAS_CHANGED:
+ p_chld = pci_pwr_get_info(pwr_p, rdip);
+ DEBUG5(DBG_PWR, dip, "%s@%s CHANGED_POWER cmp = %d "
+ "old = %d new = %d\n",
+ ddi_node_name(rdip), ddi_get_name_addr(rdip),
+ bpc->bpc_comp, bpc->bpc_olevel, bpc->bpc_nlevel);
+
+ if (*res == DDI_FAILURE) {
+ DEBUG0(DBG_PWR, rdip, "changed_power_req FAILED\n");
+ break;
+ } else {
+
+ /*
+ * pci_pwr_add_components must be called here if
+ * comp_pwr hasn't been set up yet. It has to be done
+ * here rather than in post-attach, since it is possible
+ * for power() of child to get called before attach
+ * completes.
+ */
+ if (p_chld->comp_pwr == NULL)
+ pci_pwr_add_components(pwr_p, rdip, p_chld);
+
+ pci_pwr_update_comp(pwr_p, p_chld,
+ bpc->bpc_comp, bpc->bpc_nlevel);
+ }
+
+ new_level = pci_pwr_new_lvl(pwr_p);
+ bpn.bpn_dip = pwr_p->pwr_dip;
+ bpn.bpn_comp = PCI_PM_COMP_0;
+ bpn.bpn_level = new_level;
+ bpn.bpn_private = bpc->bpc_private;
+
+ if (new_level > pwr_p->current_lvl)
+ return (pci_raise_power(pwr_p, pwr_p->current_lvl,
+ new_level, impl_arg, bpn));
+ else
+ pci_pwr_change(pwr_p, pwr_p->current_lvl,
+ new_level);
+ break;
+
+ case BUS_POWER_PRE_NOTIFICATION:
+ DEBUG5(DBG_PWR, dip, "PRE %s@%s cmp = %d old = %d "
+ "new = %d. TEMP FULL POWER\n",
+ ddi_node_name(rdip), ddi_get_name_addr(rdip),
+ bpc->bpc_comp, bpc->bpc_olevel, bpc->bpc_nlevel);
+
+ /*
+ * Any state changes require that the bus be at full
+ * power (B0) so that the device configuration
+ * registers can be accessed. Make a fp hold here
+ * so device remains at full power during power
+ * configuration.
+ */
+
+ pwr_p->pwr_fp++;
+ DEBUG1(DBG_PWR, pwr_p->pwr_dip,
+ "incremented fp is %d in PRE_NOTE\n\n", pwr_p->pwr_fp);
+
+ bpn.bpn_dip = pwr_p->pwr_dip;
+ bpn.bpn_comp = PCI_PM_COMP_0;
+ bpn.bpn_level = PM_LEVEL_B0;
+ bpn.bpn_private = bpc->bpc_private;
+
+ if (PM_LEVEL_B0 > pwr_p->current_lvl)
+ return (pci_raise_power(pwr_p, pwr_p->current_lvl,
+ PM_LEVEL_B0, impl_arg, bpn));
+
+ break;
+
+ case BUS_POWER_POST_NOTIFICATION:
+ p_chld = pci_pwr_get_info(pwr_p, rdip);
+ DEBUG5(DBG_PWR, dip, "POST %s@%s cmp = %d old = %d new = %d\n",
+ ddi_node_name(rdip), ddi_get_name_addr(rdip),
+ bpc->bpc_comp, bpc->bpc_olevel, bpc->bpc_nlevel);
+
+ if (*res == DDI_FAILURE) {
+ DEBUG0(DBG_PWR, rdip, "child's power routine FAILED\n");
+ } else {
+
+ /*
+ * pci_pwr_add_components must be called here if
+ * comp_pwr hasen't been set up yet. It has to be done
+ * here rather than in post-attach, since it is possible
+ * for power() of child to get called before attach
+ * completes.
+ */
+ if (p_chld->comp_pwr == NULL)
+ pci_pwr_add_components(pwr_p, rdip, p_chld);
+
+ pci_pwr_update_comp(pwr_p, p_chld,
+ bpc->bpc_comp, bpc->bpc_nlevel);
+
+ }
+
+ pwr_p->pwr_fp--;
+ DEBUG1(DBG_PWR, pwr_p->pwr_dip,
+ "decremented fp is %d in POST_NOTE\n\n", pwr_p->pwr_fp);
+
+ new_level = pci_pwr_new_lvl(pwr_p);
+ bpn.bpn_dip = pwr_p->pwr_dip;
+ bpn.bpn_comp = PCI_PM_COMP_0;
+ bpn.bpn_level = new_level;
+ bpn.bpn_private = bpc->bpc_private;
+
+ if (new_level > pwr_p->current_lvl)
+ return (pci_raise_power(pwr_p, pwr_p->current_lvl,
+ new_level, impl_arg, bpn));
+ else
+ pci_pwr_change(pwr_p, pwr_p->current_lvl,
+ new_level);
+
+ break;
+ default:
+ mutex_exit(&pwr_p->pwr_mutex);
+ return (pm_busop_bus_power(dip, impl_arg, op, arg, result));
+ }
+
+ mutex_exit(&pwr_p->pwr_mutex);
+
+ return (ret);
+}
+
+void
+pci_pwr_resume(dev_info_t *dip, pci_pwr_t *pwr_p)
+{
+ dev_info_t *cdip;
+
+ /*
+ * Inform the PM framework of the current state of the device.
+ * (it is unknown to PM framework at this point).
+ */
+ if (PM_CAPABLE(pwr_p)) {
+ pwr_p->current_lvl = pci_pwr_current_lvl(pwr_p);
+ pm_power_has_changed(dip, PCI_PM_COMP_0,
+ pwr_p->current_lvl);
+ }
+
+ /*
+ * Restore config registers for children that did not save
+ * their own registers. Children pwr states are UNKNOWN after
+ * a resume since it is possible for the PM framework to call
+ * resume without an actual power cycle. (ie if suspend fails).
+ */
+ for (cdip = ddi_get_child(dip); cdip != NULL;
+ cdip = ddi_get_next_sibling(cdip)) {
+
+ /*
+ * Not interested in children who are not already
+ * init'ed. They will be set up by init_child().
+ */
+ if (i_ddi_node_state(cdip) < DS_INITIALIZED) {
+ DEBUG2(DBG_DETACH, dip,
+ "DDI_RESUME: skipping %s%d not in CF1\n",
+ ddi_driver_name(cdip), ddi_get_instance(cdip));
+
+ continue;
+ }
+
+ /*
+ * Only restore config registers if saved by nexus.
+ */
+ if (ddi_prop_exists(DDI_DEV_T_ANY, cdip, DDI_PROP_DONTPASS,
+ NEXUS_SAVED) == 1) {
+ (void) pci_restore_config_regs(cdip);
+
+ DEBUG2(DBG_PWR, dip,
+ "DDI_RESUME: nexus restoring %s%d config regs\n",
+ ddi_driver_name(cdip), ddi_get_instance(cdip));
+
+
+ if (ndi_prop_remove(DDI_DEV_T_NONE, cdip,
+ NEXUS_SAVED) != DDI_PROP_SUCCESS) {
+ cmn_err(CE_WARN, "%s%d can't remove prop %s",
+ ddi_driver_name(cdip),
+ ddi_get_instance(cdip),
+ NEXUS_SAVED);
+ }
+ }
+ }
+}
+
+void
+pci_pwr_suspend(dev_info_t *dip, pci_pwr_t *pwr_p)
+{
+ dev_info_t *cdip;
+
+ /*
+ * Save the state of the configuration headers of child
+ * nodes.
+ */
+
+ for (cdip = ddi_get_child(dip); cdip != NULL;
+ cdip = ddi_get_next_sibling(cdip)) {
+ pci_pwr_chld_t *p;
+ int i;
+ int num_comps;
+ int ret;
+ /*
+ * Not interested in children who are not already
+ * init'ed. They will be set up in init_child().
+ */
+ if (i_ddi_node_state(cdip) < DS_INITIALIZED) {
+ DEBUG2(DBG_DETACH, dip, "DDI_SUSPEND: skipping "
+ "%s%d not in CF1\n", ddi_driver_name(cdip),
+ ddi_get_instance(cdip));
+
+ continue;
+ }
+
+ /*
+ * Only save config registers if not already saved by child.
+ */
+ if (ddi_prop_exists(DDI_DEV_T_ANY, cdip, DDI_PROP_DONTPASS,
+ SAVED_CONFIG_REGS) == 1) {
+
+ continue;
+ }
+
+ /*
+ * The nexus needs to save config registers. Create a property
+ * so it knows to restore on resume.
+ */
+ ret = ndi_prop_create_boolean(DDI_DEV_T_NONE, cdip,
+ NEXUS_SAVED);
+
+ if (ret != DDI_PROP_SUCCESS) {
+ cmn_err(CE_WARN, "%s%d can't update prop %s",
+ ddi_driver_name(cdip), ddi_get_instance(cdip),
+ NEXUS_SAVED);
+ }
+
+ if (!PM_CAPABLE(pwr_p)) {
+ (void) pci_save_config_regs(cdip);
+
+ continue;
+ }
+
+ mutex_enter(&pwr_p->pwr_mutex);
+ p = pci_pwr_get_info(pwr_p, cdip);
+ num_comps = p->num_comps;
+
+ /*
+ * If a device has components, reset the power level
+ * to unknown. This will ensure that the bus is full
+ * power so that saving register won't panic (if
+ * the device is already powered off, the child should
+ * have already done the save, but an incorrect driver
+ * may have forgotten). If resetting power levels
+ * to unknown isn't done here, it would have to be done
+ * in resume since pci driver has no way of knowing
+ * actual state of HW (power cycle may not have
+ * occurred, and it was decided that poking into a
+ * child's config space should be avoided unless
+ * absolutely necessary).
+ */
+ if (p->comp_pwr == NULL) {
+ (void) pci_save_config_regs(cdip);
+ } else {
+
+ for (i = 0; i < num_comps; i++) {
+ pci_pwr_update_comp(pwr_p, p, i,
+ PM_LEVEL_UNKNOWN);
+ }
+ /*
+ * ensure bus power is on before saving
+ * config regs.
+ */
+ pci_pwr_change(pwr_p, pwr_p->current_lvl,
+ pci_pwr_new_lvl(pwr_p));
+
+ (void) pci_save_config_regs(cdip);
+ }
+ mutex_exit(&pwr_p->pwr_mutex);
+ }
+}
+
+void
+pci_pwr_component_busy(pci_pwr_t *p)
+{
+ ASSERT(MUTEX_HELD(&p->pwr_mutex));
+ if ((p->pwr_flags & PCI_PWR_COMP_BUSY) == 0) {
+ if (pm_busy_component(p->pwr_dip, PCI_PM_COMP_0) ==
+ DDI_FAILURE) {
+ cmn_err(CE_WARN,
+ "%s%d pm_busy_component failed",
+ ddi_driver_name(p->pwr_dip),
+ ddi_get_instance(p->pwr_dip));
+ } else {
+ DEBUG0(DBG_PWR, p->pwr_dip,
+ "called PM_BUSY_COMPONENT(). BUSY BIT SET\n");
+ p->pwr_flags |= PCI_PWR_COMP_BUSY;
+ }
+ } else {
+ DEBUG0(DBG_PWR, p->pwr_dip, "BUSY BIT ALREADY SET\n");
+ }
+}
+
+void
+pci_pwr_component_idle(pci_pwr_t *p)
+{
+ ASSERT(MUTEX_HELD(&p->pwr_mutex));
+ if (p->pwr_flags & PCI_PWR_COMP_BUSY) {
+ if (pm_idle_component(p->pwr_dip, PCI_PM_COMP_0) ==
+ DDI_FAILURE) {
+ cmn_err(CE_WARN,
+ "%s%d pm_idle_component failed",
+ ddi_driver_name(p->pwr_dip),
+ ddi_get_instance(p->pwr_dip));
+ } else {
+ DEBUG0(DBG_PWR, p->pwr_dip,
+ "called PM_IDLE_COMPONENT() BUSY BIT CLEARED\n");
+ p->pwr_flags &= ~PCI_PWR_COMP_BUSY;
+ }
+ } else {
+ DEBUG0(DBG_PWR, p->pwr_dip, "BUSY BIT ALREADY CLEARED\n");
+ }
+}
+
+void
+pci_pwr_change(pci_pwr_t *pwr_p, int current, int new)
+{
+ ASSERT(MUTEX_HELD(&pwr_p->pwr_mutex));
+ if (current == new) {
+ DEBUG2(DBG_PWR, pwr_p->pwr_dip,
+ "No change in power required. Should be "
+ "busy. (current=%d) == (new=%d)\n",
+ current, new);
+ pci_pwr_component_busy(pwr_p);
+
+ return;
+ }
+
+ if (new < current) {
+ DEBUG2(DBG_PWR, pwr_p->pwr_dip,
+ "should be idle (new=%d) < (current=%d)\n",
+ new, current);
+ pci_pwr_component_idle(pwr_p);
+
+ return;
+ }
+
+ if (new > current) {
+ DEBUG2(DBG_PWR, pwr_p->pwr_dip, "pwr_change: "
+ "pm_raise_power() and should be busy. "
+ "(new=%d) > (current=%d)\n", new, current);
+ pci_pwr_component_busy(pwr_p);
+ mutex_exit(&pwr_p->pwr_mutex);
+ if (pm_raise_power(pwr_p->pwr_dip, PCI_PM_COMP_0,
+ new) == DDI_FAILURE) {
+ cmn_err(CE_WARN, "%s%d pm_raise_power failed",
+ ddi_driver_name(pwr_p->pwr_dip),
+ ddi_get_instance(pwr_p->pwr_dip));
+ }
+ mutex_enter(&pwr_p->pwr_mutex);
+
+ return;
+ }
+}