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
ppm_cpu_next(ppm_domain_t * domp,int level)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
ppm_cpu_pre_chng(ppm_domain_t * domp,int oldl,int speedup)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
ppm_cpu_go(ppm_domain_t * domp,int level)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
ppm_cpu_post_chng(ppm_domain_t * domp,int newl,int speedup)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
ppm_change_cpu_power(ppm_dev_t * ppmd,int newlevel)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
ppm_manage_early_cpus(dev_info_t * dip,int new,int * result)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