#include <sys/types.h>
#include <sys/ddi.h>
#include <sys/kmem.h>
#include <sys/sysmacros.h>
#include <sys/sunddi.h>
#include <sys/sunpm.h>
#include <sys/epm.h>
#include <sys/sunndi.h>
#include <sys/ddi_impldefs.h>
#include <sys/ddi_implfuncs.h>
#include <sys/pcie.h>
#include <sys/pcie_impl.h>
#include <sys/promif.h>
#include <sys/pcie_pwr.h>
static int pcie_pwr_change(dev_info_t *dip, pcie_pwr_t *pwr_p, int new);
static void pwr_update_counters(int *countersp, int olevel, int nlevel);
static int pwr_level_allowed(pcie_pwr_t *pwr_p);
static void pcie_add_comps(dev_info_t *dip, dev_info_t *cdip,
pcie_pwr_t *pwr_p);
static void pcie_remove_comps(dev_info_t *dip, dev_info_t *cdip,
pcie_pwr_t *pwr_p);
static void pcie_pm_subrelease(dev_info_t *dip, pcie_pwr_t *pwr_p);
static boolean_t pcie_is_pcie(dev_info_t *dip);
#ifdef DEBUG
static char *pcie_decode_pwr_op(pm_bus_power_op_t op);
#else
#define pcie_decode_pwr_op
#endif
int
pcie_power(dev_info_t *dip, int component, int level)
{
pcie_pwr_t *pwr_p = PCIE_NEXUS_PMINFO(dip);
int *counters = pwr_p->pwr_counters;
int pmcaps = pwr_p->pwr_pmcaps;
int ret = DDI_FAILURE;
#if defined(__x86)
if (dip)
return (DDI_SUCCESS);
#endif
ASSERT(level != PM_LEVEL_UNKNOWN);
ASSERT(level == PM_LEVEL_D0 || level == PM_LEVEL_D3 ||
(level == PM_LEVEL_D1 && (pmcaps & PCIE_SUPPORTS_D1)) ||
(level == PM_LEVEL_D2 && (pmcaps & PCIE_SUPPORTS_D2)));
mutex_enter(&pwr_p->pwr_lock);
PCIE_DBG("%s(%d): pcie_power: change from %d to %d\n",
ddi_driver_name(dip), ddi_get_instance(dip), pwr_p->pwr_func_lvl,
level);
if (pwr_p->pwr_func_lvl == level) {
PCIE_DBG("%s(%d): pcie_power: already at %d\n",
ddi_driver_name(dip), ddi_get_instance(dip), level);
ret = DDI_SUCCESS;
goto pcie_pwr_done;
}
if (level < pwr_p->pwr_func_lvl) {
if (pwr_p->pwr_flags & PCIE_PM_BUSY) {
PCIE_DBG("%s(%d): pcie_power: rejecting change to %d "
"as busy\n", ddi_driver_name(dip),
ddi_get_instance(dip), level);
goto pcie_pwr_done;
}
ASSERT(!counters[PCIE_D0_INDEX] &&
!counters[PCIE_UNKNOWN_INDEX]);
if (level < pwr_level_allowed(pwr_p)) {
PCIE_DBG("%s(%d): pcie_power: rejecting level %d as"
" %d is the lowest possible\n",
ddi_driver_name(dip), ddi_get_instance(dip), level,
pwr_level_allowed(pwr_p));
goto pcie_pwr_done;
}
}
if (pcie_pwr_change(dip, pwr_p, level) != DDI_SUCCESS) {
PCIE_DBG("%s(%d): pcie_power: attempt to change to %d "
" failed \n", ddi_driver_name(dip), ddi_get_instance(dip),
level);
goto pcie_pwr_done;
}
pwr_p->pwr_func_lvl = level;
PCIE_DBG("%s(%d): pcie_power: level changed to %d \n",
ddi_driver_name(dip), ddi_get_instance(dip), level);
ret = DDI_SUCCESS;
pcie_pwr_done:
mutex_exit(&pwr_p->pwr_lock);
return (ret);
}
static int
pcie_pwr_change(dev_info_t *dip, pcie_pwr_t *pwr_p, int new)
{
uint16_t pmcsr;
ASSERT(MUTEX_HELD(&pwr_p->pwr_lock));
ASSERT(new != pwr_p->pwr_func_lvl);
pmcsr = pci_config_get16(pwr_p->pwr_conf_hdl, pwr_p->pwr_pmcsr_offset);
pmcsr &= ~PCI_PMCSR_STATE_MASK;
switch (new) {
case PM_LEVEL_D0:
pmcsr |= PCI_PMCSR_D0;
break;
case PM_LEVEL_D1:
pmcsr |= PCI_PMCSR_D1;
break;
case PM_LEVEL_D2:
pmcsr |= PCI_PMCSR_D2;
break;
case PM_LEVEL_D3:
pmcsr |= PCI_PMCSR_D3HOT;
break;
default:
ASSERT(0);
break;
}
if (new == PM_LEVEL_D3) {
PCIE_DBG("%s(%d): pwr_change: saving config space regs\n",
ddi_driver_name(dip), ddi_get_instance(dip));
if (pci_save_config_regs(dip) != DDI_SUCCESS) {
PCIE_DBG("%s(%d): pcie_pwr_change: failed to save "
"config space regs\n", ddi_driver_name(dip),
ddi_get_instance(dip));
return (DDI_FAILURE);
}
}
pci_config_put16(pwr_p->pwr_conf_hdl, pwr_p->pwr_pmcsr_offset, pmcsr);
delay(drv_usectohz(PCI_CLK_SETTLE_TIME));
if (pwr_p->pwr_func_lvl == PM_LEVEL_D3) {
PCIE_DBG("%s(%d): pcie_pwr_change: restoring config space\n",
ddi_driver_name(dip), ddi_get_instance(dip));
if (pci_restore_config_regs(dip) != DDI_SUCCESS) {
PCIE_DBG("%s(%d): pcie_pwr_change: failed to restore "
"config space regs\n", ddi_driver_name(dip),
ddi_get_instance(dip));
return (DDI_FAILURE);
}
}
return (DDI_SUCCESS);
}
int
pcie_bus_power(dev_info_t *dip, void *impl_arg, pm_bus_power_op_t op,
void *arg, void *result)
{
pcie_pwr_t *pwr_p = PCIE_NEXUS_PMINFO(dip);
int *counters = pwr_p->pwr_counters;
int *child_counters;
pm_bp_child_pwrchg_t *bpc;
pm_bp_has_changed_t *bphc;
dev_info_t *cdip;
int new_level;
int old_level;
int rv = DDI_SUCCESS;
int level_allowed, comp;
#if defined(__x86)
if (dip)
return (DDI_SUCCESS);
#endif
switch (op) {
case BUS_POWER_PRE_NOTIFICATION:
case BUS_POWER_POST_NOTIFICATION:
bpc = (pm_bp_child_pwrchg_t *)arg;
cdip = bpc->bpc_dip;
new_level = bpc->bpc_nlevel;
old_level = bpc->bpc_olevel;
comp = bpc->bpc_comp;
break;
case BUS_POWER_HAS_CHANGED:
bphc = (pm_bp_has_changed_t *)arg;
cdip = bphc->bphc_dip;
new_level = bphc->bphc_nlevel;
old_level = bphc->bphc_olevel;
comp = bphc->bphc_comp;
break;
default:
break;
}
ASSERT(pwr_p);
mutex_enter(&pwr_p->pwr_lock);
switch (op) {
case BUS_POWER_PRE_NOTIFICATION:
PCIE_DBG("%s(%d): pcie_bus_power: %s@%d op %s %d->%d\n",
ddi_driver_name(dip), ddi_get_instance(dip),
ddi_driver_name(cdip), ddi_get_instance(cdip),
pcie_decode_pwr_op(op), old_level, new_level);
if (pwr_p->pwr_flags & PCIE_NO_CHILD_PM) {
if (!PCIE_IS_COMPS_COUNTED(cdip)) {
PCIE_DBG("%s(%d): pcie_bus_power: marking "
"child busy to disable pm \n",
ddi_driver_name(dip),
ddi_get_instance(dip));
(void) pm_busy_component(cdip, 0);
}
if (new_level < PM_LEVEL_D0 && !comp) {
PCIE_DBG("%s(%d): pcie_bus_power: rejecting "
"child's attempt to go to %d\n",
ddi_driver_name(dip), ddi_get_instance(dip),
new_level);
rv = DDI_FAILURE;
}
}
mutex_exit(&pwr_p->pwr_lock);
if (rv == DDI_SUCCESS)
rv = pcie_pm_hold(dip);
return (rv);
case BUS_POWER_HAS_CHANGED:
case BUS_POWER_POST_NOTIFICATION:
PCIE_DBG("%s(%d): pcie_bus_power: %s@%d op %s %d->%d\n",
ddi_driver_name(dip), ddi_get_instance(dip),
ddi_driver_name(cdip), ddi_get_instance(cdip),
pcie_decode_pwr_op(op), old_level, new_level);
if (!PCIE_IS_COMPS_COUNTED(cdip)) {
(void) pcie_pm_add_child(dip, cdip);
if ((pwr_p->pwr_flags & PCIE_NO_CHILD_PM) &&
(op == BUS_POWER_HAS_CHANGED)) {
PCIE_DBG("%s(%d): pcie_bus_power: marking "
"child busy to disable pm \n",
ddi_driver_name(dip),
ddi_get_instance(dip));
(void) pm_busy_component(cdip, 0);
if (new_level < PM_LEVEL_D0)
cmn_err(CE_WARN, "!Downstream device "
"%s@%d went to non-D0 state: "
"possible loss of link\n",
ddi_driver_name(cdip),
ddi_get_instance(cdip));
}
}
if (op == BUS_POWER_POST_NOTIFICATION &&
PCIE_SUPPORTS_DEVICE_PM(dip)) {
pcie_pm_subrelease(dip, pwr_p);
}
if (*((int *)result) == DDI_FAILURE) {
PCIE_DBG("%s(%d): pcie_bus_power: change for %s%d "
"failed\n", ddi_driver_name(dip),
ddi_get_instance(dip), ddi_driver_name(cdip),
ddi_get_instance(cdip));
break;
}
pwr_update_counters(counters, old_level, new_level);
child_counters = PCIE_CHILD_COUNTERS(cdip);
pwr_update_counters(child_counters, old_level, new_level);
if (!PCIE_SUPPORTS_DEVICE_PM(dip))
break;
level_allowed = pwr_level_allowed(pwr_p);
if (level_allowed >= pwr_p->pwr_func_lvl &&
!(pwr_p->pwr_flags & PCIE_PM_BUSY)) {
PCIE_DBG("%s(%d): pcie_bus_power: marking busy\n",
ddi_driver_name(dip), ddi_get_instance(dip));
(void) pm_busy_component(dip, 0);
pwr_p->pwr_flags |= PCIE_PM_BUSY;
break;
}
if ((level_allowed < pwr_p->pwr_func_lvl) &&
(pwr_p->pwr_hold == 0) &&
(pwr_p->pwr_flags & PCIE_PM_BUSY)) {
PCIE_DBG("%s(%d): pcie_bus_power: marking idle\n",
ddi_driver_name(dip), ddi_get_instance(dip));
(void) pm_idle_component(dip, 0);
pwr_p->pwr_flags &= ~PCIE_PM_BUSY;
break;
}
break;
default:
mutex_exit(&pwr_p->pwr_lock);
return (pm_busop_bus_power(dip, impl_arg, op, arg, result));
}
mutex_exit(&pwr_p->pwr_lock);
return (rv);
}
static void
pwr_update_counters(int *countersp, int olevel, int nlevel)
{
uint32_t index;
ASSERT(olevel >= PM_LEVEL_UNKNOWN && olevel <= PM_LEVEL_D0);
ASSERT(nlevel >= PM_LEVEL_UNKNOWN && nlevel <= PM_LEVEL_D0);
index = (olevel == PM_LEVEL_UNKNOWN ? PCIE_UNKNOWN_INDEX : olevel);
countersp[index]--;
index = (nlevel == PM_LEVEL_UNKNOWN ? PCIE_UNKNOWN_INDEX : nlevel);
countersp[index]++;
}
static int
pwr_level_allowed(pcie_pwr_t *pwr_p)
{
int *counters = pwr_p->pwr_counters;
int i, j;
ASSERT(MUTEX_HELD(&pwr_p->pwr_lock));
for (i = PCIE_UNKNOWN_INDEX; i > 0; i--) {
if (counters[i]) {
if (i == PCIE_UNKNOWN_INDEX)
return (PM_LEVEL_D0);
if (PCIE_LEVEL_SUPPORTED(pwr_p->pwr_pmcaps, i))
return (i);
for (j = i + 1; j <= PCIE_D0_INDEX; j++) {
if (PCIE_LEVEL_SUPPORTED(pwr_p->pwr_pmcaps, j))
return (j);
}
}
}
return (PM_LEVEL_D3);
}
static void
pcie_add_comps(dev_info_t *dip, dev_info_t *cdip, pcie_pwr_t *pwr_p)
{
int comps = PM_NUMCMPTS(cdip);
pcie_pm_t *pcie_pm_p;
pcie_pwr_child_t *cpwr_p;
ASSERT(MUTEX_HELD(&pwr_p->pwr_lock));
if (!comps)
return;
PCIE_DBG("%s(%d): pcie_add_comps: unknown level counter incremented "
"from %d by %d because of %s@%d\n",
ddi_driver_name(dip), ddi_get_instance(dip),
(pwr_p->pwr_counters)[PCIE_UNKNOWN_INDEX], comps,
ddi_driver_name(cdip), ddi_get_instance(cdip));
(pwr_p->pwr_counters)[PCIE_UNKNOWN_INDEX] += comps;
if ((pcie_pm_p = PCIE_PMINFO(cdip)) == NULL) {
pcie_pm_p = (pcie_pm_t *)kmem_zalloc(
sizeof (pcie_pm_t), KM_SLEEP);
PCIE_SET_PMINFO(cdip, pcie_pm_p);
}
cpwr_p = (pcie_pwr_child_t *)kmem_zalloc(sizeof (pcie_pwr_child_t),
KM_SLEEP);
pcie_pm_p->pcie_par_pminfo = cpwr_p;
(cpwr_p->pwr_child_counters)[PCIE_UNKNOWN_INDEX] += comps;
}
static void
pcie_remove_comps(dev_info_t *dip, dev_info_t *cdip, pcie_pwr_t *pwr_p)
{
int i;
int *child_counters;
ASSERT(MUTEX_HELD(&pwr_p->pwr_lock));
if (!(PCIE_PMINFO(cdip)) || !PCIE_PAR_PMINFO(cdip)) {
if (PCIE_SUPPORTS_DEVICE_PM(dip)) {
pcie_pm_subrelease(dip, pwr_p);
}
return;
}
PCIE_DBG("%s(%d): pcie_remove_comps:counters decremented because of "
"%s@%d\n", ddi_driver_name(dip), ddi_get_instance(dip),
ddi_driver_name(cdip), ddi_get_instance(cdip));
child_counters = PCIE_CHILD_COUNTERS(cdip);
for (i = 0; i < PCIE_MAX_PWR_LEVELS; i++) {
ASSERT((pwr_p->pwr_counters)[i] >= child_counters[i]);
(pwr_p->pwr_counters)[i] -= child_counters[i];
}
kmem_free(PCIE_PAR_PMINFO(cdip), sizeof (pcie_pwr_child_t));
kmem_free(PCIE_PMINFO(cdip), sizeof (pcie_pm_t));
PCIE_RESET_PMINFO(cdip);
}
int
pwr_common_setup(dev_info_t *dip)
{
pcie_pm_t *pcie_pm_p;
pcie_pwr_t *pwr_p;
int pminfo_created = 0;
if ((pcie_pm_p = PCIE_PMINFO(dip)) == NULL) {
pcie_pm_p = (pcie_pm_t *)kmem_zalloc(
sizeof (pcie_pm_t), KM_SLEEP);
PCIE_SET_PMINFO(dip, pcie_pm_p);
pminfo_created = 1;
}
pwr_p = (pcie_pwr_t *)kmem_zalloc(sizeof (pcie_pwr_t), KM_SLEEP);
mutex_init(&pwr_p->pwr_lock, NULL, MUTEX_DRIVER, NULL);
pwr_p->pwr_func_lvl = PM_LEVEL_UNKNOWN;
pwr_p->pwr_pmcaps = PCIE_DEFAULT_LEVEL_SUPPORTED;
if (pcie_plat_pwr_setup(dip) != DDI_SUCCESS)
goto pwr_common_err;
pcie_pm_p->pcie_pwr_p = pwr_p;
return (DDI_SUCCESS);
pwr_common_err:
mutex_destroy(&pwr_p->pwr_lock);
kmem_free(pwr_p, sizeof (pcie_pwr_t));
if (pminfo_created) {
PCIE_RESET_PMINFO(dip);
kmem_free(pcie_pm_p, sizeof (pcie_pm_t));
}
return (DDI_FAILURE);
}
void
pwr_common_teardown(dev_info_t *dip)
{
pcie_pm_t *pcie_pm_p = PCIE_PMINFO(dip);
pcie_pwr_t *pwr_p;
if (!pcie_pm_p || !(pwr_p = PCIE_NEXUS_PMINFO(dip)))
return;
pcie_plat_pwr_teardown(dip);
mutex_destroy(&pwr_p->pwr_lock);
pcie_pm_p->pcie_pwr_p = NULL;
kmem_free(pwr_p, sizeof (pcie_pwr_t));
if (!PCIE_PAR_PMINFO(dip)) {
kmem_free(pcie_pm_p, sizeof (pcie_pm_t));
PCIE_RESET_PMINFO(dip);
}
}
int
pcie_pm_hold(dev_info_t *dip)
{
pcie_pwr_t *pwr_p;
if (!PCIE_PMINFO(dip) || !(pwr_p = PCIE_NEXUS_PMINFO(dip)) ||
!(PCIE_SUPPORTS_DEVICE_PM(dip)))
return (DDI_SUCCESS);
mutex_enter(&pwr_p->pwr_lock);
ASSERT(pwr_p->pwr_hold >= 0);
PCIE_DBG("%s(%d): pm_hold: incrementing hold \n",
ddi_driver_name(dip), ddi_get_instance(dip));
pwr_p->pwr_hold++;
if (!(pwr_p->pwr_flags & PCIE_PM_BUSY)) {
PCIE_DBG("%s(%d): pm_hold: marking busy\n",
ddi_driver_name(dip), ddi_get_instance(dip));
pwr_p->pwr_flags |= PCIE_PM_BUSY;
(void) pm_busy_component(dip, 0);
}
if (pwr_p->pwr_func_lvl == PM_LEVEL_D0) {
mutex_exit(&pwr_p->pwr_lock);
return (DDI_SUCCESS);
}
mutex_exit(&pwr_p->pwr_lock);
if (pm_raise_power(dip, 0, PM_LEVEL_D0) != DDI_SUCCESS) {
PCIE_DBG("%s(%d): pm_hold: attempt to raise power "
"from %d to %d failed\n", ddi_driver_name(dip),
ddi_get_instance(dip), pwr_p->pwr_func_lvl,
PM_LEVEL_D0);
pcie_pm_release(dip);
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
void
pcie_pm_release(dev_info_t *dip)
{
pcie_pwr_t *pwr_p;
if (!PCIE_PMINFO(dip) || !(pwr_p = PCIE_NEXUS_PMINFO(dip)) ||
!(PCIE_SUPPORTS_DEVICE_PM(dip)))
return;
mutex_enter(&pwr_p->pwr_lock);
pcie_pm_subrelease(dip, pwr_p);
mutex_exit(&pwr_p->pwr_lock);
}
static void
pcie_pm_subrelease(dev_info_t *dip, pcie_pwr_t *pwr_p)
{
int level;
ASSERT(MUTEX_HELD(&pwr_p->pwr_lock));
ASSERT(pwr_p->pwr_hold > 0);
PCIE_DBG("%s(%d): pm_subrelease: decrementing hold \n",
ddi_driver_name(dip), ddi_get_instance(dip));
pwr_p->pwr_hold--;
ASSERT(pwr_p->pwr_hold >= 0);
ASSERT(pwr_p->pwr_flags & PCIE_PM_BUSY);
level = pwr_level_allowed(pwr_p);
if (pwr_p->pwr_hold == 0 && level < pwr_p->pwr_func_lvl) {
PCIE_DBG("%s(%d): pm_subrelease: marking idle \n",
ddi_driver_name(dip), ddi_get_instance(dip));
(void) pm_idle_component(dip, 0);
pwr_p->pwr_flags &= ~PCIE_PM_BUSY;
}
}
int
pcie_pm_add_child(dev_info_t *dip, dev_info_t *cdip)
{
pcie_pwr_t *pwr_p;
if (!PCIE_PMINFO(dip) || !(pwr_p = PCIE_NEXUS_PMINFO(dip)))
return (DDI_SUCCESS);
ASSERT(MUTEX_HELD(&pwr_p->pwr_lock));
ASSERT(pwr_p->pwr_func_lvl == PM_LEVEL_D0);
pcie_add_comps(dip, cdip, pwr_p);
if (!PCIE_SUPPORTS_DEVICE_PM(dip))
return (DDI_SUCCESS);
ASSERT(pwr_p->pwr_hold > 0);
PCIE_DBG("%s(%d): pm_add_child: decrementing hold \n",
ddi_driver_name(dip), ddi_get_instance(dip));
pwr_p->pwr_hold--;
ASSERT(pwr_p->pwr_flags & PCIE_PM_BUSY);
return (DDI_SUCCESS);
}
int
pcie_pm_remove_child(dev_info_t *dip, dev_info_t *cdip)
{
int *counters;
int total;
pcie_pwr_t *pwr_p;
if (!PCIE_PMINFO(dip) || !(pwr_p = PCIE_NEXUS_PMINFO(dip)))
return (DDI_SUCCESS);
counters = pwr_p->pwr_counters;
mutex_enter(&pwr_p->pwr_lock);
pcie_remove_comps(dip, cdip, pwr_p);
if (!PCIE_SUPPORTS_DEVICE_PM(dip)) {
mutex_exit(&pwr_p->pwr_lock);
return (DDI_SUCCESS);
}
total = (counters[PCIE_D0_INDEX] + counters[PCIE_UNKNOWN_INDEX] +
counters[PCIE_D1_INDEX] + counters[PCIE_D2_INDEX] +
counters[PCIE_D3_INDEX]);
if ((pwr_p->pwr_hold == 0) &&
(!total || (pwr_level_allowed(pwr_p) < pwr_p->pwr_func_lvl))) {
if (pwr_p->pwr_flags & PCIE_PM_BUSY) {
PCIE_DBG("%s(%d): pcie_bus_power: marking idle\n",
ddi_driver_name(dip), ddi_get_instance(dip));
(void) pm_idle_component(dip, 0);
pwr_p->pwr_flags &= ~PCIE_PM_BUSY;
}
}
mutex_exit(&pwr_p->pwr_lock);
return (DDI_SUCCESS);
}
boolean_t
pcie_is_pcie(dev_info_t *dip)
{
pcie_bus_t *bus_p = PCIE_DIP2BUS(dip);
ASSERT(bus_p);
return (bus_p->bus_pcie_off != 0);
}
int
pcie_pwr_resume(dev_info_t *dip)
{
dev_info_t *cdip;
pcie_pwr_t *pwr_p = NULL;
#if defined(__x86)
if (dip)
return (DDI_SUCCESS);
#endif
if (PCIE_PMINFO(dip))
pwr_p = PCIE_NEXUS_PMINFO(dip);
if (pwr_p) {
if (PCIE_SUPPORTS_DEVICE_PM(dip)) {
ASSERT(pwr_p->pwr_func_lvl == PM_LEVEL_D0);
(void) pm_raise_power(dip, 0,
pwr_p->pwr_func_lvl);
}
}
for (cdip = ddi_get_child(dip); cdip != NULL;
cdip = ddi_get_next_sibling(cdip)) {
boolean_t is_pcie;
if (i_ddi_node_state(cdip) < DS_INITIALIZED) {
PCIE_DBG("%s(%d): "
"DDI_RESUME: skipping %s%d not in CF1\n",
ddi_driver_name(dip), ddi_get_instance(dip),
ddi_driver_name(cdip), ddi_get_instance(cdip));
continue;
}
if (ddi_prop_exists(DDI_DEV_T_ANY, cdip, DDI_PROP_DONTPASS,
"nexus-saved-config-regs") != 1)
continue;
PCIE_DBG("%s(%d): "
"DDI_RESUME: nexus restoring %s%d config regs\n",
ddi_driver_name(dip), ddi_get_instance(dip),
ddi_driver_name(cdip), ddi_get_instance(cdip));
pcie_clear_errors(cdip);
is_pcie = pcie_is_pcie(cdip);
if (is_pcie)
pcie_disable_errors(cdip);
(void) pci_restore_config_regs(cdip);
if (is_pcie) {
pcie_enable_errors(cdip);
(void) pcie_enable_ce(cdip);
}
if (ndi_prop_remove(DDI_DEV_T_NONE, cdip,
"nexus-saved-config-regs") != DDI_PROP_SUCCESS) {
PCIE_DBG("%s(%d): %s%d can't remove prop %s",
ddi_driver_name(dip), ddi_get_instance(dip),
ddi_driver_name(cdip), ddi_get_instance(cdip),
"nexus-saved-config-regs");
}
}
return (DDI_SUCCESS);
}
int
pcie_pwr_suspend(dev_info_t *dip)
{
dev_info_t *cdip;
int i, *counters;
int *child_counters = NULL;
pcie_pwr_t *pwr_p = NULL;
#if defined(__x86)
if (dip)
return (DDI_SUCCESS);
#endif
if (PCIE_PMINFO(dip))
pwr_p = PCIE_NEXUS_PMINFO(dip);
if (pwr_p) {
mutex_enter(&pwr_p->pwr_lock);
if (PCIE_SUPPORTS_DEVICE_PM(dip) &&
pwr_p->pwr_func_lvl != PM_LEVEL_D0) {
mutex_exit(&pwr_p->pwr_lock);
if (pm_raise_power(dip, 0, PM_LEVEL_D0) !=
DDI_SUCCESS) {
PCIE_DBG("%s(%d): pwr_suspend: attempt "
"to raise power from %d to %d "
"failed\n", ddi_driver_name(dip),
ddi_get_instance(dip), pwr_p->pwr_func_lvl,
PM_LEVEL_D0);
return (DDI_FAILURE);
}
mutex_enter(&pwr_p->pwr_lock);
}
counters = pwr_p->pwr_counters;
for (i = 0; i < PCIE_UNKNOWN_INDEX; i++) {
counters[PCIE_UNKNOWN_INDEX] += counters[i];
counters[i] = 0;
}
mutex_exit(&pwr_p->pwr_lock);
}
for (cdip = ddi_get_child(dip); cdip != NULL;
cdip = ddi_get_next_sibling(cdip)) {
boolean_t is_pcie;
if (i_ddi_node_state(cdip) < DS_INITIALIZED) {
PCIE_DBG("%s(%d): DDI_SUSPEND: skipping "
"%s%d not in CF1\n", ddi_driver_name(dip),
ddi_get_instance(dip), ddi_driver_name(cdip),
ddi_get_instance(cdip));
continue;
}
if (PCIE_PMINFO(cdip) && PCIE_PAR_PMINFO(cdip))
child_counters = PCIE_CHILD_COUNTERS(cdip);
if (child_counters && pwr_p) {
mutex_enter(&pwr_p->pwr_lock);
for (i = 0; i < PCIE_UNKNOWN_INDEX; i++) {
child_counters[PCIE_UNKNOWN_INDEX] +=
child_counters[i];
child_counters[i] = 0;
}
mutex_exit(&pwr_p->pwr_lock);
}
if (ddi_prop_exists(DDI_DEV_T_ANY, cdip, DDI_PROP_DONTPASS,
SAVED_CONFIG_REGS) == 1) {
continue;
}
if (ndi_prop_create_boolean(DDI_DEV_T_NONE, cdip,
"nexus-saved-config-regs") != DDI_PROP_SUCCESS) {
PCIE_DBG("%s(%d): %s%d can't update prop %s",
ddi_driver_name(dip), ddi_get_instance(dip),
ddi_driver_name(cdip), ddi_get_instance(cdip),
"nexus-saved-config-regs");
}
PCIE_DBG("%s(%d): DDI_SUSPEND: saving config space for"
" %s%d\n", ddi_driver_name(dip), ddi_get_instance(dip),
ddi_driver_name(cdip), ddi_get_instance(cdip));
is_pcie = pcie_is_pcie(cdip);
if (is_pcie)
pcie_disable_errors(cdip);
(void) pci_save_config_regs(cdip);
if (is_pcie) {
pcie_enable_errors(cdip);
(void) pcie_enable_ce(cdip);
}
}
return (DDI_SUCCESS);
}
#ifdef DEBUG
typedef struct pcie_buspwr_desc {
pm_bus_power_op_t pwr_op;
char *pwr_desc;
} pcie_buspwr_desc_t;
static pcie_buspwr_desc_t pcie_buspwr_desc[] = {
{BUS_POWER_CHILD_PWRCHG, "CHILD_PWRCHG"},
{BUS_POWER_NEXUS_PWRUP, "NEXUS_PWRUP"},
{BUS_POWER_PRE_NOTIFICATION, "PRE_NOTIFICATION"},
{BUS_POWER_POST_NOTIFICATION, "POST_NOTIFICATION"},
{BUS_POWER_HAS_CHANGED, "HAS_CHANGED"},
{BUS_POWER_NOINVOL, "NOINVOL"},
{-1, NULL}
};
static char *
pcie_decode_pwr_op(pm_bus_power_op_t op)
{
pcie_buspwr_desc_t *descp = pcie_buspwr_desc;
for (; descp->pwr_desc; descp++) {
if (op == descp->pwr_op)
return (descp->pwr_desc);
}
return ("UNKNOWN OP");
}
#endif