]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (c) 2005-2006 Apple Computer, Inc. All rights reserved. | |
3 | * | |
4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. The rights granted to you under the License | |
10 | * may not be used to create, or enable the creation or redistribution of, | |
11 | * unlawful or unlicensed copies of an Apple operating system, or to | |
12 | * circumvent, violate, or enable the circumvention or violation of, any | |
13 | * terms of an Apple operating system software license agreement. | |
14 | * | |
15 | * Please obtain a copy of the License at | |
16 | * http://www.opensource.apple.com/apsl/ and read it before using this file. | |
17 | * | |
18 | * The Original Code and all software distributed under the License are | |
19 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
20 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
21 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
22 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
23 | * Please see the License for the specific language governing rights and | |
24 | * limitations under the License. | |
25 | * | |
26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ | |
27 | */ | |
28 | #include <machine/machine_routines.h> | |
29 | #include <machine/machine_cpu.h> | |
30 | #ifdef __ppc__ | |
31 | # include <ppc/exception.h> | |
32 | # include <ppc/misc_protos.h> | |
33 | # include <ppc/cpu_internal.h> | |
34 | #else | |
35 | # include <i386/cpu_data.h> | |
36 | # include <i386/misc_protos.h> | |
37 | #endif | |
38 | #include <machine/pmap.h> | |
39 | #include <kern/pms.h> | |
40 | #include <kern/processor.h> | |
41 | #include <kern/kalloc.h> | |
42 | #include <vm/vm_protos.h> | |
43 | ||
44 | extern int is_suser(void); | |
45 | ||
46 | static uint32_t pmsSyncrolator = 0; /* Only one control operation at a time please */ | |
47 | uint32_t pmsBroadcastWait = 0; /* Number of outstanding broadcasts */ | |
48 | ||
49 | int pmsInstalled = 0; /* Power Management Stepper can run and has table installed */ | |
50 | int pmsExperimental = 0; /* Power Management Stepper in experimental mode */ | |
51 | decl_simple_lock_data(,pmsBuildLock) /* Make sure only one guy can replace table at the same time */ | |
52 | ||
53 | static pmsDef *altDpmsTab; /* Alternate step definition table */ | |
54 | static uint32_t altDpmsTabSize = 0; /* Size of alternate step definition table */ | |
55 | ||
56 | pmsDef pmsDummy = { /* This is the dummy step for initialization. All it does is to park */ | |
57 | .pmsLimit = 0, /* Time doesn't matter for a park */ | |
58 | .pmsStepID = pmsMaxStates - 1, /* Use the very last ID number for the dummy */ | |
59 | .pmsSetCmd = pmsParkIt, /* Force us to be parked */ | |
60 | .sf.pmsSetFuncInd = 0, /* No platform call for this one */ | |
61 | .pmsDown = pmsPrepSleep, /* We always park */ | |
62 | .pmsNext = pmsPrepSleep /* We always park */ | |
63 | }; | |
64 | ||
65 | pmsStat pmsStatsd[4][pmsMaxStates]; /* Generate enough statistics blocks for 4 processors */ | |
66 | ||
67 | pmsCtl pmsCtls = { /* Power Management Stepper control */ | |
68 | .pmsStats = pmsStatsd, | |
69 | }; | |
70 | ||
71 | pmsSetFunc_t pmsFuncTab[pmsSetFuncMax] = {NULL}; /* This is the function index table */ | |
72 | pmsQueryFunc_t pmsQueryFunc; /* Pointer to pmsQuery function */ | |
73 | uint32_t pmsPlatformData = 0; /* Data provided by and passed to platform functions */ | |
74 | ||
75 | #ifdef __ppc__ | |
76 | # define PER_PROC_INFO struct per_proc_info | |
77 | # define GET_PER_PROC_INFO() getPerProc() | |
78 | #else | |
79 | # define PER_PROC_INFO cpu_data_t | |
80 | # define GET_PER_PROC_INFO() current_cpu_datap() | |
81 | #endif | |
82 | ||
83 | ||
84 | ||
85 | /* | |
86 | * Do any initialization needed | |
87 | */ | |
88 | ||
89 | void | |
90 | pmsInit(void) | |
91 | { | |
92 | int i; | |
93 | ||
94 | simple_lock_init(&pmsBuildLock, 0); /* Initialize the build lock */ | |
95 | for(i = 0; i < pmsMaxStates; i++) pmsCtls.pmsDefs[i] = &pmsDummy; /* Initialize the table to dummy steps */ | |
96 | ||
97 | pmsCPUMachineInit(); | |
98 | } | |
99 | ||
100 | ||
101 | /* | |
102 | * Start the power management stepper on all processors | |
103 | * | |
104 | * All processors must be parked. This should be called when the hardware | |
105 | * is ready to step. Probably only at boot and after wake from sleep. | |
106 | * | |
107 | */ | |
108 | ||
109 | void | |
110 | pmsStart(void) | |
111 | { | |
112 | boolean_t intr; | |
113 | ||
114 | if(!pmsInstalled) | |
115 | return; /* We can't do this if no table installed */ | |
116 | ||
117 | intr = ml_set_interrupts_enabled(FALSE); /* No interruptions in here */ | |
118 | pmsRun(pmsStartUp); /* Start running the stepper everywhere */ | |
119 | (void)ml_set_interrupts_enabled(intr); /* Restore interruptions */ | |
120 | } | |
121 | ||
122 | ||
123 | /* | |
124 | * Park the stepper execution. This will force the stepper on this | |
125 | * processor to abandon its current step and stop. No changes to the | |
126 | * hardware state is made and any previous step is lost. | |
127 | * | |
128 | * This is used as the initial state at startup and when the step table | |
129 | * is being changed. | |
130 | * | |
131 | */ | |
132 | ||
133 | void | |
134 | pmsPark(void) | |
135 | { | |
136 | boolean_t intr; | |
137 | ||
138 | if(!pmsInstalled) | |
139 | return; /* We can't do this if no table installed */ | |
140 | ||
141 | intr = ml_set_interrupts_enabled(FALSE); /* No interruptions in here */ | |
142 | pmsSetStep(pmsParked, 0); /* Park the stepper */ | |
143 | (void)ml_set_interrupts_enabled(intr); /* Restore interruptions */ | |
144 | } | |
145 | ||
146 | ||
147 | /* | |
148 | * Steps down to a lower power. | |
149 | * Interrupts must be off... | |
150 | */ | |
151 | ||
152 | void | |
153 | pmsDown(void) | |
154 | { | |
155 | PER_PROC_INFO *pp; | |
156 | uint32_t nstate; | |
157 | ||
158 | pp = GET_PER_PROC_INFO(); /* Get our per_proc */ | |
159 | ||
160 | if(!pmsInstalled || pp->pms.pmsState == pmsParked) | |
161 | return; /* No stepping if parked or not installed */ | |
162 | ||
163 | nstate = pmsCtls.pmsDefs[pp->pms.pmsState]->pmsDown; /* Get the downward step */ | |
164 | pmsSetStep(nstate, 0); /* Step to it */ | |
165 | } | |
166 | ||
167 | ||
168 | /* | |
169 | * Steps up to a higher power. The "timer" parameter is true if the | |
170 | * step was driven due to the pms timer expiring. | |
171 | * | |
172 | * Interrupts must be off... | |
173 | */ | |
174 | ||
175 | int pmsStepIdleSneaks; | |
176 | int pmsStepIdleTries; | |
177 | ||
178 | void | |
179 | pmsStep(int timer) | |
180 | { | |
181 | PER_PROC_INFO *pp; | |
182 | uint32_t nstate; | |
183 | uint32_t tstate; | |
184 | uint32_t pkgstate; | |
185 | int dir; | |
186 | int i; | |
187 | ||
188 | pp = GET_PER_PROC_INFO(); /* Get our per_proc */ | |
189 | ||
190 | if(!pmsInstalled || pp->pms.pmsState == pmsParked) | |
191 | return; /* No stepping if parked or not installed */ | |
192 | ||
193 | /* | |
194 | * Assume a normal step. | |
195 | */ | |
196 | nstate = pmsCtls.pmsDefs[pp->pms.pmsState]->pmsNext; | |
197 | ||
198 | /* | |
199 | * If we are idling and being asked to step up, check to see whether | |
200 | * the package we're in is already at a non-idle power state. If so, | |
201 | * attempt to work out what state that is, and go there directly to | |
202 | * avoid wasting time ramping up. | |
203 | */ | |
204 | if ((pp->pms.pmsState == pmsIdle) | |
205 | && ((pkgstate = pmsCPUPackageQuery()) != ~(uint32_t)0)) { | |
206 | /* | |
207 | * Search forward through the stepper program, | |
208 | * avoid looping for too long. | |
209 | */ | |
210 | tstate = nstate; | |
211 | pmsStepIdleTries++; | |
212 | for (i = 0; i < 32; i++) { | |
213 | /* | |
214 | * Compare command with current package state | |
215 | */ | |
216 | if ((pmsCtls.pmsDefs[tstate]->pmsSetCmd & pmsCPU) == pkgstate) { | |
217 | nstate = tstate; | |
218 | pmsStepIdleSneaks++; | |
219 | break; | |
220 | } | |
221 | ||
222 | /* | |
223 | * Advance to the next step in the program. | |
224 | */ | |
225 | if (pmsCtls.pmsDefs[tstate]->pmsNext == tstate) | |
226 | break; /* infinite loop */ | |
227 | tstate = pmsCtls.pmsDefs[tstate]->pmsNext; | |
228 | } | |
229 | } | |
230 | ||
231 | /* | |
232 | * Default to a step up. | |
233 | */ | |
234 | dir = 1; | |
235 | ||
236 | /* | |
237 | * If we are stepping as a consequence of timer expiry, select the | |
238 | * alternate exit path and note this as downward step for accounting | |
239 | * purposes. | |
240 | */ | |
241 | if (timer | |
242 | && (pmsCtls.pmsDefs[pp->pms.pmsState]->pmsSetCmd == pmsDelay)) { | |
243 | nstate = pmsCtls.pmsDefs[pp->pms.pmsState]->pmsTDelay; | |
244 | ||
245 | /* | |
246 | * Delayed steps are a step down for accounting purposes. | |
247 | */ | |
248 | dir = 0; | |
249 | } | |
250 | ||
251 | pmsSetStep(nstate, dir); | |
252 | } | |
253 | ||
254 | ||
255 | /* | |
256 | * Set a specific step | |
257 | * | |
258 | * We do not do statistics if exiting park | |
259 | * Interrupts must be off... | |
260 | * | |
261 | */ | |
262 | ||
263 | void | |
264 | pmsSetStep(uint32_t nstep, int dir) | |
265 | { | |
266 | PER_PROC_INFO *pp; | |
267 | uint32_t pstate, nCSetCmd, mCSetCmd; | |
268 | pmsDef *pnstate, *pcstate; | |
269 | uint64_t tb, dur; | |
270 | int cpu; | |
271 | ||
272 | pp = GET_PER_PROC_INFO(); /* Get our per_proc */ | |
273 | cpu = cpu_number(); /* Get our processor */ | |
274 | ||
275 | while(1) { /* Keep stepping until we get a delay */ | |
276 | ||
277 | if(pp->pms.pmsCSetCmd & pmsMustCmp) { /* Do we have to finish the delay before changing? */ | |
278 | while(mach_absolute_time() < pp->pms.pmsPop); /* Yes, spin here... */ | |
279 | } | |
280 | ||
281 | if((nstep == pmsParked) || ((uint32_t)pmsCtls.pmsDefs[nstep]->pmsSetCmd == pmsParkIt)) { /* Are we parking? */ | |
282 | ||
283 | tb = mach_absolute_time(); /* What time is it? */ | |
284 | pp->pms.pmsStamp = tb; /* Show transition now */ | |
285 | pp->pms.pmsPop = HalfwayToForever; /* Set the pop way into the future */ | |
286 | pp->pms.pmsState = pmsParked; /* Make sure we are parked */ | |
287 | etimer_resync_deadlines(); /* Cancel our timer if going */ | |
288 | return; | |
289 | } | |
290 | ||
291 | pnstate = pmsCtls.pmsDefs[nstep]; /* Point to the state definition */ | |
292 | pstate = pp->pms.pmsState; /* Save the current step */ | |
293 | pp->pms.pmsState = nstep; /* Set the current to the next step */ | |
294 | ||
295 | if(pnstate->pmsSetCmd != pmsDelay) { /* If this is not a delayed state, change the actual hardware now */ | |
296 | if(pnstate->pmsSetCmd & pmsCngCPU) pmsCPUSet(pnstate->pmsSetCmd); /* We have some CPU work to do... */ | |
297 | if((uint32_t)pnstate->sf.pmsSetFunc) pnstate->sf.pmsSetFunc(pnstate->pmsSetCmd, cpu, pmsPlatformData); /* Tell the platform to set power mode */ | |
298 | ||
299 | mCSetCmd = pnstate->pmsSetCmd & (pmsCngXClk | pmsCngCPU | pmsCngVolt); /* Isolate just the change flags */ | |
300 | mCSetCmd = (mCSetCmd - (mCSetCmd >> 7)) | pmsSync | pmsMustCmp | pmsPowerID; /* Form mask of bits that come from new command */ | |
301 | nCSetCmd = pp->pms.pmsCSetCmd & ~mCSetCmd; /* Clear changing bits */ | |
302 | nCSetCmd = nCSetCmd | (pnstate->pmsSetCmd & mCSetCmd); /* Flip on the changing bits and the always copy bits */ | |
303 | ||
304 | pp->pms.pmsCSetCmd = nCSetCmd; /* Set it for real */ | |
305 | } | |
306 | ||
307 | tb = mach_absolute_time(); /* What time is it? */ | |
308 | pp->pms.pmsPop = tb + pnstate->pmsLimit; /* Set the next pop */ | |
309 | ||
310 | if((pnstate->pmsSetCmd != pmsDelay) && (pp->pms.pmsCSetCmd & pmsSync) && (pnstate->pmsLimit != 0)) { /* Is this a synchronous command with a delay? */ | |
311 | while(mach_absolute_time() < pp->pms.pmsPop); /* Yes, spin here and wait it out... */ | |
312 | } | |
313 | ||
314 | /* | |
315 | * Gather some statistics | |
316 | */ | |
317 | ||
318 | dur = tb - pp->pms.pmsStamp; /* Get the amount of time we were in the old step */ | |
319 | pp->pms.pmsStamp = tb; /* Set the new timestamp */ | |
320 | if(!(pstate == pmsParked)) { /* Only take stats if we were not parked */ | |
321 | pcstate = pmsCtls.pmsDefs[pstate]; /* Get the previous step */ | |
322 | pmsCtls.pmsStats[cpu][pcstate->pmsStepID].stTime[dir] += dur; /* Accumulate the total time in the old step */ | |
323 | pmsCtls.pmsStats[cpu][pcstate->pmsStepID].stCnt[dir] += 1; /* Count transitions */ | |
324 | } | |
325 | ||
326 | /* | |
327 | * See if we are done chaining steps | |
328 | */ | |
329 | ||
330 | if((pnstate->pmsSetCmd == pmsDelay) | |
331 | || (!(pp->pms.pmsCSetCmd & pmsSync) && (pnstate->pmsLimit != 0))) { /* Is this not syncronous and a non-zero delay or a delayed step? */ | |
332 | etimer_resync_deadlines(); /* Start the timers ticking */ | |
333 | break; /* We've stepped as far as we're going to... */ | |
334 | } | |
335 | ||
336 | nstep = pnstate->pmsNext; /* Chain on to the next */ | |
337 | } | |
338 | } | |
339 | ||
340 | /* | |
341 | * Either park the stepper or force the step on a parked stepper for local processor only | |
342 | * | |
343 | */ | |
344 | ||
345 | void | |
346 | pmsRunLocal(uint32_t nstep) | |
347 | { | |
348 | PER_PROC_INFO *pp; | |
349 | uint32_t lastState; | |
350 | int cpu, i; | |
351 | boolean_t intr; | |
352 | ||
353 | if(!pmsInstalled) /* Ignore this if no step programs installed... */ | |
354 | return; | |
355 | ||
356 | intr = ml_set_interrupts_enabled(FALSE); /* No interruptions in here */ | |
357 | ||
358 | pp = GET_PER_PROC_INFO(); /* Get our per_proc */ | |
359 | ||
360 | if(nstep == pmsStartUp) { /* Should we start up? */ | |
361 | pmsCPUInit(); /* Get us up to full with high voltage and park */ | |
362 | nstep = pmsNormHigh; /* Change request to transition to normal high */ | |
363 | } | |
364 | ||
365 | lastState = pp->pms.pmsState; /* Remember if we are parked now */ | |
366 | ||
367 | pmsSetStep(nstep, 1); /* Step to the new state */ | |
368 | ||
369 | if((lastState == pmsParked) && (pp->pms.pmsState != pmsParked)) { /* Did we just unpark? */ | |
370 | cpu = cpu_number(); /* Get our processor */ | |
371 | for(i = 0; i < pmsMaxStates; i++) { /* Step through the steps and clear the statistics since we were parked */ | |
372 | pmsCtls.pmsStats[cpu][i].stTime[0] = 0; /* Clear accumulated time - downward */ | |
373 | pmsCtls.pmsStats[cpu][i].stTime[1] = 0; /* Clear accumulated time - forward */ | |
374 | pmsCtls.pmsStats[cpu][i].stCnt[0] = 0; /* Clear transition count - downward */ | |
375 | pmsCtls.pmsStats[cpu][i].stCnt[1] = 0; /* Clear transition count - forward */ | |
376 | } | |
377 | } | |
378 | ||
379 | (void)ml_set_interrupts_enabled(intr); /* Restore interruptions */ | |
380 | } | |
381 | ||
382 | /* | |
383 | * Control the Power Management Stepper. | |
384 | * Called from user state by the superuser. | |
385 | * Interruptions disabled. | |
386 | * | |
387 | */ | |
388 | kern_return_t | |
389 | pmsControl(uint32_t request, user_addr_t reqaddr, uint32_t reqsize) | |
390 | { | |
391 | uint32_t nstep = 0, result, presult; | |
392 | int ret, cpu; | |
393 | kern_return_t kret = KERN_SUCCESS; | |
394 | pmsDef *ndefs; | |
395 | PER_PROC_INFO *pp; | |
396 | ||
397 | pp = GET_PER_PROC_INFO(); /* Get our per_proc */ | |
398 | cpu = cpu_number(); /* Get our processor */ | |
399 | ||
400 | if(!is_suser()) { /* We are better than most, */ | |
401 | kret = KERN_FAILURE; | |
402 | goto out; | |
403 | } | |
404 | ||
405 | if(request >= pmsCFree) { /* Can we understand the request? */ | |
406 | kret = KERN_INVALID_ARGUMENT; | |
407 | goto out; | |
408 | } | |
409 | ||
410 | if(request == pmsCQuery) { /* Are we just checking? */ | |
411 | result = pmsCPUQuery() & pmsCPU; /* Get the processor data and make sure there is no slop */ | |
412 | presult = 0; /* Assume nothing */ | |
413 | if((uint32_t)pmsQueryFunc) | |
414 | presult = pmsQueryFunc(cpu, pmsPlatformData); /* Go get the platform state */ | |
415 | kret = result | (presult & (pmsXClk | pmsVoltage | pmsPowerID)); /* Merge the platform state with no slop */ | |
416 | goto out; | |
417 | } | |
418 | ||
419 | if(request == pmsCExperimental) { /* Enter experimental mode? */ | |
420 | ||
421 | if(pmsInstalled || (pmsExperimental & 1)) { /* Are we already running or in experimental? */ | |
422 | kret = KERN_FAILURE; | |
423 | goto out; | |
424 | } | |
425 | ||
426 | pmsExperimental |= 1; /* Flip us into experimental but don't change other flags */ | |
427 | ||
428 | pmsCPUConf(); /* Configure for this machine */ | |
429 | pmsStart(); /* Start stepping */ | |
430 | goto out; | |
431 | } | |
432 | ||
433 | if(request == pmsCCnfg) { /* Do some up-front checking before we commit to doing this */ | |
434 | if((reqsize > (pmsMaxStates * sizeof(pmsDef))) || (reqsize < (pmsFree * sizeof(pmsDef)))) { /* Check that the size is reasonable */ | |
435 | kret = KERN_NO_SPACE; | |
436 | goto out; | |
437 | } | |
438 | } | |
439 | ||
440 | if (request == pmsGCtls) { | |
441 | if (reqsize != sizeof(pmsCtls)) { | |
442 | kret = KERN_FAILURE; | |
443 | goto out; | |
444 | } | |
445 | ret = copyout(&pmsCtls, reqaddr, reqsize); | |
446 | goto out; | |
447 | } | |
448 | ||
449 | if (request == pmsGStats) { | |
450 | if (reqsize != sizeof(pmsStatsd)) { /* request size is fixed */ | |
451 | kret = KERN_FAILURE; | |
452 | goto out; | |
453 | } | |
454 | ret = copyout(&pmsStatsd, reqaddr, reqsize); | |
455 | goto out; | |
456 | } | |
457 | ||
458 | /* | |
459 | * We are committed after here. If there are any errors detected, we shouldn't die, but we | |
460 | * will be stuck in park. | |
461 | * | |
462 | * Also, we can possibly end up on another processor after the broadcast. | |
463 | * | |
464 | */ | |
465 | ||
466 | if(!hw_compare_and_store(0, 1, &pmsSyncrolator)) { /* Are we already doing this? */ | |
467 | /* Tell them that we are already busy and to try again */ | |
468 | kret = KERN_RESOURCE_SHORTAGE; | |
469 | goto out; | |
470 | } | |
471 | ||
472 | // NOTE: We will block in the following code until everyone has finished the prepare | |
473 | ||
474 | pmsRun(pmsPrepCng); /* Get everyone parked and in a proper state for step table changes, including me */ | |
475 | ||
476 | if(request == pmsCPark) { /* Is all we're supposed to do park? */ | |
477 | pmsSyncrolator = 0; /* Free us up */ | |
478 | goto out; | |
479 | } | |
480 | ||
481 | switch(request) { /* Select the routine */ | |
482 | ||
483 | case pmsCStart: /* Starts normal steppping */ | |
484 | nstep = pmsNormHigh; /* Set the request */ | |
485 | break; | |
486 | ||
487 | case pmsCFLow: /* Forces low power */ | |
488 | nstep = pmsLow; /* Set request */ | |
489 | break; | |
490 | ||
491 | case pmsCFHigh: /* Forces high power */ | |
492 | nstep = pmsHigh; /* Set request */ | |
493 | break; | |
494 | ||
495 | case pmsCCnfg: /* Loads new stepper program */ | |
496 | ||
497 | if(!(ndefs = (pmsDef *)kalloc(reqsize))) { /* Get memory for the whole thing */ | |
498 | pmsSyncrolator = 0; /* Free us up */ | |
499 | kret = KERN_INVALID_ADDRESS; | |
500 | goto out; | |
501 | } | |
502 | ||
503 | ret = copyin(reqaddr, (void *)ndefs, reqsize); /* Get the new config table */ | |
504 | if(ret) { /* Hmmm, something went wrong with the copyin */ | |
505 | kfree(ndefs, reqsize); /* Free up the copied in data */ | |
506 | pmsSyncrolator = 0; /* Free us up */ | |
507 | kret = KERN_INVALID_ADDRESS; | |
508 | goto out; | |
509 | } | |
510 | ||
511 | kret = pmsBuild(ndefs, reqsize, NULL, 0, NULL); /* Go build and replace the tables. Make sure we keep the old platform stuff */ | |
512 | if(kret) { /* Hmmm, something went wrong with the compilation */ | |
513 | kfree(ndefs, reqsize); /* Free up the copied in data */ | |
514 | pmsSyncrolator = 0; /* Free us up */ | |
515 | goto out; | |
516 | } | |
517 | ||
518 | nstep = pmsNormHigh; /* Set the request */ | |
519 | break; | |
520 | ||
521 | default: | |
522 | panic("pmsCntrl: stepper control is so very, very confused = %08X\n", request); | |
523 | ||
524 | } | |
525 | ||
526 | pmsRun(nstep); /* Get everyone into step */ | |
527 | pmsSyncrolator = 0; /* Free us up */ | |
528 | out: | |
529 | return kret; | |
530 | ||
531 | } | |
532 | ||
533 | /* | |
534 | * Broadcast a change to all processors including ourselves. | |
535 | * | |
536 | * Interruptions are disabled. | |
537 | */ | |
538 | ||
539 | void | |
540 | pmsRun(uint32_t nstep) | |
541 | { | |
542 | pmsCPURun(nstep); | |
543 | } | |
544 | ||
545 | ||
546 | /* | |
547 | * Build the tables needed for the stepper. This includes both the step definitions and the step control table. | |
548 | * | |
549 | * We most absolutely need to be parked before this happens because we're gonna change the table. | |
550 | * We're going to have to be pretty complete about checking for errors. | |
551 | * Also, a copy is always made because we don't want to be crippled by not being able to change | |
552 | * the table or description formats. | |
553 | * | |
554 | * We pass in a table of external functions and the new stepper def uses the corresponding | |
555 | * indexes rather than actual function addresses. This is done so that a proper table can be | |
556 | * built with the control syscall. It can't supply addresses, so the index has to do. We | |
557 | * internalize the table so our caller does not need to keep it. Note that passing in a 0 | |
558 | * will use the current function table. Also note that entry 0 is reserved and must be 0, | |
559 | * we will check and fail the build. | |
560 | * | |
561 | * The platformData parameter is a 32-bit word of data that is passed unaltered to the set function. | |
562 | * | |
563 | * The queryFunc parameter is the address of a function that will return the current state of the platform. | |
564 | * The format of the data returned is the same as the platform specific portions of pmsSetCmd, i.e., pmsXClk, | |
565 | * pmsVoltage, and any part of pmsPowerID that is maintained by the platform hardware (an example would be | |
566 | * the values of the gpios that correspond to pmsPowerID). The value should be constructed by querying | |
567 | * hardware rather than returning a value cached by software. One of the intents of this function is to | |
568 | * help recover lost or determine initial power states. | |
569 | * | |
570 | */ | |
571 | ||
572 | kern_return_t pmsBuild(pmsDef *pd, uint32_t pdsize, pmsSetFunc_t *functab, uint32_t platformData, pmsQueryFunc_t queryFunc) { | |
573 | ||
574 | int newsize, cstp, oldAltSize, xdsply; | |
575 | uint32_t setf, steps, i, nstps; | |
576 | uint64_t nlimit; | |
577 | pmsDef *newpd, *oldAlt; | |
578 | boolean_t intr; | |
579 | ||
580 | xdsply = (pmsExperimental & 3) != 0; /* Turn on kprintfs if requested or in experimental mode */ | |
581 | ||
582 | if(pdsize % sizeof(pmsDef)) | |
583 | return KERN_INVALID_ARGUMENT; /* Length not multiple of definition size */ | |
584 | ||
585 | steps = pdsize / sizeof(pmsDef); /* Get the number of steps supplied */ | |
586 | ||
587 | if((steps >= pmsMaxStates) || (steps < pmsFree)) /* Complain if too big or too small */ | |
588 | return KERN_INVALID_ARGUMENT; /* Squeak loudly!!! */ | |
589 | ||
590 | if((uint32_t)functab && (uint32_t)functab[0]) /* Verify that if they supplied a new function table, entry 0 is 0 */ | |
591 | return KERN_INVALID_ARGUMENT; /* Fail because they didn't reserve entry 0 */ | |
592 | ||
593 | if(xdsply) kprintf("\n StepID Down Next HWSel HWfun Limit\n"); | |
594 | ||
595 | for(i = 0; i < steps; i++) { /* Step through and verify the definitions */ | |
596 | ||
597 | if(xdsply) kprintf(" %6d %6d %6d %08X %6d %20lld\n", pd[i].pmsStepID, pd[i].pmsDown, | |
598 | pd[i].pmsNext, pd[i].pmsSetCmd, | |
599 | pd[i].sf.pmsSetFuncInd, pd[i].pmsLimit); | |
600 | ||
601 | if((pd[i].pmsLimit != 0) && (pd[i].pmsLimit < 100ULL)) { | |
602 | if(xdsply) kprintf("error step %3d: pmsLimit too small/n", i); | |
603 |