root / prex-0.9.0 / bsp / drv / dev / base / pm.c @ 03e9c04a
History | View | Annotate | Download (10.2 KB)
1 |
/*-
|
---|---|
2 |
* Copyright (c) 2005-2009, Kohsuke Ohtani
|
3 |
* All rights reserved.
|
4 |
*
|
5 |
* Redistribution and use in source and binary forms, with or without
|
6 |
* modification, are permitted provided that the following conditions
|
7 |
* are met:
|
8 |
* 1. Redistributions of source code must retain the above copyright
|
9 |
* notice, this list of conditions and the following disclaimer.
|
10 |
* 2. Redistributions in binary form must reproduce the above copyright
|
11 |
* notice, this list of conditions and the following disclaimer in the
|
12 |
* documentation and/or other materials provided with the distribution.
|
13 |
* 3. Neither the name of the author nor the names of any co-contributors
|
14 |
* may be used to endorse or promote products derived from this software
|
15 |
* without specific prior written permission.
|
16 |
*
|
17 |
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
18 |
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
19 |
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
20 |
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
21 |
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
22 |
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
23 |
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
24 |
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
25 |
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
26 |
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
27 |
* SUCH DAMAGE.
|
28 |
*/
|
29 |
|
30 |
/*
|
31 |
* pm.c - power management driver
|
32 |
*/
|
33 |
|
34 |
#include <sys/ioctl.h> |
35 |
#include <sys/capability.h> |
36 |
#include <sys/power.h> |
37 |
#include <sys/signal.h> |
38 |
#include <driver.h> |
39 |
#include <devctl.h> |
40 |
#include <cons.h> |
41 |
#include <pm.h> |
42 |
|
43 |
/* #define DEBUG_PM 1 */
|
44 |
|
45 |
#ifdef DEBUG_PM
|
46 |
#define DPRINTF(a) printf a
|
47 |
#else
|
48 |
#define DPRINTF(a)
|
49 |
#endif
|
50 |
|
51 |
struct pm_softc {
|
52 |
device_t dev; /* device object */
|
53 |
int isopen; /* number of open counts */ |
54 |
int policy; /* power management policy */ |
55 |
int timer_active; /* true if pm timer is staring */ |
56 |
timer_t timer; /* pm timer */
|
57 |
u_long idlecnt; /* idle counter in sec */
|
58 |
u_long dimtime; /* auto dim (lcd off) time in sec */
|
59 |
u_long sustime; /* auto suspend time in sec */
|
60 |
task_t powtask; /* task for power server */
|
61 |
int lcd_on; /* true if lcd is off */ |
62 |
device_t lcd_dev; /* lcd device */
|
63 |
int lastevt; /* last event */ |
64 |
}; |
65 |
|
66 |
static int pm_open(device_t, int); |
67 |
static int pm_close(device_t); |
68 |
static int pm_ioctl(device_t, u_long, void *); |
69 |
static int pm_init(struct driver *); |
70 |
static void pm_stop_timer(void); |
71 |
static void pm_update_timer(void); |
72 |
static void pm_timeout(void *); |
73 |
|
74 |
|
75 |
static struct devops pm_devops = { |
76 |
/* open */ pm_open,
|
77 |
/* close */ pm_close,
|
78 |
/* read */ no_read,
|
79 |
/* write */ no_write,
|
80 |
/* ioctl */ pm_ioctl,
|
81 |
/* devctl */ no_devctl,
|
82 |
}; |
83 |
|
84 |
struct driver pm_driver = {
|
85 |
/* name */ "pm", |
86 |
/* devops */ &pm_devops,
|
87 |
/* devsz */ sizeof(struct pm_softc), |
88 |
/* flags */ 0, |
89 |
/* probe */ NULL, |
90 |
/* init */ pm_init,
|
91 |
/* unload */ NULL, |
92 |
}; |
93 |
|
94 |
/*
|
95 |
* Pointer to the PM state. There can be only one PM instance.
|
96 |
*/
|
97 |
static struct pm_softc *pm_softc; |
98 |
|
99 |
static int |
100 |
pm_open(device_t dev, int mode)
|
101 |
{ |
102 |
struct pm_softc *sc = pm_softc;
|
103 |
|
104 |
if (!task_capable(CAP_POWERMGMT))
|
105 |
return EPERM;
|
106 |
|
107 |
if (sc->isopen > 0) |
108 |
return EBUSY;
|
109 |
|
110 |
sc->isopen++; |
111 |
return 0; |
112 |
} |
113 |
|
114 |
static int |
115 |
pm_close(device_t dev) |
116 |
{ |
117 |
struct pm_softc *sc = pm_softc;
|
118 |
|
119 |
if (!task_capable(CAP_POWERMGMT))
|
120 |
return EPERM;
|
121 |
|
122 |
if (sc->isopen != 1) |
123 |
return EINVAL;
|
124 |
|
125 |
sc->isopen--; |
126 |
return 0; |
127 |
} |
128 |
|
129 |
static int |
130 |
pm_ioctl(device_t dev, u_long cmd, void *arg)
|
131 |
{ |
132 |
struct pm_softc *sc = pm_softc;
|
133 |
int error = 0; |
134 |
int policy, state, event;
|
135 |
|
136 |
if (!task_capable(CAP_POWERMGMT))
|
137 |
return EPERM;
|
138 |
|
139 |
switch (cmd) {
|
140 |
|
141 |
case PMIOC_CONNECT:
|
142 |
/* Connection request from the power server */
|
143 |
if (copyin(arg, &sc->powtask, sizeof(task_t))) |
144 |
return EFAULT;
|
145 |
DPRINTF(("pm: connect power server\n"));
|
146 |
break;
|
147 |
|
148 |
case PMIOC_QUERY_EVENT:
|
149 |
event = sc->lastevt; |
150 |
sc->lastevt = PME_NO_EVENT; |
151 |
if (copyout(&event, arg, sizeof(int))) |
152 |
return EFAULT;
|
153 |
DPRINTF(("pm: query event=%d\n", event));
|
154 |
break;
|
155 |
|
156 |
case PMIOC_SET_POWER:
|
157 |
if (copyin(arg, &state, sizeof(int))) |
158 |
return EFAULT;
|
159 |
|
160 |
switch (state) {
|
161 |
case PWR_SUSPEND:
|
162 |
case PWR_OFF:
|
163 |
case PWR_REBOOT:
|
164 |
pm_set_power(state); |
165 |
break;
|
166 |
default:
|
167 |
error = EINVAL; |
168 |
break;
|
169 |
} |
170 |
break;
|
171 |
|
172 |
case PMIOC_GET_POLICY:
|
173 |
if (copyout(&sc->policy, arg, sizeof(int))) |
174 |
return EFAULT;
|
175 |
DPRINTF(("pm: get policy %d\n", sc->policy));
|
176 |
break;
|
177 |
|
178 |
case PMIOC_SET_POLICY:
|
179 |
if (copyin(arg, &policy, sizeof(int))) |
180 |
return EFAULT;
|
181 |
if (policy != PM_POWERSAVE && policy != PM_PERFORMANCE)
|
182 |
return EINVAL;
|
183 |
|
184 |
DPRINTF(("pm: set policy %d\n", policy));
|
185 |
|
186 |
if (policy == sc->policy) {
|
187 |
/* same policy */
|
188 |
break;
|
189 |
} |
190 |
/* Call devctl() routine for all devices */
|
191 |
device_broadcast(DEVCTL_PM_CHGPOLICY, &policy, 1);
|
192 |
|
193 |
sc->policy = policy; |
194 |
if (policy == PM_POWERSAVE)
|
195 |
pm_update_timer(); |
196 |
else
|
197 |
pm_stop_timer(); |
198 |
break;
|
199 |
|
200 |
case PMIOC_GET_SUSTMR:
|
201 |
if (copyout(&sc->sustime, arg, sizeof(u_long))) |
202 |
return EFAULT;
|
203 |
break;
|
204 |
|
205 |
case PMIOC_SET_SUSTMR:
|
206 |
if (copyin(arg, &sc->sustime, sizeof(u_long))) |
207 |
return EFAULT;
|
208 |
DPRINTF(("pm: set sustmr=%d\n", sc->sustime));
|
209 |
pm_update_timer(); |
210 |
break;
|
211 |
|
212 |
case PMIOC_GET_DIMTMR:
|
213 |
if (copyout(&sc->dimtime, arg, sizeof(u_long))) |
214 |
return EFAULT;
|
215 |
break;
|
216 |
|
217 |
case PMIOC_SET_DIMTMR:
|
218 |
if (copyin(arg, &sc->dimtime, sizeof(u_long))) |
219 |
return EFAULT;
|
220 |
DPRINTF(("pm: set dimtmr=%d\n", sc->dimtime));
|
221 |
pm_update_timer(); |
222 |
break;
|
223 |
|
224 |
default:
|
225 |
return EINVAL;
|
226 |
} |
227 |
return error;
|
228 |
} |
229 |
|
230 |
|
231 |
static void |
232 |
pm_stop_timer(void)
|
233 |
{ |
234 |
struct pm_softc *sc = pm_softc;
|
235 |
int s;
|
236 |
|
237 |
DPRINTF(("pm: stop timer\n"));
|
238 |
|
239 |
s = splhigh(); |
240 |
if (sc->timer_active) {
|
241 |
timer_stop(&sc->timer); |
242 |
sc->idlecnt = 0;
|
243 |
sc->timer_active = 0;
|
244 |
} |
245 |
splx(s); |
246 |
} |
247 |
|
248 |
static void |
249 |
pm_update_timer(void)
|
250 |
{ |
251 |
struct pm_softc *sc = pm_softc;
|
252 |
int s;
|
253 |
|
254 |
if (sc->policy != PM_POWERSAVE)
|
255 |
return;
|
256 |
|
257 |
s = splhigh(); |
258 |
sc->idlecnt = 0;
|
259 |
if (sc->timer_active) {
|
260 |
if (sc->sustime == 0 && sc->dimtime == 0) |
261 |
timer_stop(&sc->timer); |
262 |
} else {
|
263 |
if (sc->sustime != 0 || sc->dimtime != 0) { |
264 |
DPRINTF(("pm: start timer\n"));
|
265 |
timer_callout(&sc->timer, 1000, &pm_timeout, sc);
|
266 |
sc->timer_active = 1;
|
267 |
} |
268 |
} |
269 |
} |
270 |
|
271 |
|
272 |
static int |
273 |
pm_suspend(void)
|
274 |
{ |
275 |
int error;
|
276 |
|
277 |
DPRINTF(("pm: suspend system...\n"));
|
278 |
|
279 |
pm_stop_timer(); |
280 |
error = device_broadcast(DEVCTL_PM_POWERDOWN, NULL, 1); |
281 |
if (error) {
|
282 |
device_broadcast(DEVCTL_PM_POWERUP, NULL, 1); |
283 |
return error;
|
284 |
} |
285 |
machine_powerdown(PWR_SUSPEND); |
286 |
return 0; |
287 |
} |
288 |
|
289 |
static int |
290 |
pm_resume(void)
|
291 |
{ |
292 |
|
293 |
DPRINTF(("pm: resume...\n"));
|
294 |
|
295 |
device_broadcast(DEVCTL_PM_POWERUP, NULL, 1); |
296 |
pm_update_timer(); |
297 |
return 0; |
298 |
} |
299 |
|
300 |
static int |
301 |
pm_poweroff(void)
|
302 |
{ |
303 |
|
304 |
DPRINTF(("pm: power off...\n"));
|
305 |
|
306 |
pm_stop_timer(); |
307 |
device_broadcast(DEVCTL_PM_POWERDOWN, NULL, 1); |
308 |
driver_shutdown(); |
309 |
|
310 |
#ifdef CONFIG_CONS
|
311 |
cons_puts("\nThe system is halted. You can turn off power.");
|
312 |
#endif
|
313 |
machine_powerdown(PWR_OFF); |
314 |
|
315 |
/* NOTREACHED */
|
316 |
return 0; |
317 |
} |
318 |
|
319 |
static int |
320 |
pm_reboot(void)
|
321 |
{ |
322 |
|
323 |
DPRINTF(("pm: rebooting...\n"));
|
324 |
|
325 |
pm_stop_timer(); |
326 |
device_broadcast(DEVCTL_PM_POWERDOWN, NULL, 1); |
327 |
driver_shutdown(); |
328 |
machine_powerdown(PWR_REBOOT); |
329 |
|
330 |
/* NOTREACHED */
|
331 |
return 0; |
332 |
} |
333 |
|
334 |
static void |
335 |
pm_lcd_off(void)
|
336 |
{ |
337 |
struct pm_softc *sc = pm_softc;
|
338 |
|
339 |
DPRINTF(("pm: LCD off\n"));
|
340 |
|
341 |
if (sc->lcd_dev != NODEV && sc->lcd_on) {
|
342 |
device_control(sc->lcd_dev, DEVCTL_PM_LCDOFF, NULL);
|
343 |
if (sc->sustime == 0) |
344 |
pm_stop_timer(); |
345 |
sc->lcd_on = 0;
|
346 |
} |
347 |
} |
348 |
|
349 |
static void |
350 |
pm_lcd_on(void)
|
351 |
{ |
352 |
struct pm_softc *sc = pm_softc;
|
353 |
|
354 |
DPRINTF(("pm: LCD on\n"));
|
355 |
|
356 |
if (sc->lcd_dev != NODEV && !sc->lcd_on) {
|
357 |
device_control(sc->lcd_dev, DEVCTL_PM_LCDON, NULL);
|
358 |
pm_update_timer(); |
359 |
sc->lcd_on = 1;
|
360 |
} |
361 |
} |
362 |
|
363 |
static void |
364 |
pm_timeout(void *arg)
|
365 |
{ |
366 |
struct pm_softc *sc = arg;
|
367 |
int s, reload;
|
368 |
|
369 |
s = splhigh(); |
370 |
sc->idlecnt++; |
371 |
splx(s); |
372 |
|
373 |
DPRINTF(("pm: idlecnt=%d\n", sc->idlecnt));
|
374 |
|
375 |
if (sc->sustime != 0 && sc->idlecnt >= sc->sustime) { |
376 |
#ifdef CONFIG_CONS
|
377 |
cons_puts("\nThe system is about to suspend...");
|
378 |
#endif
|
379 |
pm_suspend(); |
380 |
} else {
|
381 |
reload = 0;
|
382 |
if (sc->dimtime != 0 && sc->idlecnt >= sc->dimtime) { |
383 |
pm_lcd_off(); |
384 |
if (sc->sustime != 0) |
385 |
reload = 1;
|
386 |
} else
|
387 |
reload = 1;
|
388 |
|
389 |
if (reload)
|
390 |
timer_callout(&sc->timer, 1000, &pm_timeout, sc);
|
391 |
} |
392 |
} |
393 |
|
394 |
/*
|
395 |
* PM service for other drivers.
|
396 |
*/
|
397 |
int
|
398 |
pm_set_power(int state)
|
399 |
{ |
400 |
int error;
|
401 |
|
402 |
switch (state) {
|
403 |
case PWR_ON:
|
404 |
error = pm_resume(); |
405 |
break;
|
406 |
case PWR_SUSPEND:
|
407 |
error = pm_suspend(); |
408 |
break;
|
409 |
case PWR_OFF:
|
410 |
error = pm_poweroff(); |
411 |
break;
|
412 |
case PWR_REBOOT:
|
413 |
error = pm_reboot(); |
414 |
break;
|
415 |
default:
|
416 |
error = EINVAL; |
417 |
} |
418 |
return error;
|
419 |
} |
420 |
|
421 |
/*
|
422 |
* PM event notification.
|
423 |
*/
|
424 |
void
|
425 |
pm_notify(int event)
|
426 |
{ |
427 |
struct pm_softc *sc = pm_softc;
|
428 |
int s;
|
429 |
|
430 |
if (event == PME_USER_ACTIVITY) {
|
431 |
/*
|
432 |
* Reload suspend timer for user activity.
|
433 |
*/
|
434 |
s = splhigh(); |
435 |
sc->idlecnt = 0;
|
436 |
splx(s); |
437 |
|
438 |
if (!sc->lcd_on)
|
439 |
pm_lcd_on(); |
440 |
return;
|
441 |
} |
442 |
|
443 |
DPRINTF(("pm: notify %d\n", event));
|
444 |
|
445 |
if (sc->powtask != TASK_NULL) {
|
446 |
/*
|
447 |
* Power server exists.
|
448 |
*/
|
449 |
switch (event) {
|
450 |
case PME_PWRBTN_PRESS:
|
451 |
case PME_SLPBTN_PRESS:
|
452 |
case PME_LOW_BATTERY:
|
453 |
case PME_LCD_CLOSE:
|
454 |
/*
|
455 |
* Post an exception to the power server.
|
456 |
* Then, the power server will query PM event.
|
457 |
*/
|
458 |
sc->lastevt = event; |
459 |
DPRINTF(("pm: post %d\n", event));
|
460 |
exception_post(sc->powtask, SIGPWR); |
461 |
break;
|
462 |
case PME_LCD_OPEN:
|
463 |
sc->lastevt = PME_NO_EVENT; |
464 |
pm_lcd_on(); |
465 |
break;
|
466 |
} |
467 |
} else {
|
468 |
/*
|
469 |
* No power server.
|
470 |
* Map power event to default action.
|
471 |
*/
|
472 |
switch (event) {
|
473 |
case PME_PWRBTN_PRESS:
|
474 |
pm_poweroff(); |
475 |
break;
|
476 |
case PME_SLPBTN_PRESS:
|
477 |
case PME_LOW_BATTERY:
|
478 |
pm_suspend(); |
479 |
break;
|
480 |
case PME_LCD_OPEN:
|
481 |
pm_lcd_on(); |
482 |
break;
|
483 |
case PME_LCD_CLOSE:
|
484 |
pm_lcd_off(); |
485 |
break;
|
486 |
} |
487 |
} |
488 |
} |
489 |
|
490 |
void
|
491 |
pm_attach_lcd(device_t dev) |
492 |
{ |
493 |
|
494 |
ASSERT(pm_softc != NULL);
|
495 |
|
496 |
pm_softc->lcd_dev = dev; |
497 |
} |
498 |
|
499 |
static int |
500 |
pm_init(struct driver *self)
|
501 |
{ |
502 |
struct pm_softc *sc;
|
503 |
device_t dev; |
504 |
|
505 |
/* Create device object */
|
506 |
dev = device_create(self, "pm", D_CHR|D_PROT);
|
507 |
|
508 |
sc = device_private(dev); |
509 |
sc->dev = dev; |
510 |
sc->isopen = 0;
|
511 |
sc->policy = DEFAULT_POWER_POLICY; |
512 |
sc->idlecnt = 0;
|
513 |
sc->dimtime = 0;
|
514 |
sc->sustime = 0;
|
515 |
sc->timer_active = 0;
|
516 |
sc->powtask = TASK_NULL; |
517 |
sc->lcd_dev = NODEV; |
518 |
sc->lcd_on = 1;
|
519 |
sc->lastevt = PME_NO_EVENT; |
520 |
|
521 |
pm_softc = sc; |
522 |
|
523 |
DPRINTF(("Power policy: %s mode\n",
|
524 |
(sc->policy == PM_POWERSAVE) ? "power save" : "performance")); |
525 |
return 0; |
526 |
} |