root / prex-0.9.0 / bsp / drv / dev / base / pm.c @ 03e9c04a
History | View | Annotate | Download (10.2 KB)
1 | 03e9c04a | Brad Neuman | /*-
|
---|---|---|---|
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 | } |