xref: /illumos-gate/usr/src/uts/sun4u/io/ppm_plat.c (revision 374858d291554c199353841e2900bc130463934a)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * Platform Power Management master pseudo driver platform support.
30  */
31 
32 #include <sys/file.h>
33 #include <sys/ddi.h>
34 #include <sys/sunddi.h>
35 #include <sys/ppmvar.h>
36 
37 /*
38  * This flag disables vcore/vid feature by default.
39  */
40 uint_t	ppm_do_vcore = 0;
41 
42 /*
43  * PPMDC_CPU_NEXT operation
44  */
45 static int
46 ppm_cpu_next(ppm_domain_t *domp, int level)
47 {
48 #ifdef DEBUG
49 	char *str = "ppm_cpu_next";
50 #endif
51 	ppm_dc_t *dc;
52 	int index = level - 1;
53 	int ret = 0;
54 
55 	dc = ppm_lookup_dc(domp, PPMDC_CPU_NEXT);
56 	for (; dc && (dc->cmd == PPMDC_CPU_NEXT); dc = dc->next) {
57 		switch (dc->method) {
58 		case PPMDC_CPUSPEEDKIO:
59 			ret = ldi_ioctl(dc->lh, dc->m_un.cpu.iowr,
60 			    (intptr_t)index, FWRITE | FKIOCTL, kcred, NULL);
61 			if (ret)
62 				return (ret);
63 			break;
64 
65 		default:
66 			PPMD(D_CPU, ("%s: unsupported method(0x%x)\n",
67 			    str, dc->method))
68 			return (-1);
69 		}
70 	}
71 	return (ret);
72 }
73 
74 /*
75  * PPMDC_PRE_CHNG operation
76  */
77 static int
78 ppm_cpu_pre_chng(ppm_domain_t *domp, int oldl, int speedup)
79 {
80 #ifdef DEBUG
81 	char *str = "ppm_cpu_pre_chng";
82 #endif
83 	ppm_dc_t *dc;
84 	int lowest;
85 	int ret = 0;
86 
87 	dc = ppm_lookup_dc(domp, PPMDC_PRE_CHNG);
88 	for (; dc && (dc->cmd == PPMDC_PRE_CHNG); dc = dc->next) {
89 
90 		switch (dc->method) {
91 		case PPMDC_VCORE:
92 			lowest = domp->devlist->lowest;
93 			if ((oldl != lowest) || (speedup != 1))
94 				break;
95 
96 			/* raise core voltage */
97 			if (ppm_do_vcore > 0) {
98 				ret = ldi_ioctl(dc->lh,
99 				    dc->m_un.cpu.iowr,
100 				    (intptr_t)&dc->m_un.cpu.val,
101 				    FWRITE | FKIOCTL, kcred, NULL);
102 				if (ret != 0)
103 					return (ret);
104 				if (dc->m_un.cpu.delay > 0)
105 					drv_usecwait(dc->m_un.cpu.delay);
106 			}
107 			break;
108 
109 		default:
110 			PPMD(D_CPU, ("%s: unsupported method(0x%x)\n",
111 			    str, dc->method))
112 			return (-1);
113 		}
114 	}
115 
116 	return (ret);
117 }
118 
119 /*
120  * PPMDC_CPU_GO operation
121  */
122 /* ARGSUSED */
123 static int
124 ppm_cpu_go(ppm_domain_t *domp, int level)
125 {
126 	ppm_dc_t *dc;
127 	int ret = 0;
128 
129 	dc = ppm_lookup_dc(domp, PPMDC_CPU_GO);
130 	if (dc == NULL) {
131 		return (ret);
132 	}
133 	switch (dc->method) {
134 	case PPMDC_KIO:
135 		ret = ldi_ioctl(dc->lh, dc->m_un.kio.iowr,
136 		    (intptr_t)dc->m_un.kio.val, FWRITE | FKIOCTL,
137 		    kcred, NULL);
138 		break;
139 	default:
140 		return (-1);
141 	}
142 
143 	return (ret);
144 }
145 
146 /*
147  * PPMDC_POST_CHNG operation
148  */
149 static int
150 ppm_cpu_post_chng(ppm_domain_t *domp, int newl, int speedup)
151 {
152 #ifdef DEBUG
153 	char *str = "ppm_cpu_post_chng";
154 #endif
155 	ppm_dc_t *dc;
156 	int	lowest;
157 	int ret = 0;
158 
159 	dc = ppm_lookup_dc(domp, PPMDC_POST_CHNG);
160 	for (; dc && (dc->cmd == PPMDC_POST_CHNG); dc = dc->next) {
161 
162 		switch (dc->method) {
163 		case PPMDC_VCORE:
164 			lowest = domp->devlist->lowest;
165 			if ((newl != lowest) || (speedup != 0))
166 				break;
167 
168 			/* lower core voltage */
169 			if (ppm_do_vcore > 0) {
170 				ret = ldi_ioctl(dc->lh,
171 				    dc->m_un.cpu.iowr,
172 				    (intptr_t)&dc->m_un.cpu.val,
173 				    FWRITE | FKIOCTL, kcred, NULL);
174 				if (ret != 0)
175 					return (ret);
176 				if (dc->m_un.cpu.delay > 0)
177 					drv_usecwait(dc->m_un.cpu.delay);
178 			}
179 			break;
180 
181 		default:
182 			PPMD(D_CPU, ("%s: unsupported method(0x%x)\n",
183 			    str, dc->method))
184 			return (-1);
185 		}
186 	}
187 	return (ret);
188 }
189 
190 /*
191  * The effective cpu estar model is: program all cpus to be ready to go
192  * the same next(or new) speed level, program all other system bus resident
193  * devices to the same next speed level.  At last, pull the trigger to
194  * initiate the speed change for all system bus resident devices
195  * simultaneously.
196  *
197  * On Excalibur, the Safari bus resident devices are Cheetah/Cheetah+ and
198  * Schizo.  On Enchilada, the JBus resident devides are Jalapeno(s) and
199  * Tomatillo(s).
200  */
201 int
202 ppm_change_cpu_power(ppm_dev_t *ppmd, int newlevel)
203 {
204 #ifdef DEBUG
205 	char *str = "ppm_change_cpu_power";
206 #endif
207 	ppm_unit_t *unitp;
208 	ppm_domain_t *domp;
209 	ppm_dev_t *cpup;
210 	dev_info_t *dip;
211 	int level, oldlevel;
212 	int speedup, incr, lowest, highest;
213 	char *chstr;
214 	int ret;
215 
216 	unitp = ddi_get_soft_state(ppm_statep, ppm_inst);
217 	ASSERT(unitp);
218 	domp = ppmd->domp;
219 	cpup = domp->devlist;
220 	lowest = cpup->lowest;
221 	highest = cpup->highest;
222 
223 	/*
224 	 * Not all cpus may have transitioned to a known level by this time
225 	 */
226 	oldlevel = (cpup->level == PM_LEVEL_UNKNOWN) ? highest : cpup->level;
227 	dip = cpup->dip;
228 	ASSERT(dip);
229 
230 	PPMD(D_CPU, ("%s: old %d, new %d, highest %d, lowest %d\n",
231 	    str, oldlevel, newlevel, highest, lowest))
232 
233 	if (newlevel > oldlevel) {
234 		chstr = "UP";
235 		speedup = 1;
236 		incr = 1;
237 	} else if (newlevel < oldlevel) {
238 		chstr = "DOWN";
239 		speedup = 0;
240 		incr = -1;
241 	} else
242 		return (DDI_SUCCESS);
243 
244 	/*
245 	 * This loop will execute 1x or 2x depending on
246 	 * number of times we need to change clock rates
247 	 */
248 	for (level = oldlevel+incr; level != newlevel+incr; level += incr) {
249 		/* bring each cpu to next level */
250 		for (; cpup; cpup = cpup->next) {
251 			if (cpup->level == level)
252 				continue;
253 
254 			ret = pm_power(cpup->dip, 0, level);
255 			PPMD(D_CPU, ("%s: \"%s\", %s to level %d, ret %d\n",
256 			    str, cpup->path, chstr, level, ret))
257 			if (ret == DDI_SUCCESS) {
258 				cpup->level = level;
259 				cpup->rplvl = PM_LEVEL_UNKNOWN;
260 				continue;
261 			}
262 
263 			/*
264 			 * if the driver was unable to lower cpu speed,
265 			 * the cpu probably got busy; set the previous
266 			 * cpus back to the original level
267 			 */
268 			if (speedup == 0)
269 				ret = ppm_revert_cpu_power(cpup, level - incr);
270 			return (ret);
271 		}
272 		cpup = domp->devlist;
273 
274 		/*
275 		 * set bus resident devices at next speed level
276 		 */
277 		ret = ppm_cpu_next(domp, level);
278 		if (ret != 0) {
279 			(void) ppm_revert_cpu_power(cpup, level - incr);
280 			return (ret);
281 		}
282 
283 		/*
284 		 * platform dependent various operations before
285 		 * initiating cpu speed change
286 		 */
287 		ret = ppm_cpu_pre_chng(domp, level - incr, speedup);
288 		if (ret != 0) {
289 			(void) ppm_revert_cpu_power(cpup, level - incr);
290 			(void) ppm_cpu_next(domp, level - incr);
291 			return (ret);
292 		}
293 
294 		/*
295 		 * the following 1us delay is actually required for us3i only.
296 		 * on us3i system, entering estar mode from full requires
297 		 * to set mcu to single fsm state followed by 1us delay
298 		 * before trigger actual transition.  The mcu part is
299 		 * handled in us_drv, the delay is here.
300 		 */
301 		if ((oldlevel == highest) && (speedup == 0))
302 			drv_usecwait(1);
303 
304 		/*
305 		 * initiate cpu speed change
306 		 */
307 		ret = ppm_cpu_go(domp, level);
308 		if (ret != 0) {
309 			(void) ppm_revert_cpu_power(cpup, level - incr);
310 			(void) ppm_cpu_next(domp, level - incr);
311 			return (ret);
312 		}
313 
314 		/*
315 		 * platform dependent operations post cpu speed change
316 		 */
317 		ret = ppm_cpu_post_chng(domp, level, speedup);
318 		if (ret != 0)
319 			return (ret);
320 
321 	}   /* end of looping each level */
322 
323 	return (DDI_SUCCESS);
324 }
325 
326 /*
327  * This handles the power-on case where cpu power level is
328  * PM_LEVEL_UNKNOWN.  Per agreement with OBP, cpus always
329  * boot up at full speed.  In fact, we must not making calls
330  * into tomtppm or schppm to trigger cpu speed change to a
331  * different level at early boot time since some cpu may not
332  * be ready, causing xc_one() to fail silently.
333  *
334  * Here we simply call pm_power() to get the power level updated
335  * in pm and ppm. Had xc_one() failed silently inside us_power()
336  * at this time we're unaffected.
337  */
338 boolean_t
339 ppm_manage_early_cpus(dev_info_t *dip, int new, int *result)
340 {
341 	ppm_dev_t *ppmd = PPM_GET_PRIVATE(dip);
342 	int ret;
343 
344 	if (ppmd->level == PM_LEVEL_UNKNOWN && new == ppmd->highest) {
345 		ret = pm_power(dip, 0, new);
346 		if (ret != DDI_SUCCESS) {
347 			PPMD(D_CPU, ("ppm_manage_early_cpus: pm_power() "
348 			    "failed to change power level to %d", new))
349 		} else {
350 			ppmd->level = new;
351 			ppmd->rplvl = PM_LEVEL_UNKNOWN;
352 		}
353 		*result = ret;
354 		return (B_TRUE);
355 	}
356 	return (B_FALSE);
357 }
358