xref: /onnv-gate/usr/src/uts/i86pc/io/cpudrv_mach.c (revision 8906:e559381f1e2b)
1*8906SEric.Saxe@Sun.COM /*
2*8906SEric.Saxe@Sun.COM  * CDDL HEADER START
3*8906SEric.Saxe@Sun.COM  *
4*8906SEric.Saxe@Sun.COM  * The contents of this file are subject to the terms of the
5*8906SEric.Saxe@Sun.COM  * Common Development and Distribution License (the "License").
6*8906SEric.Saxe@Sun.COM  * You may not use this file except in compliance with the License.
7*8906SEric.Saxe@Sun.COM  *
8*8906SEric.Saxe@Sun.COM  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9*8906SEric.Saxe@Sun.COM  * or http://www.opensolaris.org/os/licensing.
10*8906SEric.Saxe@Sun.COM  * See the License for the specific language governing permissions
11*8906SEric.Saxe@Sun.COM  * and limitations under the License.
12*8906SEric.Saxe@Sun.COM  *
13*8906SEric.Saxe@Sun.COM  * When distributing Covered Code, include this CDDL HEADER in each
14*8906SEric.Saxe@Sun.COM  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15*8906SEric.Saxe@Sun.COM  * If applicable, add the following below this CDDL HEADER, with the
16*8906SEric.Saxe@Sun.COM  * fields enclosed by brackets "[]" replaced with your own identifying
17*8906SEric.Saxe@Sun.COM  * information: Portions Copyright [yyyy] [name of copyright owner]
18*8906SEric.Saxe@Sun.COM  *
19*8906SEric.Saxe@Sun.COM  * CDDL HEADER END
20*8906SEric.Saxe@Sun.COM  */
21*8906SEric.Saxe@Sun.COM /*
22*8906SEric.Saxe@Sun.COM  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23*8906SEric.Saxe@Sun.COM  * Use is subject to license terms.
24*8906SEric.Saxe@Sun.COM  */
25*8906SEric.Saxe@Sun.COM 
26*8906SEric.Saxe@Sun.COM /*
27*8906SEric.Saxe@Sun.COM  * CPU power management driver support for i86pc.
28*8906SEric.Saxe@Sun.COM  */
29*8906SEric.Saxe@Sun.COM 
30*8906SEric.Saxe@Sun.COM #include <sys/ddi.h>
31*8906SEric.Saxe@Sun.COM #include <sys/sunddi.h>
32*8906SEric.Saxe@Sun.COM #include <sys/cpupm.h>
33*8906SEric.Saxe@Sun.COM #include <sys/cpudrv_mach.h>
34*8906SEric.Saxe@Sun.COM #include <sys/machsystm.h>
35*8906SEric.Saxe@Sun.COM #include <sys/cpu_pm.h>
36*8906SEric.Saxe@Sun.COM #include <sys/cpuvar.h>
37*8906SEric.Saxe@Sun.COM #include <sys/sdt.h>
38*8906SEric.Saxe@Sun.COM #include <sys/cpu_idle.h>
39*8906SEric.Saxe@Sun.COM 
40*8906SEric.Saxe@Sun.COM /*
41*8906SEric.Saxe@Sun.COM  * Note that our driver numbers the power levels from lowest to
42*8906SEric.Saxe@Sun.COM  * highest starting at 1 (i.e., the lowest power level is 1 and
43*8906SEric.Saxe@Sun.COM  * the highest power level is cpupm->num_spd). The x86 modules get
44*8906SEric.Saxe@Sun.COM  * their power levels from ACPI which numbers power levels from
45*8906SEric.Saxe@Sun.COM  * highest to lowest starting at 0 (i.e., the lowest power level
46*8906SEric.Saxe@Sun.COM  * is (cpupm->num_spd - 1) and the highest power level is 0). So to
47*8906SEric.Saxe@Sun.COM  * map one of our driver power levels to one understood by ACPI we
48*8906SEric.Saxe@Sun.COM  * simply subtract our driver power level from cpupm->num_spd. Likewise,
49*8906SEric.Saxe@Sun.COM  * to map an ACPI power level to the proper driver power level, we
50*8906SEric.Saxe@Sun.COM  * subtract the ACPI power level from cpupm->num_spd.
51*8906SEric.Saxe@Sun.COM  */
52*8906SEric.Saxe@Sun.COM #define	PM_2_PLAT_LEVEL(cpupm, pm_level) (cpupm->num_spd - pm_level)
53*8906SEric.Saxe@Sun.COM #define	PLAT_2_PM_LEVEL(cpupm, plat_level) (cpupm->num_spd - plat_level)
54*8906SEric.Saxe@Sun.COM 
55*8906SEric.Saxe@Sun.COM /*
56*8906SEric.Saxe@Sun.COM  * Change CPU speed using interface provided by module.
57*8906SEric.Saxe@Sun.COM  */
58*8906SEric.Saxe@Sun.COM int
59*8906SEric.Saxe@Sun.COM cpudrv_change_speed(cpudrv_devstate_t *cpudsp, cpudrv_pm_spd_t *new_spd)
60*8906SEric.Saxe@Sun.COM {
61*8906SEric.Saxe@Sun.COM 	cpu_t *cp = cpudsp->cp;
62*8906SEric.Saxe@Sun.COM 	cpupm_mach_state_t *mach_state =
63*8906SEric.Saxe@Sun.COM 	    (cpupm_mach_state_t *)cp->cpu_m.mcpu_pm_mach_state;
64*8906SEric.Saxe@Sun.COM 	cpudrv_pm_t *cpupm;
65*8906SEric.Saxe@Sun.COM 	cpuset_t set;
66*8906SEric.Saxe@Sun.COM 	uint32_t plat_level;
67*8906SEric.Saxe@Sun.COM 
68*8906SEric.Saxe@Sun.COM 	if (!(mach_state->ms_caps & CPUPM_P_STATES))
69*8906SEric.Saxe@Sun.COM 		return (DDI_FAILURE);
70*8906SEric.Saxe@Sun.COM 	ASSERT(mach_state->ms_pstate.cma_ops != NULL);
71*8906SEric.Saxe@Sun.COM 	cpupm = &(cpudsp->cpudrv_pm);
72*8906SEric.Saxe@Sun.COM 	plat_level = PM_2_PLAT_LEVEL(cpupm, new_spd->pm_level);
73*8906SEric.Saxe@Sun.COM 	CPUSET_ONLY(set, cp->cpu_id);
74*8906SEric.Saxe@Sun.COM 	mach_state->ms_pstate.cma_ops->cpus_change(set, plat_level);
75*8906SEric.Saxe@Sun.COM 
76*8906SEric.Saxe@Sun.COM 	return (DDI_SUCCESS);
77*8906SEric.Saxe@Sun.COM }
78*8906SEric.Saxe@Sun.COM 
79*8906SEric.Saxe@Sun.COM /*
80*8906SEric.Saxe@Sun.COM  * Determine the cpu_id for the CPU device.
81*8906SEric.Saxe@Sun.COM  */
82*8906SEric.Saxe@Sun.COM boolean_t
83*8906SEric.Saxe@Sun.COM cpudrv_get_cpu_id(dev_info_t *dip,  processorid_t *cpu_id)
84*8906SEric.Saxe@Sun.COM {
85*8906SEric.Saxe@Sun.COM 	return ((*cpu_id = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
86*8906SEric.Saxe@Sun.COM 	    DDI_PROP_DONTPASS, "reg", -1)) != -1);
87*8906SEric.Saxe@Sun.COM 
88*8906SEric.Saxe@Sun.COM }
89*8906SEric.Saxe@Sun.COM 
90*8906SEric.Saxe@Sun.COM boolean_t
91*8906SEric.Saxe@Sun.COM cpudrv_is_enabled(cpudrv_devstate_t *cpudsp)
92*8906SEric.Saxe@Sun.COM {
93*8906SEric.Saxe@Sun.COM 	cpupm_mach_state_t *mach_state;
94*8906SEric.Saxe@Sun.COM 
95*8906SEric.Saxe@Sun.COM 	if (!cpupm_is_enabled(CPUPM_P_STATES) || !cpudrv_enabled)
96*8906SEric.Saxe@Sun.COM 		return (B_FALSE);
97*8906SEric.Saxe@Sun.COM 
98*8906SEric.Saxe@Sun.COM 	/*
99*8906SEric.Saxe@Sun.COM 	 * Only check the instance specific setting it exists.
100*8906SEric.Saxe@Sun.COM 	 */
101*8906SEric.Saxe@Sun.COM 	if (cpudsp != NULL && cpudsp->cp != NULL &&
102*8906SEric.Saxe@Sun.COM 	    cpudsp->cp->cpu_m.mcpu_pm_mach_state != NULL) {
103*8906SEric.Saxe@Sun.COM 		mach_state =
104*8906SEric.Saxe@Sun.COM 		    (cpupm_mach_state_t *)cpudsp->cp->cpu_m.mcpu_pm_mach_state;
105*8906SEric.Saxe@Sun.COM 		return (mach_state->ms_caps & CPUPM_P_STATES);
106*8906SEric.Saxe@Sun.COM 	}
107*8906SEric.Saxe@Sun.COM 
108*8906SEric.Saxe@Sun.COM 	return (B_TRUE);
109*8906SEric.Saxe@Sun.COM }
110*8906SEric.Saxe@Sun.COM 
111*8906SEric.Saxe@Sun.COM /*
112*8906SEric.Saxe@Sun.COM  * Is the current thread the thread that is handling the
113*8906SEric.Saxe@Sun.COM  * PPC change notification?
114*8906SEric.Saxe@Sun.COM  */
115*8906SEric.Saxe@Sun.COM boolean_t
116*8906SEric.Saxe@Sun.COM cpudrv_is_governor_thread(cpudrv_pm_t *cpupm)
117*8906SEric.Saxe@Sun.COM {
118*8906SEric.Saxe@Sun.COM 	return (curthread == cpupm->pm_governor_thread);
119*8906SEric.Saxe@Sun.COM }
120*8906SEric.Saxe@Sun.COM 
121*8906SEric.Saxe@Sun.COM /*
122*8906SEric.Saxe@Sun.COM  * This routine changes the top speed to which the CPUs can transition by:
123*8906SEric.Saxe@Sun.COM  *
124*8906SEric.Saxe@Sun.COM  * - Resetting the up_spd for all speeds lower than the new top speed
125*8906SEric.Saxe@Sun.COM  *   to point to the new top speed.
126*8906SEric.Saxe@Sun.COM  * - Updating the framework with a new "normal" (maximum power) for this
127*8906SEric.Saxe@Sun.COM  *   device.
128*8906SEric.Saxe@Sun.COM  */
129*8906SEric.Saxe@Sun.COM void
130*8906SEric.Saxe@Sun.COM cpudrv_set_topspeed(void *ctx, int plat_level)
131*8906SEric.Saxe@Sun.COM {
132*8906SEric.Saxe@Sun.COM 	cpudrv_devstate_t *cpudsp;
133*8906SEric.Saxe@Sun.COM 	cpudrv_pm_t *cpupm;
134*8906SEric.Saxe@Sun.COM 	cpudrv_pm_spd_t	*spd;
135*8906SEric.Saxe@Sun.COM 	cpudrv_pm_spd_t	*top_spd;
136*8906SEric.Saxe@Sun.COM 	dev_info_t *dip;
137*8906SEric.Saxe@Sun.COM 	int pm_level;
138*8906SEric.Saxe@Sun.COM 	int instance;
139*8906SEric.Saxe@Sun.COM 	int i;
140*8906SEric.Saxe@Sun.COM 
141*8906SEric.Saxe@Sun.COM 	dip = ctx;
142*8906SEric.Saxe@Sun.COM 	instance = ddi_get_instance(dip);
143*8906SEric.Saxe@Sun.COM 	cpudsp = ddi_get_soft_state(cpudrv_state, instance);
144*8906SEric.Saxe@Sun.COM 	ASSERT(cpudsp != NULL);
145*8906SEric.Saxe@Sun.COM 
146*8906SEric.Saxe@Sun.COM 	mutex_enter(&cpudsp->lock);
147*8906SEric.Saxe@Sun.COM 	cpupm = &(cpudsp->cpudrv_pm);
148*8906SEric.Saxe@Sun.COM 	pm_level = PLAT_2_PM_LEVEL(cpupm, plat_level);
149*8906SEric.Saxe@Sun.COM 	for (i = 0, spd = cpupm->head_spd; spd; i++, spd = spd->down_spd) {
150*8906SEric.Saxe@Sun.COM 		/*
151*8906SEric.Saxe@Sun.COM 		 * Don't mess with speeds that are higher than the new
152*8906SEric.Saxe@Sun.COM 		 * top speed. They should be out of range anyway.
153*8906SEric.Saxe@Sun.COM 		 */
154*8906SEric.Saxe@Sun.COM 		if (spd->pm_level > pm_level)
155*8906SEric.Saxe@Sun.COM 			continue;
156*8906SEric.Saxe@Sun.COM 		/*
157*8906SEric.Saxe@Sun.COM 		 * This is the new top speed.
158*8906SEric.Saxe@Sun.COM 		 */
159*8906SEric.Saxe@Sun.COM 		if (spd->pm_level == pm_level)
160*8906SEric.Saxe@Sun.COM 			top_spd = spd;
161*8906SEric.Saxe@Sun.COM 
162*8906SEric.Saxe@Sun.COM 		spd->up_spd = top_spd;
163*8906SEric.Saxe@Sun.COM 	}
164*8906SEric.Saxe@Sun.COM 	cpupm->top_spd = top_spd;
165*8906SEric.Saxe@Sun.COM 
166*8906SEric.Saxe@Sun.COM 	cpupm->pm_governor_thread = curthread;
167*8906SEric.Saxe@Sun.COM 
168*8906SEric.Saxe@Sun.COM 	mutex_exit(&cpudsp->lock);
169*8906SEric.Saxe@Sun.COM 
170*8906SEric.Saxe@Sun.COM 	(void) pm_update_maxpower(dip, 0, top_spd->pm_level);
171*8906SEric.Saxe@Sun.COM }
172*8906SEric.Saxe@Sun.COM 
173*8906SEric.Saxe@Sun.COM /*
174*8906SEric.Saxe@Sun.COM  * This routine reads the ACPI _PPC object. It's accessed as a callback
175*8906SEric.Saxe@Sun.COM  * by the ppm driver whenever a _PPC change notification is received.
176*8906SEric.Saxe@Sun.COM  */
177*8906SEric.Saxe@Sun.COM int
178*8906SEric.Saxe@Sun.COM cpudrv_get_topspeed(void *ctx)
179*8906SEric.Saxe@Sun.COM {
180*8906SEric.Saxe@Sun.COM 	cpu_t *cp;
181*8906SEric.Saxe@Sun.COM 	cpudrv_devstate_t *cpudsp;
182*8906SEric.Saxe@Sun.COM 	dev_info_t *dip;
183*8906SEric.Saxe@Sun.COM 	int instance;
184*8906SEric.Saxe@Sun.COM 	int plat_level;
185*8906SEric.Saxe@Sun.COM 
186*8906SEric.Saxe@Sun.COM 	dip = ctx;
187*8906SEric.Saxe@Sun.COM 	instance = ddi_get_instance(dip);
188*8906SEric.Saxe@Sun.COM 	cpudsp = ddi_get_soft_state(cpudrv_state, instance);
189*8906SEric.Saxe@Sun.COM 	ASSERT(cpudsp != NULL);
190*8906SEric.Saxe@Sun.COM 	cp = cpudsp->cp;
191*8906SEric.Saxe@Sun.COM 	plat_level = cpupm_get_top_speed(cp);
192*8906SEric.Saxe@Sun.COM 
193*8906SEric.Saxe@Sun.COM 	return (plat_level);
194*8906SEric.Saxe@Sun.COM }
195*8906SEric.Saxe@Sun.COM 
196*8906SEric.Saxe@Sun.COM 
197*8906SEric.Saxe@Sun.COM /*
198*8906SEric.Saxe@Sun.COM  * This notification handler is called whenever the ACPI _PPC
199*8906SEric.Saxe@Sun.COM  * object changes. The _PPC is a sort of governor on power levels.
200*8906SEric.Saxe@Sun.COM  * It sets an upper threshold on which, _PSS defined, power levels
201*8906SEric.Saxe@Sun.COM  * are usuable. The _PPC value is dynamic and may change as properties
202*8906SEric.Saxe@Sun.COM  * (i.e., thermal or AC source) of the system change.
203*8906SEric.Saxe@Sun.COM  */
204*8906SEric.Saxe@Sun.COM /* ARGSUSED */
205*8906SEric.Saxe@Sun.COM static void
206*8906SEric.Saxe@Sun.COM cpudrv_notify_handler(ACPI_HANDLE obj, UINT32 val, void *ctx)
207*8906SEric.Saxe@Sun.COM {
208*8906SEric.Saxe@Sun.COM 	extern pm_cpupm_t cpupm;
209*8906SEric.Saxe@Sun.COM 
210*8906SEric.Saxe@Sun.COM 	/*
211*8906SEric.Saxe@Sun.COM 	 * We only handle _PPC change notifications.
212*8906SEric.Saxe@Sun.COM 	 */
213*8906SEric.Saxe@Sun.COM 	if (val == CPUPM_PPC_CHANGE_NOTIFICATION && !PM_EVENT_CPUPM)
214*8906SEric.Saxe@Sun.COM 		cpudrv_redefine_topspeed(ctx);
215*8906SEric.Saxe@Sun.COM }
216*8906SEric.Saxe@Sun.COM 
217*8906SEric.Saxe@Sun.COM void
218*8906SEric.Saxe@Sun.COM cpudrv_install_notify_handler(cpudrv_devstate_t *cpudsp)
219*8906SEric.Saxe@Sun.COM {
220*8906SEric.Saxe@Sun.COM 	cpu_t *cp = cpudsp->cp;
221*8906SEric.Saxe@Sun.COM 	cpupm_add_notify_handler(cp, cpudrv_notify_handler,
222*8906SEric.Saxe@Sun.COM 	    cpudsp->dip);
223*8906SEric.Saxe@Sun.COM }
224*8906SEric.Saxe@Sun.COM 
225*8906SEric.Saxe@Sun.COM void
226*8906SEric.Saxe@Sun.COM cpudrv_redefine_topspeed(void *ctx)
227*8906SEric.Saxe@Sun.COM {
228*8906SEric.Saxe@Sun.COM 	/*
229*8906SEric.Saxe@Sun.COM 	 * This should never happen, unless ppm does not get loaded.
230*8906SEric.Saxe@Sun.COM 	 */
231*8906SEric.Saxe@Sun.COM 	if (cpupm_redefine_topspeed == NULL) {
232*8906SEric.Saxe@Sun.COM 		cmn_err(CE_WARN, "cpudrv_redefine_topspeed: "
233*8906SEric.Saxe@Sun.COM 		    "cpupm_redefine_topspeed has not been initialized - "
234*8906SEric.Saxe@Sun.COM 		    "ignoring notification");
235*8906SEric.Saxe@Sun.COM 		return;
236*8906SEric.Saxe@Sun.COM 	}
237*8906SEric.Saxe@Sun.COM 
238*8906SEric.Saxe@Sun.COM 	/*
239*8906SEric.Saxe@Sun.COM 	 * ppm callback needs to handle redefinition for all CPUs in
240*8906SEric.Saxe@Sun.COM 	 * the domain.
241*8906SEric.Saxe@Sun.COM 	 */
242*8906SEric.Saxe@Sun.COM 	(*cpupm_redefine_topspeed)(ctx);
243*8906SEric.Saxe@Sun.COM }
244*8906SEric.Saxe@Sun.COM 
245*8906SEric.Saxe@Sun.COM boolean_t
246*8906SEric.Saxe@Sun.COM cpudrv_mach_init(cpudrv_devstate_t *cpudsp)
247*8906SEric.Saxe@Sun.COM {
248*8906SEric.Saxe@Sun.COM 	cpupm_mach_state_t *mach_state;
249*8906SEric.Saxe@Sun.COM 
250*8906SEric.Saxe@Sun.COM 	mutex_enter(&cpu_lock);
251*8906SEric.Saxe@Sun.COM 	cpudsp->cp = cpu_get(cpudsp->cpu_id);
252*8906SEric.Saxe@Sun.COM 	mutex_exit(&cpu_lock);
253*8906SEric.Saxe@Sun.COM 	if (cpudsp->cp == NULL) {
254*8906SEric.Saxe@Sun.COM 		cmn_err(CE_WARN, "cpudrv_mach_pm_init: instance %d: "
255*8906SEric.Saxe@Sun.COM 		    "can't get cpu_t", ddi_get_instance(cpudsp->dip));
256*8906SEric.Saxe@Sun.COM 		return (B_FALSE);
257*8906SEric.Saxe@Sun.COM 	}
258*8906SEric.Saxe@Sun.COM 
259*8906SEric.Saxe@Sun.COM 	mach_state = (cpupm_mach_state_t *)
260*8906SEric.Saxe@Sun.COM 	    (cpudsp->cp->cpu_m.mcpu_pm_mach_state);
261*8906SEric.Saxe@Sun.COM 	mach_state->ms_dip = cpudsp->dip;
262*8906SEric.Saxe@Sun.COM 	return (B_TRUE);
263*8906SEric.Saxe@Sun.COM }
264*8906SEric.Saxe@Sun.COM 
265*8906SEric.Saxe@Sun.COM uint_t
266*8906SEric.Saxe@Sun.COM cpudrv_get_speeds(cpudrv_devstate_t *cpudsp, int **speeds)
267*8906SEric.Saxe@Sun.COM {
268*8906SEric.Saxe@Sun.COM 	return (cpupm_get_speeds(cpudsp->cp, speeds));
269*8906SEric.Saxe@Sun.COM }
270*8906SEric.Saxe@Sun.COM 
271*8906SEric.Saxe@Sun.COM void
272*8906SEric.Saxe@Sun.COM cpudrv_free_speeds(int *speeds, uint_t nspeeds)
273*8906SEric.Saxe@Sun.COM {
274*8906SEric.Saxe@Sun.COM 	cpupm_free_speeds(speeds, nspeeds);
275*8906SEric.Saxe@Sun.COM }
276*8906SEric.Saxe@Sun.COM 
277*8906SEric.Saxe@Sun.COM boolean_t
278*8906SEric.Saxe@Sun.COM cpudrv_power_ready(void)
279*8906SEric.Saxe@Sun.COM {
280*8906SEric.Saxe@Sun.COM 	return (cpupm_power_ready());
281*8906SEric.Saxe@Sun.COM }
282*8906SEric.Saxe@Sun.COM 
283*8906SEric.Saxe@Sun.COM /* ARGSUSED */
284*8906SEric.Saxe@Sun.COM void
285*8906SEric.Saxe@Sun.COM cpudrv_set_supp_freqs(cpudrv_devstate_t *cpudsp)
286*8906SEric.Saxe@Sun.COM {
287*8906SEric.Saxe@Sun.COM }
288