xref: /onnv-gate/usr/src/uts/common/io/pciex/pcie_pwr.c (revision 10750:9168799eeedc)
110187SKrishna.Elango@Sun.COM /*
210187SKrishna.Elango@Sun.COM  * CDDL HEADER START
310187SKrishna.Elango@Sun.COM  *
410187SKrishna.Elango@Sun.COM  * The contents of this file are subject to the terms of the
510187SKrishna.Elango@Sun.COM  * Common Development and Distribution License (the "License").
610187SKrishna.Elango@Sun.COM  * You may not use this file except in compliance with the License.
710187SKrishna.Elango@Sun.COM  *
810187SKrishna.Elango@Sun.COM  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
910187SKrishna.Elango@Sun.COM  * or http://www.opensolaris.org/os/licensing.
1010187SKrishna.Elango@Sun.COM  * See the License for the specific language governing permissions
1110187SKrishna.Elango@Sun.COM  * and limitations under the License.
1210187SKrishna.Elango@Sun.COM  *
1310187SKrishna.Elango@Sun.COM  * When distributing Covered Code, include this CDDL HEADER in each
1410187SKrishna.Elango@Sun.COM  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
1510187SKrishna.Elango@Sun.COM  * If applicable, add the following below this CDDL HEADER, with the
1610187SKrishna.Elango@Sun.COM  * fields enclosed by brackets "[]" replaced with your own identifying
1710187SKrishna.Elango@Sun.COM  * information: Portions Copyright [yyyy] [name of copyright owner]
1810187SKrishna.Elango@Sun.COM  *
1910187SKrishna.Elango@Sun.COM  * CDDL HEADER END
2010187SKrishna.Elango@Sun.COM  */
2110187SKrishna.Elango@Sun.COM /*
2210187SKrishna.Elango@Sun.COM  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
2310187SKrishna.Elango@Sun.COM  * Use is subject to license terms.
2410187SKrishna.Elango@Sun.COM  */
2510187SKrishna.Elango@Sun.COM 
2610187SKrishna.Elango@Sun.COM #include <sys/types.h>
2710187SKrishna.Elango@Sun.COM #include <sys/ddi.h>
2810187SKrishna.Elango@Sun.COM #include <sys/kmem.h>
2910187SKrishna.Elango@Sun.COM #include <sys/sysmacros.h>
3010187SKrishna.Elango@Sun.COM #include <sys/sunddi.h>
3110187SKrishna.Elango@Sun.COM #include <sys/sunpm.h>
3210187SKrishna.Elango@Sun.COM #include <sys/epm.h>
3310187SKrishna.Elango@Sun.COM #include <sys/sunndi.h>
3410187SKrishna.Elango@Sun.COM #include <sys/ddi_impldefs.h>
3510187SKrishna.Elango@Sun.COM #include <sys/ddi_implfuncs.h>
3610187SKrishna.Elango@Sun.COM #include <sys/pcie.h>
3710187SKrishna.Elango@Sun.COM #include <sys/pcie_impl.h>
3810187SKrishna.Elango@Sun.COM #include <sys/promif.h>		/* prom_printf */
3910187SKrishna.Elango@Sun.COM #include <sys/pcie_pwr.h>
4010187SKrishna.Elango@Sun.COM 
4110187SKrishna.Elango@Sun.COM /*
4210187SKrishna.Elango@Sun.COM  * This file implements the power management functionality for
4310187SKrishna.Elango@Sun.COM  * pci express switch and pci express-to-pci/pci-x bridge. All the
4410187SKrishna.Elango@Sun.COM  * code in this file is generic and is not specific to a particular chip.
4510187SKrishna.Elango@Sun.COM  * The algorithm, which decides when to go to a lower power is explained
4610187SKrishna.Elango@Sun.COM  * below:
4710187SKrishna.Elango@Sun.COM  *
4810187SKrishna.Elango@Sun.COM  *	1. Initially when no children are attached, the driver is idle from
4910187SKrishna.Elango@Sun.COM  *	PM framework point of view ( PM idle/PM busy).
5010187SKrishna.Elango@Sun.COM  *
5110187SKrishna.Elango@Sun.COM  *	2. Driver is PM busy if either a reference count called pwr_hold is
5210187SKrishna.Elango@Sun.COM  *	greater than zero or driver is already at the lowest possible power
5310187SKrishna.Elango@Sun.COM  *	level. The lowest possible power level for the driver is equal to the
5410187SKrishna.Elango@Sun.COM  *	highest power level among its children. The PM busy condition is
5510187SKrishna.Elango@Sun.COM  *	indicated by PCIE_PM_BUSY bit. At any point, only one pm_busy_component
5610187SKrishna.Elango@Sun.COM  *	call is made for a nexus driver instance.
5710187SKrishna.Elango@Sun.COM  *
5810187SKrishna.Elango@Sun.COM  *	3. Driver is PM idle if the pwr_hold is zero and the lowest
5910187SKrishna.Elango@Sun.COM  *	possible power level is less than the driver's current power level.
6010187SKrishna.Elango@Sun.COM  *	At any point, only one pm_idle_component call is made for a nexus
6110187SKrishna.Elango@Sun.COM  *	driver instance.
6210187SKrishna.Elango@Sun.COM  *
6310187SKrishna.Elango@Sun.COM  *	4. For any events like child attach, it increments pwr_hold and marks
6410187SKrishna.Elango@Sun.COM  *	itslef busy, if it is not already done so. This temporary hold is
6510187SKrishna.Elango@Sun.COM  *	removed when the event is complete.
6610187SKrishna.Elango@Sun.COM  *
6710187SKrishna.Elango@Sun.COM  *	5. Any child's power change requires the parent (this driver) to be
6810187SKrishna.Elango@Sun.COM  *	full power. So it raises its power and increments pwr_hold. It also
6910187SKrishna.Elango@Sun.COM  *	marks itself temporarily busy, if it is not already done. This hold
7010187SKrishna.Elango@Sun.COM  *	is removed when the child power change is complete.
7110187SKrishna.Elango@Sun.COM  *
7210187SKrishna.Elango@Sun.COM  *	6. After each child power change, it evaluates what is the lowest
7310187SKrishna.Elango@Sun.COM  *	possible power level. If the lowest possible power level is less than
7410187SKrishna.Elango@Sun.COM  *	the current power level and pwr_hold is zero, then it marks itself
7510187SKrishna.Elango@Sun.COM  *	idle. The lowest power level is equal or greater than the highest level
7610187SKrishna.Elango@Sun.COM  *	among the children. It keeps track of children's power level by
7710187SKrishna.Elango@Sun.COM  *	using counters.
7810187SKrishna.Elango@Sun.COM  *
7910187SKrishna.Elango@Sun.COM  *	7. Any code e.g., which is accessing the driver's own registers should
8010187SKrishna.Elango@Sun.COM  *	place a temporary hold using pcie_pm_hold.
8110187SKrishna.Elango@Sun.COM  */
8210187SKrishna.Elango@Sun.COM 
8310187SKrishna.Elango@Sun.COM static int pcie_pwr_change(dev_info_t *dip, pcie_pwr_t *pwr_p, int new);
8410187SKrishna.Elango@Sun.COM static void pwr_update_counters(int *countersp, int olevel, int nlevel);
8510187SKrishna.Elango@Sun.COM static int pwr_level_allowed(pcie_pwr_t *pwr_p);
8610187SKrishna.Elango@Sun.COM static void pcie_add_comps(dev_info_t *dip, dev_info_t *cdip,
8710187SKrishna.Elango@Sun.COM     pcie_pwr_t *pwr_p);
8810187SKrishna.Elango@Sun.COM static void pcie_remove_comps(dev_info_t *dip, dev_info_t *cdip,
8910187SKrishna.Elango@Sun.COM     pcie_pwr_t *pwr_p);
9010187SKrishna.Elango@Sun.COM static void pcie_pm_subrelease(dev_info_t *dip, pcie_pwr_t *pwr_p);
9110187SKrishna.Elango@Sun.COM static boolean_t pcie_is_pcie(dev_info_t *dip);
9210187SKrishna.Elango@Sun.COM #ifdef DEBUG
9310187SKrishna.Elango@Sun.COM static char *pcie_decode_pwr_op(pm_bus_power_op_t op);
9410187SKrishna.Elango@Sun.COM #else
9510187SKrishna.Elango@Sun.COM #define	pcie_decode_pwr_op
9610187SKrishna.Elango@Sun.COM #endif
9710187SKrishna.Elango@Sun.COM 
9810187SKrishna.Elango@Sun.COM /*
9910187SKrishna.Elango@Sun.COM  * power entry point.
10010187SKrishna.Elango@Sun.COM  *
10110187SKrishna.Elango@Sun.COM  * This function decides whether the PM request is honorable.
10210187SKrishna.Elango@Sun.COM  * If yes, it then does what's necessary for switch or
10310187SKrishna.Elango@Sun.COM  *    bridge to change its power.
10410187SKrishna.Elango@Sun.COM  */
10510187SKrishna.Elango@Sun.COM /* ARGSUSED */
10610187SKrishna.Elango@Sun.COM int
pcie_power(dev_info_t * dip,int component,int level)10710187SKrishna.Elango@Sun.COM pcie_power(dev_info_t *dip, int component, int level)
10810187SKrishna.Elango@Sun.COM {
10910187SKrishna.Elango@Sun.COM 	pcie_pwr_t *pwr_p = PCIE_NEXUS_PMINFO(dip);
11010187SKrishna.Elango@Sun.COM 	int *counters = pwr_p->pwr_counters;
11110187SKrishna.Elango@Sun.COM 	int pmcaps = pwr_p->pwr_pmcaps;
11210187SKrishna.Elango@Sun.COM 	int ret = DDI_FAILURE;
11310187SKrishna.Elango@Sun.COM 
11410187SKrishna.Elango@Sun.COM #if defined(__i386) || defined(__amd64)
11510187SKrishna.Elango@Sun.COM 	if (dip)
11610187SKrishna.Elango@Sun.COM 		return (DDI_SUCCESS);
11710187SKrishna.Elango@Sun.COM #endif /* defined(__i386) || defined(__amd64) */
11810187SKrishna.Elango@Sun.COM 
11910187SKrishna.Elango@Sun.COM 	ASSERT(level != PM_LEVEL_UNKNOWN);
12010187SKrishna.Elango@Sun.COM 	/* PM should not asking for a level, which is unsupported */
12110187SKrishna.Elango@Sun.COM 	ASSERT(level == PM_LEVEL_D0 || level == PM_LEVEL_D3 ||
12210187SKrishna.Elango@Sun.COM 	    (level == PM_LEVEL_D1 && (pmcaps & PCIE_SUPPORTS_D1)) ||
12310187SKrishna.Elango@Sun.COM 	    (level == PM_LEVEL_D2 && (pmcaps & PCIE_SUPPORTS_D2)));
12410187SKrishna.Elango@Sun.COM 
12510187SKrishna.Elango@Sun.COM 	mutex_enter(&pwr_p->pwr_lock);
126*10750SShesha.Sreenivasamurthy@Sun.COM 	PCIE_DBG("%s(%d): pcie_power: change from %d to %d\n",
127*10750SShesha.Sreenivasamurthy@Sun.COM 	    ddi_driver_name(dip), ddi_get_instance(dip), pwr_p->pwr_func_lvl,
128*10750SShesha.Sreenivasamurthy@Sun.COM 	    level);
12910187SKrishna.Elango@Sun.COM 	if (pwr_p->pwr_func_lvl == level) {
130*10750SShesha.Sreenivasamurthy@Sun.COM 		PCIE_DBG("%s(%d): pcie_power: already at %d\n",
131*10750SShesha.Sreenivasamurthy@Sun.COM 		    ddi_driver_name(dip), ddi_get_instance(dip), level);
13210187SKrishna.Elango@Sun.COM 		ret = DDI_SUCCESS;
13310187SKrishna.Elango@Sun.COM 		goto pcie_pwr_done;
13410187SKrishna.Elango@Sun.COM 	}
13510187SKrishna.Elango@Sun.COM 
13610187SKrishna.Elango@Sun.COM 	if (level < pwr_p->pwr_func_lvl) {
13710187SKrishna.Elango@Sun.COM 		/*
13810187SKrishna.Elango@Sun.COM 		 * Going to lower power. Reject this if we are either busy
13910187SKrishna.Elango@Sun.COM 		 * or there is a hold.
14010187SKrishna.Elango@Sun.COM 		 */
14110187SKrishna.Elango@Sun.COM 		if (pwr_p->pwr_flags & PCIE_PM_BUSY) {
142*10750SShesha.Sreenivasamurthy@Sun.COM 			PCIE_DBG("%s(%d): pcie_power: rejecting change to %d "
143*10750SShesha.Sreenivasamurthy@Sun.COM 			    "as busy\n", ddi_driver_name(dip),
144*10750SShesha.Sreenivasamurthy@Sun.COM 			    ddi_get_instance(dip), level);
14510187SKrishna.Elango@Sun.COM 			goto pcie_pwr_done;
14610187SKrishna.Elango@Sun.COM 		}
14710187SKrishna.Elango@Sun.COM 
14810187SKrishna.Elango@Sun.COM 		/*
14910187SKrishna.Elango@Sun.COM 		 * Now we know that we are neither busy nor there is a hold.
15010187SKrishna.Elango@Sun.COM 		 * At this point none of the children should be at full power.
15110187SKrishna.Elango@Sun.COM 		 * Reject the request if level reqested is lower than the level
15210187SKrishna.Elango@Sun.COM 		 * possible.
15310187SKrishna.Elango@Sun.COM 		 */
15410187SKrishna.Elango@Sun.COM 		ASSERT(!counters[PCIE_D0_INDEX] &&
15510187SKrishna.Elango@Sun.COM 		    !counters[PCIE_UNKNOWN_INDEX]);
15610187SKrishna.Elango@Sun.COM 		if (level < pwr_level_allowed(pwr_p)) {
157*10750SShesha.Sreenivasamurthy@Sun.COM 			PCIE_DBG("%s(%d): pcie_power: rejecting level %d as"
158*10750SShesha.Sreenivasamurthy@Sun.COM 			    " %d is the lowest possible\n",
159*10750SShesha.Sreenivasamurthy@Sun.COM 			    ddi_driver_name(dip), ddi_get_instance(dip), level,
16010187SKrishna.Elango@Sun.COM 			    pwr_level_allowed(pwr_p));
16110187SKrishna.Elango@Sun.COM 			goto pcie_pwr_done;
16210187SKrishna.Elango@Sun.COM 		}
16310187SKrishna.Elango@Sun.COM 	}
16410187SKrishna.Elango@Sun.COM 
16510187SKrishna.Elango@Sun.COM 	if (pcie_pwr_change(dip, pwr_p, level) != DDI_SUCCESS) {
166*10750SShesha.Sreenivasamurthy@Sun.COM 		PCIE_DBG("%s(%d): pcie_power: attempt to change to %d "
167*10750SShesha.Sreenivasamurthy@Sun.COM 		    " failed \n", ddi_driver_name(dip), ddi_get_instance(dip),
168*10750SShesha.Sreenivasamurthy@Sun.COM 		    level);
16910187SKrishna.Elango@Sun.COM 		goto pcie_pwr_done;
17010187SKrishna.Elango@Sun.COM 	}
17110187SKrishna.Elango@Sun.COM 	pwr_p->pwr_func_lvl = level;
172*10750SShesha.Sreenivasamurthy@Sun.COM 	PCIE_DBG("%s(%d): pcie_power: level changed to %d \n",
173*10750SShesha.Sreenivasamurthy@Sun.COM 	    ddi_driver_name(dip), ddi_get_instance(dip), level);
17410187SKrishna.Elango@Sun.COM 	ret = DDI_SUCCESS;
17510187SKrishna.Elango@Sun.COM 
17610187SKrishna.Elango@Sun.COM pcie_pwr_done:
17710187SKrishna.Elango@Sun.COM 	mutex_exit(&pwr_p->pwr_lock);
17810187SKrishna.Elango@Sun.COM 	return (ret);
17910187SKrishna.Elango@Sun.COM }
18010187SKrishna.Elango@Sun.COM 
18110187SKrishna.Elango@Sun.COM /*
18210187SKrishna.Elango@Sun.COM  * Called by pcie_power() only. Caller holds the pwr_lock.
18310187SKrishna.Elango@Sun.COM  *
18410187SKrishna.Elango@Sun.COM  * dip - dev_info pointer
18510187SKrishna.Elango@Sun.COM  * pwr_p - pm info for the node.
18610187SKrishna.Elango@Sun.COM  * new     - new level
18710187SKrishna.Elango@Sun.COM  */
18810187SKrishna.Elango@Sun.COM static int
pcie_pwr_change(dev_info_t * dip,pcie_pwr_t * pwr_p,int new)18910187SKrishna.Elango@Sun.COM pcie_pwr_change(dev_info_t *dip, pcie_pwr_t *pwr_p, int new)
19010187SKrishna.Elango@Sun.COM {
19110187SKrishna.Elango@Sun.COM 	uint16_t pmcsr;
19210187SKrishna.Elango@Sun.COM 
19310187SKrishna.Elango@Sun.COM 	ASSERT(MUTEX_HELD(&pwr_p->pwr_lock));
19410187SKrishna.Elango@Sun.COM 	ASSERT(new != pwr_p->pwr_func_lvl);
19510187SKrishna.Elango@Sun.COM 	pmcsr = pci_config_get16(pwr_p->pwr_conf_hdl, pwr_p->pwr_pmcsr_offset);
19610187SKrishna.Elango@Sun.COM 	pmcsr &= ~PCI_PMCSR_STATE_MASK;
19710187SKrishna.Elango@Sun.COM 	switch (new) {
19810187SKrishna.Elango@Sun.COM 	case PM_LEVEL_D0:
19910187SKrishna.Elango@Sun.COM 		pmcsr |= PCI_PMCSR_D0;
20010187SKrishna.Elango@Sun.COM 		break;
20110187SKrishna.Elango@Sun.COM 
20210187SKrishna.Elango@Sun.COM 	case PM_LEVEL_D1:
20310187SKrishna.Elango@Sun.COM 		pmcsr |= PCI_PMCSR_D1;
20410187SKrishna.Elango@Sun.COM 		break;
20510187SKrishna.Elango@Sun.COM 
20610187SKrishna.Elango@Sun.COM 	case PM_LEVEL_D2:
20710187SKrishna.Elango@Sun.COM 		pmcsr |= PCI_PMCSR_D2;
20810187SKrishna.Elango@Sun.COM 		break;
20910187SKrishna.Elango@Sun.COM 
21010187SKrishna.Elango@Sun.COM 	case PM_LEVEL_D3:
21110187SKrishna.Elango@Sun.COM 		pmcsr |= PCI_PMCSR_D3HOT;
21210187SKrishna.Elango@Sun.COM 		break;
21310187SKrishna.Elango@Sun.COM 
21410187SKrishna.Elango@Sun.COM 	default:
21510187SKrishna.Elango@Sun.COM 		ASSERT(0);
21610187SKrishna.Elango@Sun.COM 		break;
21710187SKrishna.Elango@Sun.COM 	}
21810187SKrishna.Elango@Sun.COM 	/* Save config space, if going to D3 */
21910187SKrishna.Elango@Sun.COM 	if (new == PM_LEVEL_D3) {
220*10750SShesha.Sreenivasamurthy@Sun.COM 		PCIE_DBG("%s(%d): pwr_change: saving config space regs\n",
221*10750SShesha.Sreenivasamurthy@Sun.COM 		    ddi_driver_name(dip), ddi_get_instance(dip));
22210187SKrishna.Elango@Sun.COM 		if (pci_save_config_regs(dip) != DDI_SUCCESS) {
223*10750SShesha.Sreenivasamurthy@Sun.COM 			PCIE_DBG("%s(%d): pcie_pwr_change: failed to save "
224*10750SShesha.Sreenivasamurthy@Sun.COM 			    "config space regs\n", ddi_driver_name(dip),
225*10750SShesha.Sreenivasamurthy@Sun.COM 			    ddi_get_instance(dip));
22610187SKrishna.Elango@Sun.COM 			return (DDI_FAILURE);
22710187SKrishna.Elango@Sun.COM 		}
22810187SKrishna.Elango@Sun.COM 	}
22910187SKrishna.Elango@Sun.COM 
23010187SKrishna.Elango@Sun.COM 	pci_config_put16(pwr_p->pwr_conf_hdl, pwr_p->pwr_pmcsr_offset, pmcsr);
23110187SKrishna.Elango@Sun.COM 
23210187SKrishna.Elango@Sun.COM 	/*
23310187SKrishna.Elango@Sun.COM 	 * TBD: Taken from pci_pci driver. Is this required?
23410187SKrishna.Elango@Sun.COM 	 * No bus transactions should occur without waiting for
23510187SKrishna.Elango@Sun.COM 	 * settle time specified in PCI PM spec rev 2.1 sec 5.6.1
23610187SKrishna.Elango@Sun.COM 	 * To make things simple, just use the max time specified for
23710187SKrishna.Elango@Sun.COM 	 * all state transitions.
23810187SKrishna.Elango@Sun.COM 	 */
23910187SKrishna.Elango@Sun.COM 	delay(drv_usectohz(PCI_CLK_SETTLE_TIME));
24010187SKrishna.Elango@Sun.COM 
24110187SKrishna.Elango@Sun.COM 	/*
24210187SKrishna.Elango@Sun.COM 	 * Restore config space if coming out of D3
24310187SKrishna.Elango@Sun.COM 	 */
24410187SKrishna.Elango@Sun.COM 	if (pwr_p->pwr_func_lvl == PM_LEVEL_D3) {
245*10750SShesha.Sreenivasamurthy@Sun.COM 		PCIE_DBG("%s(%d): pcie_pwr_change: restoring config space\n",
246*10750SShesha.Sreenivasamurthy@Sun.COM 		    ddi_driver_name(dip), ddi_get_instance(dip));
24710187SKrishna.Elango@Sun.COM 		if (pci_restore_config_regs(dip) != DDI_SUCCESS) {
248*10750SShesha.Sreenivasamurthy@Sun.COM 			PCIE_DBG("%s(%d): pcie_pwr_change: failed to restore "
249*10750SShesha.Sreenivasamurthy@Sun.COM 			    "config space regs\n", ddi_driver_name(dip),
250*10750SShesha.Sreenivasamurthy@Sun.COM 			    ddi_get_instance(dip));
25110187SKrishna.Elango@Sun.COM 			return (DDI_FAILURE);
25210187SKrishna.Elango@Sun.COM 		}
25310187SKrishna.Elango@Sun.COM 	}
25410187SKrishna.Elango@Sun.COM 	return (DDI_SUCCESS);
25510187SKrishna.Elango@Sun.COM }
25610187SKrishna.Elango@Sun.COM 
25710187SKrishna.Elango@Sun.COM /*
25810187SKrishna.Elango@Sun.COM  * bus_ctlops.bus_power function.
25910187SKrishna.Elango@Sun.COM  *
26010187SKrishna.Elango@Sun.COM  * This function handles PRE_ POST_ change notifications, sent by
26110187SKrishna.Elango@Sun.COM  * PM framework related to child's power level change. It marks itself
26210187SKrishna.Elango@Sun.COM  * idle or busy based on the children's power level.
26310187SKrishna.Elango@Sun.COM  */
26410187SKrishna.Elango@Sun.COM int
pcie_bus_power(dev_info_t * dip,void * impl_arg,pm_bus_power_op_t op,void * arg,void * result)26510187SKrishna.Elango@Sun.COM pcie_bus_power(dev_info_t *dip, void *impl_arg, pm_bus_power_op_t op,
26610187SKrishna.Elango@Sun.COM     void *arg, void *result)
26710187SKrishna.Elango@Sun.COM {
26810187SKrishna.Elango@Sun.COM 	pcie_pwr_t *pwr_p = PCIE_NEXUS_PMINFO(dip);
26910187SKrishna.Elango@Sun.COM 	int *counters = pwr_p->pwr_counters; /* nexus counters */
27010187SKrishna.Elango@Sun.COM 	int *child_counters; /* per child dip counters */
27110187SKrishna.Elango@Sun.COM 	pm_bp_child_pwrchg_t *bpc;
27210187SKrishna.Elango@Sun.COM 	pm_bp_has_changed_t *bphc;
27310187SKrishna.Elango@Sun.COM 	dev_info_t *cdip;
27410187SKrishna.Elango@Sun.COM 	int new_level;
27510187SKrishna.Elango@Sun.COM 	int old_level;
27610187SKrishna.Elango@Sun.COM 	int rv = DDI_SUCCESS;
27710187SKrishna.Elango@Sun.COM 	int level_allowed, comp;
27810187SKrishna.Elango@Sun.COM 
27910187SKrishna.Elango@Sun.COM #if defined(__i386) || defined(__amd64)
28010187SKrishna.Elango@Sun.COM 	if (dip)
28110187SKrishna.Elango@Sun.COM 		return (DDI_SUCCESS);
28210187SKrishna.Elango@Sun.COM #endif /* defined(__i386) || defined(__amd64) */
28310187SKrishna.Elango@Sun.COM 
28410187SKrishna.Elango@Sun.COM 	switch (op) {
28510187SKrishna.Elango@Sun.COM 	case BUS_POWER_PRE_NOTIFICATION:
28610187SKrishna.Elango@Sun.COM 	case BUS_POWER_POST_NOTIFICATION:
28710187SKrishna.Elango@Sun.COM 		bpc = (pm_bp_child_pwrchg_t *)arg;
28810187SKrishna.Elango@Sun.COM 		cdip = bpc->bpc_dip;
28910187SKrishna.Elango@Sun.COM 		new_level = bpc->bpc_nlevel;
29010187SKrishna.Elango@Sun.COM 		old_level = bpc->bpc_olevel;
29110187SKrishna.Elango@Sun.COM 		comp = bpc->bpc_comp;
29210187SKrishna.Elango@Sun.COM 		break;
29310187SKrishna.Elango@Sun.COM 
29410187SKrishna.Elango@Sun.COM 	case BUS_POWER_HAS_CHANGED:
29510187SKrishna.Elango@Sun.COM 		bphc = (pm_bp_has_changed_t *)arg;
29610187SKrishna.Elango@Sun.COM 		cdip = bphc->bphc_dip;
29710187SKrishna.Elango@Sun.COM 		new_level = bphc->bphc_nlevel;
29810187SKrishna.Elango@Sun.COM 		old_level = bphc->bphc_olevel;
29910187SKrishna.Elango@Sun.COM 		comp = bphc->bphc_comp;
30010187SKrishna.Elango@Sun.COM 		break;
30110187SKrishna.Elango@Sun.COM 
30210187SKrishna.Elango@Sun.COM 	default:
30310187SKrishna.Elango@Sun.COM 		break;
30410187SKrishna.Elango@Sun.COM 
30510187SKrishna.Elango@Sun.COM 	}
30610187SKrishna.Elango@Sun.COM 
30710187SKrishna.Elango@Sun.COM 	ASSERT(pwr_p);
30810187SKrishna.Elango@Sun.COM 	mutex_enter(&pwr_p->pwr_lock);
30910187SKrishna.Elango@Sun.COM 	switch (op) {
31010187SKrishna.Elango@Sun.COM 	case BUS_POWER_PRE_NOTIFICATION:
311*10750SShesha.Sreenivasamurthy@Sun.COM 		PCIE_DBG("%s(%d): pcie_bus_power: %s@%d op %s %d->%d\n",
312*10750SShesha.Sreenivasamurthy@Sun.COM 		    ddi_driver_name(dip), ddi_get_instance(dip),
31310187SKrishna.Elango@Sun.COM 		    ddi_driver_name(cdip), ddi_get_instance(cdip),
31410187SKrishna.Elango@Sun.COM 		    pcie_decode_pwr_op(op), old_level, new_level);
31510187SKrishna.Elango@Sun.COM 		/*
31610187SKrishna.Elango@Sun.COM 		 * If the nexus doesn't want the child to go into
31710187SKrishna.Elango@Sun.COM 		 * non-D0 state, mark the child busy. This way PM
31810187SKrishna.Elango@Sun.COM 		 * framework will never try to lower the child's power.
31910187SKrishna.Elango@Sun.COM 		 * In case of pm_lower_power, marking busy won't help.
32010187SKrishna.Elango@Sun.COM 		 * So we need to specifically reject the attempt to
32110187SKrishna.Elango@Sun.COM 		 * go to non-D0 state.
32210187SKrishna.Elango@Sun.COM 		 */
32310187SKrishna.Elango@Sun.COM 		if (pwr_p->pwr_flags & PCIE_NO_CHILD_PM) {
32410187SKrishna.Elango@Sun.COM 			if (!PCIE_IS_COMPS_COUNTED(cdip)) {
325*10750SShesha.Sreenivasamurthy@Sun.COM 				PCIE_DBG("%s(%d): pcie_bus_power: marking "
326*10750SShesha.Sreenivasamurthy@Sun.COM 				    "child busy to disable pm \n",
327*10750SShesha.Sreenivasamurthy@Sun.COM 				    ddi_driver_name(dip),
328*10750SShesha.Sreenivasamurthy@Sun.COM 				    ddi_get_instance(dip));
32910187SKrishna.Elango@Sun.COM 				(void) pm_busy_component(cdip, 0);
33010187SKrishna.Elango@Sun.COM 			}
33110187SKrishna.Elango@Sun.COM 			if (new_level < PM_LEVEL_D0 && !comp) {
332*10750SShesha.Sreenivasamurthy@Sun.COM 				PCIE_DBG("%s(%d): pcie_bus_power: rejecting "
333*10750SShesha.Sreenivasamurthy@Sun.COM 				    "child's attempt to go to %d\n",
334*10750SShesha.Sreenivasamurthy@Sun.COM 				    ddi_driver_name(dip), ddi_get_instance(dip),
335*10750SShesha.Sreenivasamurthy@Sun.COM 				    new_level);
33610187SKrishna.Elango@Sun.COM 				rv = DDI_FAILURE;
33710187SKrishna.Elango@Sun.COM 			}
33810187SKrishna.Elango@Sun.COM 		}
33910187SKrishna.Elango@Sun.COM 		mutex_exit(&pwr_p->pwr_lock);
34010187SKrishna.Elango@Sun.COM 		if (rv == DDI_SUCCESS)
34110187SKrishna.Elango@Sun.COM 			rv = pcie_pm_hold(dip);
34210187SKrishna.Elango@Sun.COM 		return (rv);
34310187SKrishna.Elango@Sun.COM 
34410187SKrishna.Elango@Sun.COM 	case BUS_POWER_HAS_CHANGED:
34510187SKrishna.Elango@Sun.COM 	case BUS_POWER_POST_NOTIFICATION:
346*10750SShesha.Sreenivasamurthy@Sun.COM 		PCIE_DBG("%s(%d): pcie_bus_power: %s@%d op %s %d->%d\n",
347*10750SShesha.Sreenivasamurthy@Sun.COM 		    ddi_driver_name(dip), ddi_get_instance(dip),
34810187SKrishna.Elango@Sun.COM 		    ddi_driver_name(cdip), ddi_get_instance(cdip),
34910187SKrishna.Elango@Sun.COM 		    pcie_decode_pwr_op(op), old_level, new_level);
35010187SKrishna.Elango@Sun.COM 		/*
35110187SKrishna.Elango@Sun.COM 		 * Child device power changed
35210187SKrishna.Elango@Sun.COM 		 * If pm components of this child aren't accounted for
35310187SKrishna.Elango@Sun.COM 		 * then add the components to the counters. This can't
35410187SKrishna.Elango@Sun.COM 		 * be done in POST_ATTACH ctlop as pm info isn't created
35510187SKrishna.Elango@Sun.COM 		 * by then. Also because a driver can make a pm call during
35610187SKrishna.Elango@Sun.COM 		 * the attach.
35710187SKrishna.Elango@Sun.COM 		 */
35810187SKrishna.Elango@Sun.COM 		if (!PCIE_IS_COMPS_COUNTED(cdip)) {
35910187SKrishna.Elango@Sun.COM 			(void) pcie_pm_add_child(dip, cdip);
36010187SKrishna.Elango@Sun.COM 			if ((pwr_p->pwr_flags & PCIE_NO_CHILD_PM) &&
36110187SKrishna.Elango@Sun.COM 			    (op == BUS_POWER_HAS_CHANGED)) {
362*10750SShesha.Sreenivasamurthy@Sun.COM 				PCIE_DBG("%s(%d): pcie_bus_power: marking "
363*10750SShesha.Sreenivasamurthy@Sun.COM 				    "child busy to disable pm \n",
364*10750SShesha.Sreenivasamurthy@Sun.COM 				    ddi_driver_name(dip),
365*10750SShesha.Sreenivasamurthy@Sun.COM 				    ddi_get_instance(dip));
36610187SKrishna.Elango@Sun.COM 				(void) pm_busy_component(cdip, 0);
36710187SKrishna.Elango@Sun.COM 				/*
36810187SKrishna.Elango@Sun.COM 				 * If the driver has already changed to lower
36910187SKrishna.Elango@Sun.COM 				 * power(pm_power_has_changed) on its own,
37010187SKrishna.Elango@Sun.COM 				 * there is nothing we can do other than
37110187SKrishna.Elango@Sun.COM 				 * logging the warning message on the console.
37210187SKrishna.Elango@Sun.COM 				 */
37310187SKrishna.Elango@Sun.COM 				if (new_level < PM_LEVEL_D0)
37410187SKrishna.Elango@Sun.COM 					cmn_err(CE_WARN, "!Downstream device "
37510187SKrishna.Elango@Sun.COM 					    "%s@%d went to non-D0 state: "
37610187SKrishna.Elango@Sun.COM 					    "possible loss of link\n",
37710187SKrishna.Elango@Sun.COM 					    ddi_driver_name(cdip),
37810187SKrishna.Elango@Sun.COM 					    ddi_get_instance(cdip));
37910187SKrishna.Elango@Sun.COM 			}
38010187SKrishna.Elango@Sun.COM 		}
38110187SKrishna.Elango@Sun.COM 
38210187SKrishna.Elango@Sun.COM 
38310187SKrishna.Elango@Sun.COM 		/*
38410187SKrishna.Elango@Sun.COM 		 * If it is POST and device PM is supported, release the
38510187SKrishna.Elango@Sun.COM 		 * hold done in PRE.
38610187SKrishna.Elango@Sun.COM 		 */
38710187SKrishna.Elango@Sun.COM 		if (op == BUS_POWER_POST_NOTIFICATION &&
38810187SKrishna.Elango@Sun.COM 		    PCIE_SUPPORTS_DEVICE_PM(dip)) {
38910187SKrishna.Elango@Sun.COM 			pcie_pm_subrelease(dip, pwr_p);
39010187SKrishna.Elango@Sun.COM 		}
39110187SKrishna.Elango@Sun.COM 
39210187SKrishna.Elango@Sun.COM 		if (*((int *)result) == DDI_FAILURE) {
393*10750SShesha.Sreenivasamurthy@Sun.COM 			PCIE_DBG("%s(%d): pcie_bus_power: change for %s%d "
394*10750SShesha.Sreenivasamurthy@Sun.COM 			    "failed\n", ddi_driver_name(dip),
395*10750SShesha.Sreenivasamurthy@Sun.COM 			    ddi_get_instance(dip), ddi_driver_name(cdip),
396*10750SShesha.Sreenivasamurthy@Sun.COM 			    ddi_get_instance(cdip));
39710187SKrishna.Elango@Sun.COM 			break;
39810187SKrishna.Elango@Sun.COM 		}
39910187SKrishna.Elango@Sun.COM 		/* Modify counters appropriately */
40010187SKrishna.Elango@Sun.COM 		pwr_update_counters(counters, old_level, new_level);
40110187SKrishna.Elango@Sun.COM 
40210187SKrishna.Elango@Sun.COM 		child_counters = PCIE_CHILD_COUNTERS(cdip);
40310187SKrishna.Elango@Sun.COM 		pwr_update_counters(child_counters, old_level, new_level);
40410187SKrishna.Elango@Sun.COM 
40510187SKrishna.Elango@Sun.COM 		/* If no device PM, return */
40610187SKrishna.Elango@Sun.COM 		if (!PCIE_SUPPORTS_DEVICE_PM(dip))
40710187SKrishna.Elango@Sun.COM 			break;
40810187SKrishna.Elango@Sun.COM 
40910187SKrishna.Elango@Sun.COM 		level_allowed = pwr_level_allowed(pwr_p);
41010187SKrishna.Elango@Sun.COM 		/*
41110187SKrishna.Elango@Sun.COM 		 * Check conditions for marking busy
41210187SKrishna.Elango@Sun.COM 		 * Check the flag to set this busy only once for multiple
41310187SKrishna.Elango@Sun.COM 		 * busy conditions. Mark busy if our current lowest possible
41410187SKrishna.Elango@Sun.COM 		 * is equal or greater to the current level.
41510187SKrishna.Elango@Sun.COM 		 */
41610187SKrishna.Elango@Sun.COM 		if (level_allowed >= pwr_p->pwr_func_lvl &&
41710187SKrishna.Elango@Sun.COM 		    !(pwr_p->pwr_flags & PCIE_PM_BUSY)) {
418*10750SShesha.Sreenivasamurthy@Sun.COM 			PCIE_DBG("%s(%d): pcie_bus_power: marking busy\n",
419*10750SShesha.Sreenivasamurthy@Sun.COM 			    ddi_driver_name(dip), ddi_get_instance(dip));
42010187SKrishna.Elango@Sun.COM 			(void) pm_busy_component(dip, 0);
42110187SKrishna.Elango@Sun.COM 			pwr_p->pwr_flags |= PCIE_PM_BUSY;
42210187SKrishna.Elango@Sun.COM 			break;
42310187SKrishna.Elango@Sun.COM 		}
42410187SKrishna.Elango@Sun.COM 		/*
42510187SKrishna.Elango@Sun.COM 		 * Check conditions for marking idle.
42610187SKrishna.Elango@Sun.COM 		 * If our lowest possible level is less than our current
42710187SKrishna.Elango@Sun.COM 		 * level mark idle. Mark idle only if it is not already done.
42810187SKrishna.Elango@Sun.COM 		 */
42910187SKrishna.Elango@Sun.COM 		if ((level_allowed < pwr_p->pwr_func_lvl) &&
43010187SKrishna.Elango@Sun.COM 		    (pwr_p->pwr_hold == 0) &&
43110187SKrishna.Elango@Sun.COM 		    (pwr_p->pwr_flags & PCIE_PM_BUSY)) {
43210187SKrishna.Elango@Sun.COM 			/*
43310187SKrishna.Elango@Sun.COM 			 * For pci express, we should check here whether
43410187SKrishna.Elango@Sun.COM 			 * the link is in L1 state or not.
43510187SKrishna.Elango@Sun.COM 			 */
436*10750SShesha.Sreenivasamurthy@Sun.COM 			PCIE_DBG("%s(%d): pcie_bus_power: marking idle\n",
437*10750SShesha.Sreenivasamurthy@Sun.COM 			    ddi_driver_name(dip), ddi_get_instance(dip));
43810187SKrishna.Elango@Sun.COM 			(void) pm_idle_component(dip, 0);
43910187SKrishna.Elango@Sun.COM 			pwr_p->pwr_flags &= ~PCIE_PM_BUSY;
44010187SKrishna.Elango@Sun.COM 			break;
44110187SKrishna.Elango@Sun.COM 		}
44210187SKrishna.Elango@Sun.COM 		break;
44310187SKrishna.Elango@Sun.COM 
44410187SKrishna.Elango@Sun.COM 	default:
44510187SKrishna.Elango@Sun.COM 		mutex_exit(&pwr_p->pwr_lock);
44610187SKrishna.Elango@Sun.COM 		return (pm_busop_bus_power(dip, impl_arg, op, arg, result));
44710187SKrishna.Elango@Sun.COM 	}
44810187SKrishna.Elango@Sun.COM 	mutex_exit(&pwr_p->pwr_lock);
44910187SKrishna.Elango@Sun.COM 	return (rv);
45010187SKrishna.Elango@Sun.COM }
45110187SKrishna.Elango@Sun.COM 
45210187SKrishna.Elango@Sun.COM /*
45310187SKrishna.Elango@Sun.COM  * Decrement the count of children at olevel by one and increment
45410187SKrishna.Elango@Sun.COM  * count of children at nlevel by one.
45510187SKrishna.Elango@Sun.COM  */
45610187SKrishna.Elango@Sun.COM static void
pwr_update_counters(int * countersp,int olevel,int nlevel)45710187SKrishna.Elango@Sun.COM pwr_update_counters(int *countersp, int olevel, int nlevel)
45810187SKrishna.Elango@Sun.COM {
45910187SKrishna.Elango@Sun.COM 	uint32_t	index;
46010187SKrishna.Elango@Sun.COM 
46110187SKrishna.Elango@Sun.COM 	ASSERT(olevel >= PM_LEVEL_UNKNOWN && olevel <= PM_LEVEL_D0);
46210187SKrishna.Elango@Sun.COM 	ASSERT(nlevel >= PM_LEVEL_UNKNOWN && nlevel <= PM_LEVEL_D0);
46310187SKrishna.Elango@Sun.COM 
46410187SKrishna.Elango@Sun.COM 	index = (olevel == PM_LEVEL_UNKNOWN ? PCIE_UNKNOWN_INDEX : olevel);
46510187SKrishna.Elango@Sun.COM 	countersp[index]--;
46610187SKrishna.Elango@Sun.COM 	index = (nlevel == PM_LEVEL_UNKNOWN ? PCIE_UNKNOWN_INDEX : nlevel);
46710187SKrishna.Elango@Sun.COM 	countersp[index]++;
46810187SKrishna.Elango@Sun.COM }
46910187SKrishna.Elango@Sun.COM 
47010187SKrishna.Elango@Sun.COM /*
47110187SKrishna.Elango@Sun.COM  * Returns the lowest possible power level allowed for nexus
47210187SKrishna.Elango@Sun.COM  * based on children's power level. Lowest possible level is
47310187SKrishna.Elango@Sun.COM  * equal to the highest level among the children. It also checks
47410187SKrishna.Elango@Sun.COM  * for the supported level
47510187SKrishna.Elango@Sun.COM  * UNKNOWN = D0 > D1 > D2 > D3
47610187SKrishna.Elango@Sun.COM  */
47710187SKrishna.Elango@Sun.COM static int
pwr_level_allowed(pcie_pwr_t * pwr_p)47810187SKrishna.Elango@Sun.COM pwr_level_allowed(pcie_pwr_t *pwr_p)
47910187SKrishna.Elango@Sun.COM {
48010187SKrishna.Elango@Sun.COM 	int *counters = pwr_p->pwr_counters;
48110187SKrishna.Elango@Sun.COM 	int i, j;
48210187SKrishna.Elango@Sun.COM 
48310187SKrishna.Elango@Sun.COM 	ASSERT(MUTEX_HELD(&pwr_p->pwr_lock));
48410187SKrishna.Elango@Sun.COM 	/*
48510187SKrishna.Elango@Sun.COM 	 * Search from UNKNOWN to D2. unknown is same as D0.
48610187SKrishna.Elango@Sun.COM 	 * find the highest level among the children. If that
48710187SKrishna.Elango@Sun.COM 	 * level is supported, return that level. If not,
48810187SKrishna.Elango@Sun.COM 	 * find the next higher supported level and return that
48910187SKrishna.Elango@Sun.COM 	 * level. For example, if the D1 is the highest among
49010187SKrishna.Elango@Sun.COM 	 * children and if D1 isn't supported return D0 as the
49110187SKrishna.Elango@Sun.COM 	 * lowest possible level. We don't need to look at D3
49210187SKrishna.Elango@Sun.COM 	 * as that is the default lowest level and it is always
49310187SKrishna.Elango@Sun.COM 	 * supported.
49410187SKrishna.Elango@Sun.COM 	 */
49510187SKrishna.Elango@Sun.COM 	for (i = PCIE_UNKNOWN_INDEX; i > 0; i--) {
49610187SKrishna.Elango@Sun.COM 		if (counters[i]) {
49710187SKrishna.Elango@Sun.COM 			if (i == PCIE_UNKNOWN_INDEX)
49810187SKrishna.Elango@Sun.COM 				return (PM_LEVEL_D0);
49910187SKrishna.Elango@Sun.COM 			/*
50010187SKrishna.Elango@Sun.COM 			 * i is the highest level among children. If this is
50110187SKrishna.Elango@Sun.COM 			 * supported, return i.
50210187SKrishna.Elango@Sun.COM 			 */
50310187SKrishna.Elango@Sun.COM 			if (PCIE_LEVEL_SUPPORTED(pwr_p->pwr_pmcaps, i))
50410187SKrishna.Elango@Sun.COM 				return (i);
50510187SKrishna.Elango@Sun.COM 			/* find the next higher supported level */
50610187SKrishna.Elango@Sun.COM 			for (j = i + 1; j <= PCIE_D0_INDEX; j++) {
50710187SKrishna.Elango@Sun.COM 				if (PCIE_LEVEL_SUPPORTED(pwr_p->pwr_pmcaps, j))
50810187SKrishna.Elango@Sun.COM 					return (j);
50910187SKrishna.Elango@Sun.COM 			}
51010187SKrishna.Elango@Sun.COM 		}
51110187SKrishna.Elango@Sun.COM 	}
51210187SKrishna.Elango@Sun.COM 
51310187SKrishna.Elango@Sun.COM 	return (PM_LEVEL_D3);
51410187SKrishna.Elango@Sun.COM }
51510187SKrishna.Elango@Sun.COM 
51610187SKrishna.Elango@Sun.COM /*
51710187SKrishna.Elango@Sun.COM  * Update the counters with number pm components of the child
51810187SKrishna.Elango@Sun.COM  * all components are assumed to be at UNKNOWN level.
51910187SKrishna.Elango@Sun.COM  */
52010187SKrishna.Elango@Sun.COM static void
pcie_add_comps(dev_info_t * dip,dev_info_t * cdip,pcie_pwr_t * pwr_p)52110187SKrishna.Elango@Sun.COM pcie_add_comps(dev_info_t *dip, dev_info_t *cdip, pcie_pwr_t *pwr_p)
52210187SKrishna.Elango@Sun.COM {
52310187SKrishna.Elango@Sun.COM 	int comps = PM_NUMCMPTS(cdip);
52410187SKrishna.Elango@Sun.COM 	pcie_pm_t *pcie_pm_p;
52510187SKrishna.Elango@Sun.COM 	pcie_pwr_child_t *cpwr_p;
52610187SKrishna.Elango@Sun.COM 
52710187SKrishna.Elango@Sun.COM 	ASSERT(MUTEX_HELD(&pwr_p->pwr_lock));
52810187SKrishna.Elango@Sun.COM 	if (!comps)
52910187SKrishna.Elango@Sun.COM 		return;
53010187SKrishna.Elango@Sun.COM 
531*10750SShesha.Sreenivasamurthy@Sun.COM 	PCIE_DBG("%s(%d): pcie_add_comps: unknown level counter incremented "
53210187SKrishna.Elango@Sun.COM 	    "from %d by %d because of %s@%d\n",
533*10750SShesha.Sreenivasamurthy@Sun.COM 	    ddi_driver_name(dip), ddi_get_instance(dip),
53410187SKrishna.Elango@Sun.COM 	    (pwr_p->pwr_counters)[PCIE_UNKNOWN_INDEX], comps,
53510187SKrishna.Elango@Sun.COM 	    ddi_driver_name(cdip), ddi_get_instance(cdip));
53610187SKrishna.Elango@Sun.COM 	(pwr_p->pwr_counters)[PCIE_UNKNOWN_INDEX] += comps;
53710187SKrishna.Elango@Sun.COM 	/*
53810187SKrishna.Elango@Sun.COM 	 * Allocate counters per child. This is a part of pcie
53910187SKrishna.Elango@Sun.COM 	 * pm info. If there is no pcie pm info, allocate it here.
54010187SKrishna.Elango@Sun.COM 	 * pcie pm info might already be there for pci express nexus
54110187SKrishna.Elango@Sun.COM 	 * driver e.g. pcieb. For all leaf nodes, it is allocated here.
54210187SKrishna.Elango@Sun.COM 	 */
54310187SKrishna.Elango@Sun.COM 	if ((pcie_pm_p = PCIE_PMINFO(cdip)) == NULL) {
54410187SKrishna.Elango@Sun.COM 		pcie_pm_p = (pcie_pm_t *)kmem_zalloc(
54510187SKrishna.Elango@Sun.COM 		    sizeof (pcie_pm_t), KM_SLEEP);
54610187SKrishna.Elango@Sun.COM 		PCIE_SET_PMINFO(cdip, pcie_pm_p);
54710187SKrishna.Elango@Sun.COM 	}
54810187SKrishna.Elango@Sun.COM 	cpwr_p = (pcie_pwr_child_t *)kmem_zalloc(sizeof (pcie_pwr_child_t),
54910187SKrishna.Elango@Sun.COM 	    KM_SLEEP);
55010187SKrishna.Elango@Sun.COM 	pcie_pm_p->pcie_par_pminfo = cpwr_p;
55110187SKrishna.Elango@Sun.COM 	(cpwr_p->pwr_child_counters)[PCIE_UNKNOWN_INDEX] += comps;
55210187SKrishna.Elango@Sun.COM }
55310187SKrishna.Elango@Sun.COM 
55410187SKrishna.Elango@Sun.COM /*
55510187SKrishna.Elango@Sun.COM  * Remove the pm components of a child from our counters.
55610187SKrishna.Elango@Sun.COM  */
55710187SKrishna.Elango@Sun.COM static void
pcie_remove_comps(dev_info_t * dip,dev_info_t * cdip,pcie_pwr_t * pwr_p)55810187SKrishna.Elango@Sun.COM pcie_remove_comps(dev_info_t *dip, dev_info_t *cdip, pcie_pwr_t *pwr_p)
55910187SKrishna.Elango@Sun.COM {
56010187SKrishna.Elango@Sun.COM 	int i;
56110187SKrishna.Elango@Sun.COM 	int *child_counters;
56210187SKrishna.Elango@Sun.COM 
56310187SKrishna.Elango@Sun.COM 	ASSERT(MUTEX_HELD(&pwr_p->pwr_lock));
56410187SKrishna.Elango@Sun.COM 	if (!(PCIE_PMINFO(cdip)) || !PCIE_PAR_PMINFO(cdip)) {
56510187SKrishna.Elango@Sun.COM 		if (PCIE_SUPPORTS_DEVICE_PM(dip)) {
56610187SKrishna.Elango@Sun.COM 			/*
56710187SKrishna.Elango@Sun.COM 			 * Driver never made a PM call and we didn't create
56810187SKrishna.Elango@Sun.COM 			 * any counters for this device. This also means that
56910187SKrishna.Elango@Sun.COM 			 * hold made at the PRE_ATTACH time, still remains.
57010187SKrishna.Elango@Sun.COM 			 * Remove the hold now. The correct thing to do is to
57110187SKrishna.Elango@Sun.COM 			 * stay at full power when a child is at full power
57210187SKrishna.Elango@Sun.COM 			 * whether a driver is there or not. This will be
57310187SKrishna.Elango@Sun.COM 			 * implemented in the future.
57410187SKrishna.Elango@Sun.COM 			 */
57510187SKrishna.Elango@Sun.COM 			pcie_pm_subrelease(dip, pwr_p);
57610187SKrishna.Elango@Sun.COM 		}
57710187SKrishna.Elango@Sun.COM 		return;
57810187SKrishna.Elango@Sun.COM 	}
579*10750SShesha.Sreenivasamurthy@Sun.COM 	PCIE_DBG("%s(%d): pcie_remove_comps:counters decremented because of "
580*10750SShesha.Sreenivasamurthy@Sun.COM 	    "%s@%d\n", ddi_driver_name(dip), ddi_get_instance(dip),
581*10750SShesha.Sreenivasamurthy@Sun.COM 	    ddi_driver_name(cdip), ddi_get_instance(cdip));
58210187SKrishna.Elango@Sun.COM 	child_counters = PCIE_CHILD_COUNTERS(cdip);
58310187SKrishna.Elango@Sun.COM 	/*
58410187SKrishna.Elango@Sun.COM 	 * Adjust the nexus counters. No need to adjust per child dip
58510187SKrishna.Elango@Sun.COM 	 * counters as we are freeing the per child dip info.
58610187SKrishna.Elango@Sun.COM 	 */
58710187SKrishna.Elango@Sun.COM 	for (i = 0; i < PCIE_MAX_PWR_LEVELS; i++) {
58810187SKrishna.Elango@Sun.COM 		ASSERT((pwr_p->pwr_counters)[i] >= child_counters[i]);
58910187SKrishna.Elango@Sun.COM 		(pwr_p->pwr_counters)[i] -= child_counters[i];
59010187SKrishna.Elango@Sun.COM 	}
59110187SKrishna.Elango@Sun.COM 	/* remove both parent pm info and pcie pminfo itself */
59210187SKrishna.Elango@Sun.COM 	kmem_free(PCIE_PAR_PMINFO(cdip), sizeof (pcie_pwr_child_t));
59310187SKrishna.Elango@Sun.COM 	kmem_free(PCIE_PMINFO(cdip), sizeof (pcie_pm_t));
59410187SKrishna.Elango@Sun.COM 	PCIE_RESET_PMINFO(cdip);
59510187SKrishna.Elango@Sun.COM }
59610187SKrishna.Elango@Sun.COM 
59710187SKrishna.Elango@Sun.COM /*
59810187SKrishna.Elango@Sun.COM  * Power management related initialization common to px and pcieb
59910187SKrishna.Elango@Sun.COM  */
60010187SKrishna.Elango@Sun.COM int
pwr_common_setup(dev_info_t * dip)60110187SKrishna.Elango@Sun.COM pwr_common_setup(dev_info_t *dip)
60210187SKrishna.Elango@Sun.COM {
60310187SKrishna.Elango@Sun.COM 	pcie_pm_t		*pcie_pm_p;
60410187SKrishna.Elango@Sun.COM 	pcie_pwr_t		*pwr_p;
60510187SKrishna.Elango@Sun.COM 	int			pminfo_created = 0;
60610187SKrishna.Elango@Sun.COM 
60710187SKrishna.Elango@Sun.COM 	/* Create pminfo, if it doesn't exist already */
60810187SKrishna.Elango@Sun.COM 	if ((pcie_pm_p = PCIE_PMINFO(dip)) == NULL) {
60910187SKrishna.Elango@Sun.COM 		pcie_pm_p = (pcie_pm_t *)kmem_zalloc(
61010187SKrishna.Elango@Sun.COM 		    sizeof (pcie_pm_t), KM_SLEEP);
61110187SKrishna.Elango@Sun.COM 		PCIE_SET_PMINFO(dip, pcie_pm_p);
61210187SKrishna.Elango@Sun.COM 		pminfo_created = 1;
61310187SKrishna.Elango@Sun.COM 	}
61410187SKrishna.Elango@Sun.COM 	pwr_p = (pcie_pwr_t *)kmem_zalloc(sizeof (pcie_pwr_t), KM_SLEEP);
61510187SKrishna.Elango@Sun.COM 	mutex_init(&pwr_p->pwr_lock, NULL, MUTEX_DRIVER, NULL);
61610187SKrishna.Elango@Sun.COM 	/* Initialize the power level and default level support */
61710187SKrishna.Elango@Sun.COM 	pwr_p->pwr_func_lvl = PM_LEVEL_UNKNOWN;
61810187SKrishna.Elango@Sun.COM 	pwr_p->pwr_pmcaps = PCIE_DEFAULT_LEVEL_SUPPORTED;
61910187SKrishna.Elango@Sun.COM 
620*10750SShesha.Sreenivasamurthy@Sun.COM 	if (pcie_plat_pwr_setup(dip) != DDI_SUCCESS)
62110187SKrishna.Elango@Sun.COM 		goto pwr_common_err;
622*10750SShesha.Sreenivasamurthy@Sun.COM 
62310187SKrishna.Elango@Sun.COM 	pcie_pm_p->pcie_pwr_p = pwr_p;
62410187SKrishna.Elango@Sun.COM 	return (DDI_SUCCESS);
62510187SKrishna.Elango@Sun.COM 
62610187SKrishna.Elango@Sun.COM pwr_common_err:
62710187SKrishna.Elango@Sun.COM 	mutex_destroy(&pwr_p->pwr_lock);
62810187SKrishna.Elango@Sun.COM 	kmem_free(pwr_p, sizeof (pcie_pwr_t));
62910187SKrishna.Elango@Sun.COM 	if (pminfo_created) {
63010187SKrishna.Elango@Sun.COM 		PCIE_RESET_PMINFO(dip);
63110187SKrishna.Elango@Sun.COM 		kmem_free(pcie_pm_p, sizeof (pcie_pm_t));
63210187SKrishna.Elango@Sun.COM 	}
63310187SKrishna.Elango@Sun.COM 	return (DDI_FAILURE);
63410187SKrishna.Elango@Sun.COM 
63510187SKrishna.Elango@Sun.COM }
63610187SKrishna.Elango@Sun.COM 
63710187SKrishna.Elango@Sun.COM /*
63810187SKrishna.Elango@Sun.COM  * Undo whatever is done in pwr_common_setup. Called by px_detach or pxb_detach
63910187SKrishna.Elango@Sun.COM  */
64010187SKrishna.Elango@Sun.COM void
pwr_common_teardown(dev_info_t * dip)64110187SKrishna.Elango@Sun.COM pwr_common_teardown(dev_info_t *dip)
64210187SKrishna.Elango@Sun.COM {
64310187SKrishna.Elango@Sun.COM 	pcie_pm_t *pcie_pm_p = PCIE_PMINFO(dip);
64410187SKrishna.Elango@Sun.COM 	pcie_pwr_t *pwr_p;
64510187SKrishna.Elango@Sun.COM 
64610187SKrishna.Elango@Sun.COM 	if (!pcie_pm_p || !(pwr_p = PCIE_NEXUS_PMINFO(dip)))
64710187SKrishna.Elango@Sun.COM 		return;
64810187SKrishna.Elango@Sun.COM 
649*10750SShesha.Sreenivasamurthy@Sun.COM 	pcie_plat_pwr_teardown(dip);
65010187SKrishna.Elango@Sun.COM 	mutex_destroy(&pwr_p->pwr_lock);
65110187SKrishna.Elango@Sun.COM 	pcie_pm_p->pcie_pwr_p = NULL;
65210187SKrishna.Elango@Sun.COM 	kmem_free(pwr_p, sizeof (pcie_pwr_t));
65310187SKrishna.Elango@Sun.COM 	/*
65410187SKrishna.Elango@Sun.COM 	 * If the parent didn't store have any pm info about
65510187SKrishna.Elango@Sun.COM 	 * this node, that means parent doesn't need pminfo when it handles
65610187SKrishna.Elango@Sun.COM 	 * POST_DETACH for this node. For example, if dip is the dip of
65710187SKrishna.Elango@Sun.COM 	 * root complex, then there is no parent pm info.
65810187SKrishna.Elango@Sun.COM 	 */
65910187SKrishna.Elango@Sun.COM 	if (!PCIE_PAR_PMINFO(dip)) {
66010187SKrishna.Elango@Sun.COM 		kmem_free(pcie_pm_p, sizeof (pcie_pm_t));
66110187SKrishna.Elango@Sun.COM 		PCIE_RESET_PMINFO(dip);
66210187SKrishna.Elango@Sun.COM 	}
66310187SKrishna.Elango@Sun.COM }
66410187SKrishna.Elango@Sun.COM 
66510187SKrishna.Elango@Sun.COM /*
66610187SKrishna.Elango@Sun.COM  * Raises the power and marks itself busy.
66710187SKrishna.Elango@Sun.COM  */
66810187SKrishna.Elango@Sun.COM int
pcie_pm_hold(dev_info_t * dip)66910187SKrishna.Elango@Sun.COM pcie_pm_hold(dev_info_t *dip)
67010187SKrishna.Elango@Sun.COM {
67110187SKrishna.Elango@Sun.COM 	pcie_pwr_t *pwr_p;
67210187SKrishna.Elango@Sun.COM 
67310187SKrishna.Elango@Sun.COM 	/* If no PM info or no device PM, return */
67410187SKrishna.Elango@Sun.COM 	if (!PCIE_PMINFO(dip) || !(pwr_p = PCIE_NEXUS_PMINFO(dip)) ||
67510187SKrishna.Elango@Sun.COM 	    !(PCIE_SUPPORTS_DEVICE_PM(dip)))
67610187SKrishna.Elango@Sun.COM 		return (DDI_SUCCESS);
67710187SKrishna.Elango@Sun.COM 
67810187SKrishna.Elango@Sun.COM 	/*
67910187SKrishna.Elango@Sun.COM 	 * If we are not at full power, then powerup.
68010187SKrishna.Elango@Sun.COM 	 * Need to be at full power so that link can be
68110187SKrishna.Elango@Sun.COM 	 * at L0. Similarly for PCI/PCI-X bus, it should be
68210187SKrishna.Elango@Sun.COM 	 * at full power.
68310187SKrishna.Elango@Sun.COM 	 */
68410187SKrishna.Elango@Sun.COM 	mutex_enter(&pwr_p->pwr_lock);
68510187SKrishna.Elango@Sun.COM 	ASSERT(pwr_p->pwr_hold >= 0);
686*10750SShesha.Sreenivasamurthy@Sun.COM 	PCIE_DBG("%s(%d): pm_hold: incrementing hold \n",
687*10750SShesha.Sreenivasamurthy@Sun.COM 	    ddi_driver_name(dip), ddi_get_instance(dip));
68810187SKrishna.Elango@Sun.COM 	pwr_p->pwr_hold++;
68910187SKrishna.Elango@Sun.COM 	/* Mark itself busy, if it is not done already */
69010187SKrishna.Elango@Sun.COM 	if (!(pwr_p->pwr_flags & PCIE_PM_BUSY)) {
691*10750SShesha.Sreenivasamurthy@Sun.COM 		PCIE_DBG("%s(%d): pm_hold: marking busy\n",
692*10750SShesha.Sreenivasamurthy@Sun.COM 		    ddi_driver_name(dip), ddi_get_instance(dip));
69310187SKrishna.Elango@Sun.COM 		pwr_p->pwr_flags |= PCIE_PM_BUSY;
69410187SKrishna.Elango@Sun.COM 		(void) pm_busy_component(dip, 0);
69510187SKrishna.Elango@Sun.COM 	}
69610187SKrishna.Elango@Sun.COM 	if (pwr_p->pwr_func_lvl == PM_LEVEL_D0) {
69710187SKrishna.Elango@Sun.COM 		mutex_exit(&pwr_p->pwr_lock);
69810187SKrishna.Elango@Sun.COM 		return (DDI_SUCCESS);
69910187SKrishna.Elango@Sun.COM 	}
70010187SKrishna.Elango@Sun.COM 	mutex_exit(&pwr_p->pwr_lock);
70110187SKrishna.Elango@Sun.COM 	if (pm_raise_power(dip, 0, PM_LEVEL_D0) != DDI_SUCCESS) {
702*10750SShesha.Sreenivasamurthy@Sun.COM 		PCIE_DBG("%s(%d): pm_hold: attempt to raise power "
703*10750SShesha.Sreenivasamurthy@Sun.COM 		    "from %d to %d failed\n", ddi_driver_name(dip),
704*10750SShesha.Sreenivasamurthy@Sun.COM 		    ddi_get_instance(dip), pwr_p->pwr_func_lvl,
70510187SKrishna.Elango@Sun.COM 		    PM_LEVEL_D0);
70610187SKrishna.Elango@Sun.COM 		pcie_pm_release(dip);
70710187SKrishna.Elango@Sun.COM 		return (DDI_FAILURE);
70810187SKrishna.Elango@Sun.COM 	}
70910187SKrishna.Elango@Sun.COM 	return (DDI_SUCCESS);
71010187SKrishna.Elango@Sun.COM }
71110187SKrishna.Elango@Sun.COM 
71210187SKrishna.Elango@Sun.COM /*
71310187SKrishna.Elango@Sun.COM  * Reverse the things done in pcie_pm_hold
71410187SKrishna.Elango@Sun.COM  */
71510187SKrishna.Elango@Sun.COM void
pcie_pm_release(dev_info_t * dip)71610187SKrishna.Elango@Sun.COM pcie_pm_release(dev_info_t *dip)
71710187SKrishna.Elango@Sun.COM {
71810187SKrishna.Elango@Sun.COM 	pcie_pwr_t *pwr_p;
71910187SKrishna.Elango@Sun.COM 
72010187SKrishna.Elango@Sun.COM 	/* If no PM info or no device PM, return */
72110187SKrishna.Elango@Sun.COM 	if (!PCIE_PMINFO(dip) || !(pwr_p = PCIE_NEXUS_PMINFO(dip)) ||
72210187SKrishna.Elango@Sun.COM 	    !(PCIE_SUPPORTS_DEVICE_PM(dip)))
72310187SKrishna.Elango@Sun.COM 		return;
72410187SKrishna.Elango@Sun.COM 
72510187SKrishna.Elango@Sun.COM 	mutex_enter(&pwr_p->pwr_lock);
72610187SKrishna.Elango@Sun.COM 	pcie_pm_subrelease(dip, pwr_p);
72710187SKrishna.Elango@Sun.COM 	mutex_exit(&pwr_p->pwr_lock);
72810187SKrishna.Elango@Sun.COM }
72910187SKrishna.Elango@Sun.COM 
73010187SKrishna.Elango@Sun.COM static void
pcie_pm_subrelease(dev_info_t * dip,pcie_pwr_t * pwr_p)73110187SKrishna.Elango@Sun.COM pcie_pm_subrelease(dev_info_t *dip, pcie_pwr_t *pwr_p)
73210187SKrishna.Elango@Sun.COM {
73310187SKrishna.Elango@Sun.COM 	int level;
73410187SKrishna.Elango@Sun.COM 
73510187SKrishna.Elango@Sun.COM 	ASSERT(MUTEX_HELD(&pwr_p->pwr_lock));
73610187SKrishna.Elango@Sun.COM 	ASSERT(pwr_p->pwr_hold > 0);
737*10750SShesha.Sreenivasamurthy@Sun.COM 	PCIE_DBG("%s(%d): pm_subrelease: decrementing hold \n",
738*10750SShesha.Sreenivasamurthy@Sun.COM 	    ddi_driver_name(dip), ddi_get_instance(dip));
73910187SKrishna.Elango@Sun.COM 	pwr_p->pwr_hold--;
74010187SKrishna.Elango@Sun.COM 	ASSERT(pwr_p->pwr_hold >= 0);
74110187SKrishna.Elango@Sun.COM 	ASSERT(pwr_p->pwr_flags & PCIE_PM_BUSY);
74210187SKrishna.Elango@Sun.COM 	level = pwr_level_allowed(pwr_p);
74310187SKrishna.Elango@Sun.COM 	if (pwr_p->pwr_hold == 0 && level < pwr_p->pwr_func_lvl) {
744*10750SShesha.Sreenivasamurthy@Sun.COM 		PCIE_DBG("%s(%d): pm_subrelease: marking idle \n",
745*10750SShesha.Sreenivasamurthy@Sun.COM 		    ddi_driver_name(dip), ddi_get_instance(dip));
74610187SKrishna.Elango@Sun.COM 		(void) pm_idle_component(dip, 0);
74710187SKrishna.Elango@Sun.COM 		pwr_p->pwr_flags &= ~PCIE_PM_BUSY;
74810187SKrishna.Elango@Sun.COM 	}
74910187SKrishna.Elango@Sun.COM }
75010187SKrishna.Elango@Sun.COM 
75110187SKrishna.Elango@Sun.COM /*
75210187SKrishna.Elango@Sun.COM  * Called when the child makes the first power management call.
75310187SKrishna.Elango@Sun.COM  * sets up the counters. All the components of the child device are
75410187SKrishna.Elango@Sun.COM  * assumed to be at unknown level. It also releases the power hold
75510187SKrishna.Elango@Sun.COM  * 	pwr_p - parent's pwr_t
75610187SKrishna.Elango@Sun.COM  *	cdip   - child's dip
75710187SKrishna.Elango@Sun.COM  */
75810187SKrishna.Elango@Sun.COM int
pcie_pm_add_child(dev_info_t * dip,dev_info_t * cdip)75910187SKrishna.Elango@Sun.COM pcie_pm_add_child(dev_info_t *dip, dev_info_t *cdip)
76010187SKrishna.Elango@Sun.COM {
76110187SKrishna.Elango@Sun.COM 	pcie_pwr_t *pwr_p;
76210187SKrishna.Elango@Sun.COM 
76310187SKrishna.Elango@Sun.COM 	/* If no PM info, return */
76410187SKrishna.Elango@Sun.COM 	if (!PCIE_PMINFO(dip) || !(pwr_p = PCIE_NEXUS_PMINFO(dip)))
76510187SKrishna.Elango@Sun.COM 		return (DDI_SUCCESS);
76610187SKrishna.Elango@Sun.COM 
76710187SKrishna.Elango@Sun.COM 	ASSERT(MUTEX_HELD(&pwr_p->pwr_lock));
76810187SKrishna.Elango@Sun.COM 	ASSERT(pwr_p->pwr_func_lvl == PM_LEVEL_D0);
76910187SKrishna.Elango@Sun.COM 	pcie_add_comps(dip, cdip, pwr_p);
77010187SKrishna.Elango@Sun.COM 
77110187SKrishna.Elango@Sun.COM 	/* If no device power management then return */
77210187SKrishna.Elango@Sun.COM 	if (!PCIE_SUPPORTS_DEVICE_PM(dip))
77310187SKrishna.Elango@Sun.COM 		return (DDI_SUCCESS);
77410187SKrishna.Elango@Sun.COM 
77510187SKrishna.Elango@Sun.COM 	/*
77610187SKrishna.Elango@Sun.COM 	 * We have informed PM that we are busy at PRE_ATTACH time for
77710187SKrishna.Elango@Sun.COM 	 * this child. Release the hold and but don't clear the busy bit.
77810187SKrishna.Elango@Sun.COM 	 * If a device never changes power, hold will not be released
77910187SKrishna.Elango@Sun.COM 	 * and we stay at full power.
78010187SKrishna.Elango@Sun.COM 	 */
78110187SKrishna.Elango@Sun.COM 	ASSERT(pwr_p->pwr_hold > 0);
782*10750SShesha.Sreenivasamurthy@Sun.COM 	PCIE_DBG("%s(%d): pm_add_child: decrementing hold \n",
783*10750SShesha.Sreenivasamurthy@Sun.COM 	    ddi_driver_name(dip), ddi_get_instance(dip));
78410187SKrishna.Elango@Sun.COM 	pwr_p->pwr_hold--;
78510187SKrishna.Elango@Sun.COM 	/*
78610187SKrishna.Elango@Sun.COM 	 * We must have made sure that busy bit
78710187SKrishna.Elango@Sun.COM 	 * is set when we put the hold
78810187SKrishna.Elango@Sun.COM 	 */
78910187SKrishna.Elango@Sun.COM 	ASSERT(pwr_p->pwr_flags & PCIE_PM_BUSY);
79010187SKrishna.Elango@Sun.COM 	return (DDI_SUCCESS);
79110187SKrishna.Elango@Sun.COM }
79210187SKrishna.Elango@Sun.COM 
79310187SKrishna.Elango@Sun.COM /*
79410187SKrishna.Elango@Sun.COM  * Adjust the counters when a child detaches
79510187SKrishna.Elango@Sun.COM  * Marks itself idle if the idle conditions are met.
79610187SKrishna.Elango@Sun.COM  * Called at POST_DETACH time
79710187SKrishna.Elango@Sun.COM  */
79810187SKrishna.Elango@Sun.COM int
pcie_pm_remove_child(dev_info_t * dip,dev_info_t * cdip)79910187SKrishna.Elango@Sun.COM pcie_pm_remove_child(dev_info_t *dip, dev_info_t *cdip)
80010187SKrishna.Elango@Sun.COM {
80110187SKrishna.Elango@Sun.COM 	int *counters;
80210187SKrishna.Elango@Sun.COM 	int total;
80310187SKrishna.Elango@Sun.COM 	pcie_pwr_t *pwr_p;
80410187SKrishna.Elango@Sun.COM 
80510187SKrishna.Elango@Sun.COM 	/* If no PM info, return */
80610187SKrishna.Elango@Sun.COM 	if (!PCIE_PMINFO(dip) || !(pwr_p = PCIE_NEXUS_PMINFO(dip)))
80710187SKrishna.Elango@Sun.COM 		return (DDI_SUCCESS);
80810187SKrishna.Elango@Sun.COM 
80910187SKrishna.Elango@Sun.COM 	counters = pwr_p->pwr_counters;
81010187SKrishna.Elango@Sun.COM 	mutex_enter(&pwr_p->pwr_lock);
81110187SKrishna.Elango@Sun.COM 	pcie_remove_comps(dip, cdip, pwr_p);
81210187SKrishna.Elango@Sun.COM 	/* If no device power management then return */
81310187SKrishna.Elango@Sun.COM 	if (!PCIE_SUPPORTS_DEVICE_PM(dip)) {
81410187SKrishna.Elango@Sun.COM 		mutex_exit(&pwr_p->pwr_lock);
81510187SKrishna.Elango@Sun.COM 		return (DDI_SUCCESS);
81610187SKrishna.Elango@Sun.COM 	}
81710187SKrishna.Elango@Sun.COM 	total = (counters[PCIE_D0_INDEX] + counters[PCIE_UNKNOWN_INDEX] +
81810187SKrishna.Elango@Sun.COM 	    counters[PCIE_D1_INDEX] + counters[PCIE_D2_INDEX] +
81910187SKrishna.Elango@Sun.COM 	    counters[PCIE_D3_INDEX]);
82010187SKrishna.Elango@Sun.COM 	/*
82110187SKrishna.Elango@Sun.COM 	 * Mark idle if either there are no children or our lowest
82210187SKrishna.Elango@Sun.COM 	 * possible level is less than the current level. Mark idle
82310187SKrishna.Elango@Sun.COM 	 * only if it is not already done.
82410187SKrishna.Elango@Sun.COM 	 */
82510187SKrishna.Elango@Sun.COM 	if ((pwr_p->pwr_hold == 0) &&
82610187SKrishna.Elango@Sun.COM 	    (!total || (pwr_level_allowed(pwr_p) < pwr_p->pwr_func_lvl))) {
82710187SKrishna.Elango@Sun.COM 		if (pwr_p->pwr_flags & PCIE_PM_BUSY) {
828*10750SShesha.Sreenivasamurthy@Sun.COM 			PCIE_DBG("%s(%d): pcie_bus_power: marking idle\n",
829*10750SShesha.Sreenivasamurthy@Sun.COM 			    ddi_driver_name(dip), ddi_get_instance(dip));
83010187SKrishna.Elango@Sun.COM 			(void) pm_idle_component(dip, 0);
83110187SKrishna.Elango@Sun.COM 			pwr_p->pwr_flags &= ~PCIE_PM_BUSY;
83210187SKrishna.Elango@Sun.COM 		}
83310187SKrishna.Elango@Sun.COM 	}
83410187SKrishna.Elango@Sun.COM 	mutex_exit(&pwr_p->pwr_lock);
83510187SKrishna.Elango@Sun.COM 	return (DDI_SUCCESS);
83610187SKrishna.Elango@Sun.COM }
83710187SKrishna.Elango@Sun.COM 
83810187SKrishna.Elango@Sun.COM boolean_t
pcie_is_pcie(dev_info_t * dip)83910187SKrishna.Elango@Sun.COM pcie_is_pcie(dev_info_t *dip)
84010187SKrishna.Elango@Sun.COM {
84110187SKrishna.Elango@Sun.COM 	pcie_bus_t *bus_p = PCIE_DIP2BUS(dip);
84210187SKrishna.Elango@Sun.COM 	ASSERT(bus_p);
84310187SKrishna.Elango@Sun.COM 	return (bus_p->bus_pcie_off != 0);
84410187SKrishna.Elango@Sun.COM }
84510187SKrishna.Elango@Sun.COM 
84610187SKrishna.Elango@Sun.COM /*
84710187SKrishna.Elango@Sun.COM  * Called by px_attach or pcieb_attach:: DDI_RESUME
84810187SKrishna.Elango@Sun.COM  */
84910187SKrishna.Elango@Sun.COM int
pcie_pwr_resume(dev_info_t * dip)85010187SKrishna.Elango@Sun.COM pcie_pwr_resume(dev_info_t *dip)
85110187SKrishna.Elango@Sun.COM {
85210187SKrishna.Elango@Sun.COM 	dev_info_t *cdip;
85310187SKrishna.Elango@Sun.COM 	pcie_pwr_t *pwr_p = NULL;
85410187SKrishna.Elango@Sun.COM 
85510187SKrishna.Elango@Sun.COM #if defined(__i386) || defined(__amd64)
85610187SKrishna.Elango@Sun.COM 	if (dip)
85710187SKrishna.Elango@Sun.COM 		return (DDI_SUCCESS);
85810187SKrishna.Elango@Sun.COM #endif /* defined(__i386) || defined(__amd64) */
85910187SKrishna.Elango@Sun.COM 
86010187SKrishna.Elango@Sun.COM 	if (PCIE_PMINFO(dip))
86110187SKrishna.Elango@Sun.COM 		pwr_p = PCIE_NEXUS_PMINFO(dip);
86210187SKrishna.Elango@Sun.COM 
86310187SKrishna.Elango@Sun.COM 	if (pwr_p) {
86410187SKrishna.Elango@Sun.COM 		/* Inform the PM framework that dip is at full power */
86510187SKrishna.Elango@Sun.COM 		if (PCIE_SUPPORTS_DEVICE_PM(dip)) {
86610187SKrishna.Elango@Sun.COM 			ASSERT(pwr_p->pwr_func_lvl == PM_LEVEL_D0);
86710187SKrishna.Elango@Sun.COM 			(void) pm_raise_power(dip, 0,
86810187SKrishna.Elango@Sun.COM 			    pwr_p->pwr_func_lvl);
86910187SKrishna.Elango@Sun.COM 		}
87010187SKrishna.Elango@Sun.COM 	}
87110187SKrishna.Elango@Sun.COM 
87210187SKrishna.Elango@Sun.COM 	/*
87310187SKrishna.Elango@Sun.COM 	 * Code taken from pci driver.
87410187SKrishna.Elango@Sun.COM 	 * Restore config registers for children that did not save
87510187SKrishna.Elango@Sun.COM 	 * their own registers.  Children pwr states are UNKNOWN after
87610187SKrishna.Elango@Sun.COM 	 * a resume since it is possible for the PM framework to call
87710187SKrishna.Elango@Sun.COM 	 * resume without an actual power cycle. (ie if suspend fails).
87810187SKrishna.Elango@Sun.COM 	 */
87910187SKrishna.Elango@Sun.COM 	for (cdip = ddi_get_child(dip); cdip != NULL;
88010187SKrishna.Elango@Sun.COM 	    cdip = ddi_get_next_sibling(cdip)) {
88110187SKrishna.Elango@Sun.COM 		boolean_t	is_pcie;
88210187SKrishna.Elango@Sun.COM 
88310187SKrishna.Elango@Sun.COM 		/*
88410187SKrishna.Elango@Sun.COM 		 * Not interested in children who are not already
88510187SKrishna.Elango@Sun.COM 		 * init'ed.  They will be set up by init_child().
88610187SKrishna.Elango@Sun.COM 		 */
88710187SKrishna.Elango@Sun.COM 		if (i_ddi_node_state(cdip) < DS_INITIALIZED) {
888*10750SShesha.Sreenivasamurthy@Sun.COM 			PCIE_DBG("%s(%d): "
88910187SKrishna.Elango@Sun.COM 			    "DDI_RESUME: skipping %s%d not in CF1\n",
890*10750SShesha.Sreenivasamurthy@Sun.COM 			    ddi_driver_name(dip), ddi_get_instance(dip),
89110187SKrishna.Elango@Sun.COM 			    ddi_driver_name(cdip), ddi_get_instance(cdip));
89210187SKrishna.Elango@Sun.COM 			continue;
89310187SKrishna.Elango@Sun.COM 		}
89410187SKrishna.Elango@Sun.COM 
89510187SKrishna.Elango@Sun.COM 		/*
89610187SKrishna.Elango@Sun.COM 		 * Only restore config registers if saved by nexus.
89710187SKrishna.Elango@Sun.COM 		 */
89810187SKrishna.Elango@Sun.COM 		if (ddi_prop_exists(DDI_DEV_T_ANY, cdip, DDI_PROP_DONTPASS,
89910187SKrishna.Elango@Sun.COM 		    "nexus-saved-config-regs") != 1)
90010187SKrishna.Elango@Sun.COM 			continue;
90110187SKrishna.Elango@Sun.COM 
902*10750SShesha.Sreenivasamurthy@Sun.COM 		PCIE_DBG("%s(%d): "
90310187SKrishna.Elango@Sun.COM 		    "DDI_RESUME: nexus restoring %s%d config regs\n",
904*10750SShesha.Sreenivasamurthy@Sun.COM 		    ddi_driver_name(dip), ddi_get_instance(dip),
90510187SKrishna.Elango@Sun.COM 		    ddi_driver_name(cdip), ddi_get_instance(cdip));
90610187SKrishna.Elango@Sun.COM 
90710187SKrishna.Elango@Sun.COM 		/* clear errors left by OBP scrubbing */
90810187SKrishna.Elango@Sun.COM 		pcie_clear_errors(cdip);
90910187SKrishna.Elango@Sun.COM 
91010187SKrishna.Elango@Sun.COM 		/* PCIe workaround: disable errors during 4K config resore */
91110187SKrishna.Elango@Sun.COM 		if (is_pcie = pcie_is_pcie(cdip))
91210187SKrishna.Elango@Sun.COM 			pcie_disable_errors(cdip);
91310187SKrishna.Elango@Sun.COM 		(void) pci_restore_config_regs(cdip);
91410187SKrishna.Elango@Sun.COM 		if (is_pcie) {
91510187SKrishna.Elango@Sun.COM 			pcie_enable_errors(cdip);
91610187SKrishna.Elango@Sun.COM 			(void) pcie_enable_ce(cdip);
91710187SKrishna.Elango@Sun.COM 		}
91810187SKrishna.Elango@Sun.COM 
91910187SKrishna.Elango@Sun.COM 		if (ndi_prop_remove(DDI_DEV_T_NONE, cdip,
92010187SKrishna.Elango@Sun.COM 		    "nexus-saved-config-regs") != DDI_PROP_SUCCESS) {
921*10750SShesha.Sreenivasamurthy@Sun.COM 			PCIE_DBG("%s(%d): %s%d can't remove prop %s",
922*10750SShesha.Sreenivasamurthy@Sun.COM 			    ddi_driver_name(dip), ddi_get_instance(dip),
92310187SKrishna.Elango@Sun.COM 			    ddi_driver_name(cdip), ddi_get_instance(cdip),
92410187SKrishna.Elango@Sun.COM 			    "nexus-saved-config-regs");
92510187SKrishna.Elango@Sun.COM 		}
92610187SKrishna.Elango@Sun.COM 	}
92710187SKrishna.Elango@Sun.COM 	return (DDI_SUCCESS);
92810187SKrishna.Elango@Sun.COM }
92910187SKrishna.Elango@Sun.COM 
93010187SKrishna.Elango@Sun.COM /*
93110187SKrishna.Elango@Sun.COM  * Called by pcie_detach or pcieb_detach:: DDI_SUSPEND
93210187SKrishna.Elango@Sun.COM  */
93310187SKrishna.Elango@Sun.COM int
pcie_pwr_suspend(dev_info_t * dip)93410187SKrishna.Elango@Sun.COM pcie_pwr_suspend(dev_info_t *dip)
93510187SKrishna.Elango@Sun.COM {
93610187SKrishna.Elango@Sun.COM 	dev_info_t *cdip;
93710187SKrishna.Elango@Sun.COM 	int i, *counters; /* per nexus counters */
93810187SKrishna.Elango@Sun.COM 	int *child_counters = NULL; /* per child dip counters */
93910187SKrishna.Elango@Sun.COM 	pcie_pwr_t *pwr_p = NULL;
94010187SKrishna.Elango@Sun.COM 
94110187SKrishna.Elango@Sun.COM #if defined(__i386) || defined(__amd64)
94210187SKrishna.Elango@Sun.COM 	if (dip)
94310187SKrishna.Elango@Sun.COM 		return (DDI_SUCCESS);
94410187SKrishna.Elango@Sun.COM #endif /* defined(__i386) || defined(__amd64) */
94510187SKrishna.Elango@Sun.COM 
94610187SKrishna.Elango@Sun.COM 	if (PCIE_PMINFO(dip))
94710187SKrishna.Elango@Sun.COM 		pwr_p = PCIE_NEXUS_PMINFO(dip);
94810187SKrishna.Elango@Sun.COM 
94910187SKrishna.Elango@Sun.COM 	/*
95010187SKrishna.Elango@Sun.COM 	 * Mark all children to be unknown and bring our power level
95110187SKrishna.Elango@Sun.COM 	 * to full, if required. This is to avoid any panics while
95210187SKrishna.Elango@Sun.COM 	 * accessing the child's config space.
95310187SKrishna.Elango@Sun.COM 	 */
95410187SKrishna.Elango@Sun.COM 	if (pwr_p) {
95510187SKrishna.Elango@Sun.COM 		mutex_enter(&pwr_p->pwr_lock);
95610187SKrishna.Elango@Sun.COM 		if (PCIE_SUPPORTS_DEVICE_PM(dip) &&
95710187SKrishna.Elango@Sun.COM 		    pwr_p->pwr_func_lvl != PM_LEVEL_D0) {
95810187SKrishna.Elango@Sun.COM 			mutex_exit(&pwr_p->pwr_lock);
95910187SKrishna.Elango@Sun.COM 			if (pm_raise_power(dip, 0, PM_LEVEL_D0) !=
96010187SKrishna.Elango@Sun.COM 			    DDI_SUCCESS) {
961*10750SShesha.Sreenivasamurthy@Sun.COM 				PCIE_DBG("%s(%d): pwr_suspend: attempt "
96210187SKrishna.Elango@Sun.COM 				    "to raise power from %d to %d "
963*10750SShesha.Sreenivasamurthy@Sun.COM 				    "failed\n", ddi_driver_name(dip),
964*10750SShesha.Sreenivasamurthy@Sun.COM 				    ddi_get_instance(dip), pwr_p->pwr_func_lvl,
96510187SKrishna.Elango@Sun.COM 				    PM_LEVEL_D0);
96610187SKrishna.Elango@Sun.COM 				return (DDI_FAILURE);
96710187SKrishna.Elango@Sun.COM 			}
96810187SKrishna.Elango@Sun.COM 			mutex_enter(&pwr_p->pwr_lock);
96910187SKrishna.Elango@Sun.COM 		}
97010187SKrishna.Elango@Sun.COM 		counters = pwr_p->pwr_counters;
97110187SKrishna.Elango@Sun.COM 		/*
97210187SKrishna.Elango@Sun.COM 		 * Update the nexus counters. At the resume time all
97310187SKrishna.Elango@Sun.COM 		 * components are considered to be at unknown level. Use the
97410187SKrishna.Elango@Sun.COM 		 * fact that counters for unknown level are at the end.
97510187SKrishna.Elango@Sun.COM 		 */
97610187SKrishna.Elango@Sun.COM 		for (i = 0; i < PCIE_UNKNOWN_INDEX; i++) {
97710187SKrishna.Elango@Sun.COM 			counters[PCIE_UNKNOWN_INDEX] += counters[i];
97810187SKrishna.Elango@Sun.COM 			counters[i] = 0;
97910187SKrishna.Elango@Sun.COM 		}
98010187SKrishna.Elango@Sun.COM 		mutex_exit(&pwr_p->pwr_lock);
98110187SKrishna.Elango@Sun.COM 	}
98210187SKrishna.Elango@Sun.COM 
98310187SKrishna.Elango@Sun.COM 	/*
98410187SKrishna.Elango@Sun.COM 	 * Code taken from pci driver.
98510187SKrishna.Elango@Sun.COM 	 * Save the state of the configuration headers of child
98610187SKrishna.Elango@Sun.COM 	 * nodes.
98710187SKrishna.Elango@Sun.COM 	 */
98810187SKrishna.Elango@Sun.COM 	for (cdip = ddi_get_child(dip); cdip != NULL;
98910187SKrishna.Elango@Sun.COM 	    cdip = ddi_get_next_sibling(cdip)) {
99010187SKrishna.Elango@Sun.COM 		boolean_t	is_pcie;
99110187SKrishna.Elango@Sun.COM 
99210187SKrishna.Elango@Sun.COM 		/*
99310187SKrishna.Elango@Sun.COM 		 * Not interested in children who are not already
99410187SKrishna.Elango@Sun.COM 		 * init'ed.  They will be set up in init_child().
99510187SKrishna.Elango@Sun.COM 		 */
99610187SKrishna.Elango@Sun.COM 		if (i_ddi_node_state(cdip) < DS_INITIALIZED) {
997*10750SShesha.Sreenivasamurthy@Sun.COM 			PCIE_DBG("%s(%d): DDI_SUSPEND: skipping "
998*10750SShesha.Sreenivasamurthy@Sun.COM 			    "%s%d not in CF1\n", ddi_driver_name(dip),
999*10750SShesha.Sreenivasamurthy@Sun.COM 			    ddi_get_instance(dip), ddi_driver_name(cdip),
100010187SKrishna.Elango@Sun.COM 			    ddi_get_instance(cdip));
100110187SKrishna.Elango@Sun.COM 			continue;
100210187SKrishna.Elango@Sun.COM 		}
100310187SKrishna.Elango@Sun.COM 		/*
100410187SKrishna.Elango@Sun.COM 		 * Update per child dip counters, if any. Counters
100510187SKrishna.Elango@Sun.COM 		 * will not exist if the child is not power manageable
100610187SKrishna.Elango@Sun.COM 		 * or if its power entry is never invoked.
100710187SKrishna.Elango@Sun.COM 		 */
100810187SKrishna.Elango@Sun.COM 		if (PCIE_PMINFO(cdip) && PCIE_PAR_PMINFO(cdip))
100910187SKrishna.Elango@Sun.COM 			child_counters = PCIE_CHILD_COUNTERS(cdip);
101010187SKrishna.Elango@Sun.COM 		if (child_counters && pwr_p) {
101110187SKrishna.Elango@Sun.COM 			mutex_enter(&pwr_p->pwr_lock);
101210187SKrishna.Elango@Sun.COM 			for (i = 0; i < PCIE_UNKNOWN_INDEX; i++) {
101310187SKrishna.Elango@Sun.COM 				child_counters[PCIE_UNKNOWN_INDEX] +=
101410187SKrishna.Elango@Sun.COM 				    child_counters[i];
101510187SKrishna.Elango@Sun.COM 				child_counters[i] = 0;
101610187SKrishna.Elango@Sun.COM 			}
101710187SKrishna.Elango@Sun.COM 			mutex_exit(&pwr_p->pwr_lock);
101810187SKrishna.Elango@Sun.COM 		}
101910187SKrishna.Elango@Sun.COM 
102010187SKrishna.Elango@Sun.COM 		/*
102110187SKrishna.Elango@Sun.COM 		 * Only save config registers if not already saved by child.
102210187SKrishna.Elango@Sun.COM 		 */
102310187SKrishna.Elango@Sun.COM 		if (ddi_prop_exists(DDI_DEV_T_ANY, cdip, DDI_PROP_DONTPASS,
102410187SKrishna.Elango@Sun.COM 		    SAVED_CONFIG_REGS) == 1) {
102510187SKrishna.Elango@Sun.COM 			continue;
102610187SKrishna.Elango@Sun.COM 		}
102710187SKrishna.Elango@Sun.COM 
102810187SKrishna.Elango@Sun.COM 		/*
102910187SKrishna.Elango@Sun.COM 		 * The nexus needs to save config registers.  Create a property
103010187SKrishna.Elango@Sun.COM 		 * so it knows to restore on resume.
103110187SKrishna.Elango@Sun.COM 		 */
103210187SKrishna.Elango@Sun.COM 		if (ndi_prop_create_boolean(DDI_DEV_T_NONE, cdip,
103310187SKrishna.Elango@Sun.COM 		    "nexus-saved-config-regs") != DDI_PROP_SUCCESS) {
1034*10750SShesha.Sreenivasamurthy@Sun.COM 			PCIE_DBG("%s(%d): %s%d can't update prop %s",
1035*10750SShesha.Sreenivasamurthy@Sun.COM 			    ddi_driver_name(dip), ddi_get_instance(dip),
103610187SKrishna.Elango@Sun.COM 			    ddi_driver_name(cdip), ddi_get_instance(cdip),
103710187SKrishna.Elango@Sun.COM 			    "nexus-saved-config-regs");
103810187SKrishna.Elango@Sun.COM 		}
1039*10750SShesha.Sreenivasamurthy@Sun.COM 		PCIE_DBG("%s(%d): DDI_SUSPEND: saving config space for"
1040*10750SShesha.Sreenivasamurthy@Sun.COM 		    " %s%d\n", ddi_driver_name(dip), ddi_get_instance(dip),
1041*10750SShesha.Sreenivasamurthy@Sun.COM 		    ddi_driver_name(cdip), ddi_get_instance(cdip));
104210187SKrishna.Elango@Sun.COM 
104310187SKrishna.Elango@Sun.COM 		/* PCIe workaround: disable errors during 4K config save */
104410187SKrishna.Elango@Sun.COM 		if (is_pcie = pcie_is_pcie(cdip))
104510187SKrishna.Elango@Sun.COM 			pcie_disable_errors(cdip);
104610187SKrishna.Elango@Sun.COM 		(void) pci_save_config_regs(cdip);
104710187SKrishna.Elango@Sun.COM 		if (is_pcie) {
104810187SKrishna.Elango@Sun.COM 			pcie_enable_errors(cdip);
104910187SKrishna.Elango@Sun.COM 			(void) pcie_enable_ce(cdip);
105010187SKrishna.Elango@Sun.COM 		}
105110187SKrishna.Elango@Sun.COM 	}
105210187SKrishna.Elango@Sun.COM 	return (DDI_SUCCESS);
105310187SKrishna.Elango@Sun.COM }
105410187SKrishna.Elango@Sun.COM 
105510187SKrishna.Elango@Sun.COM #ifdef DEBUG
105610187SKrishna.Elango@Sun.COM /*
105710187SKrishna.Elango@Sun.COM  * Description of bus_power_op.
105810187SKrishna.Elango@Sun.COM  */
105910187SKrishna.Elango@Sun.COM typedef struct pcie_buspwr_desc {
106010187SKrishna.Elango@Sun.COM 	pm_bus_power_op_t pwr_op;
106110187SKrishna.Elango@Sun.COM 	char *pwr_desc;
106210187SKrishna.Elango@Sun.COM } pcie_buspwr_desc_t;
106310187SKrishna.Elango@Sun.COM 
106410187SKrishna.Elango@Sun.COM static pcie_buspwr_desc_t pcie_buspwr_desc[] = {
106510187SKrishna.Elango@Sun.COM 	{BUS_POWER_CHILD_PWRCHG, "CHILD_PWRCHG"},
106610187SKrishna.Elango@Sun.COM 	{BUS_POWER_NEXUS_PWRUP, "NEXUS_PWRUP"},
106710187SKrishna.Elango@Sun.COM 	{BUS_POWER_PRE_NOTIFICATION, "PRE_NOTIFICATION"},
106810187SKrishna.Elango@Sun.COM 	{BUS_POWER_POST_NOTIFICATION, "POST_NOTIFICATION"},
106910187SKrishna.Elango@Sun.COM 	{BUS_POWER_HAS_CHANGED, "HAS_CHANGED"},
107010187SKrishna.Elango@Sun.COM 	{BUS_POWER_NOINVOL, "NOINVOL"},
107110187SKrishna.Elango@Sun.COM 	{-1, NULL}
107210187SKrishna.Elango@Sun.COM };
107310187SKrishna.Elango@Sun.COM 
107410187SKrishna.Elango@Sun.COM /*
107510187SKrishna.Elango@Sun.COM  * Returns description of the bus_power_op.
107610187SKrishna.Elango@Sun.COM  */
107710187SKrishna.Elango@Sun.COM static char *
pcie_decode_pwr_op(pm_bus_power_op_t op)107810187SKrishna.Elango@Sun.COM pcie_decode_pwr_op(pm_bus_power_op_t op)
107910187SKrishna.Elango@Sun.COM {
108010187SKrishna.Elango@Sun.COM 	pcie_buspwr_desc_t *descp = pcie_buspwr_desc;
108110187SKrishna.Elango@Sun.COM 
108210187SKrishna.Elango@Sun.COM 	for (; descp->pwr_desc; descp++) {
108310187SKrishna.Elango@Sun.COM 		if (op == descp->pwr_op)
108410187SKrishna.Elango@Sun.COM 			return (descp->pwr_desc);
108510187SKrishna.Elango@Sun.COM 	}
108610187SKrishna.Elango@Sun.COM 	return ("UNKNOWN OP");
108710187SKrishna.Elango@Sun.COM }
108810187SKrishna.Elango@Sun.COM #endif
1089