aboutsummaryrefslogtreecommitdiffstats
path: root/arch
diff options
context:
space:
mode:
authorRodolfo Giometti <giometti@linux.it>2006-06-05 11:43:10 -0400
committerRalf Baechle <ralf@linux-mips.org>2006-06-19 12:39:22 -0400
commit952fa954a61cee43de5afba91ae605e30ed2586c (patch)
tree8d94bd87614cb1c8a7eda02e383c7b6c4f45a474 /arch
parentaa9772e330d6c0a8a94316cb38ae8a7495885a60 (diff)
[MIPS] APM emu support
Signed-off-by: Rodolfo Giometti <giometti@linux.it> Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
Diffstat (limited to 'arch')
-rw-r--r--arch/mips/Kconfig26
-rw-r--r--arch/mips/kernel/Makefile2
-rw-r--r--arch/mips/kernel/apm.c605
3 files changed, 633 insertions, 0 deletions
diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
index 1c78661abe11..ae33e0a4c18d 100644
--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
@@ -1890,6 +1890,32 @@ config PM
1890 bool "Power Management support (EXPERIMENTAL)" 1890 bool "Power Management support (EXPERIMENTAL)"
1891 depends on EXPERIMENTAL && SOC_AU1X00 1891 depends on EXPERIMENTAL && SOC_AU1X00
1892 1892
1893config APM
1894 tristate "Advanced Power Management Emulation"
1895 depends on PM
1896 ---help---
1897 APM is a BIOS specification for saving power using several different
1898 techniques. This is mostly useful for battery powered systems with
1899 APM compliant BIOSes. If you say Y here, the system time will be
1900 reset after a RESUME operation, the /proc/apm device will provide
1901 battery status information, and user-space programs will receive
1902 notification of APM "events" (e.g. battery status change).
1903
1904 In order to use APM, you will need supporting software. For location
1905 and more information, read <file:Documentation/pm.txt> and the
1906 Battery Powered Linux mini-HOWTO, available from
1907 <http://www.tldp.org/docs.html#howto>.
1908
1909 This driver does not spin down disk drives (see the hdparm(8)
1910 manpage ("man 8 hdparm") for that), and it doesn't turn off
1911 VESA-compliant "green" monitors.
1912
1913 Generally, if you don't have a battery in your machine, there isn't
1914 much point in using this driver and you should say N. If you get
1915 random kernel OOPSes or reboots that don't seem to be related to
1916 anything, try disabling/enabling this option (or disabling/enabling
1917 APM in your BIOS).
1918
1893endmenu 1919endmenu
1894 1920
1895source "net/Kconfig" 1921source "net/Kconfig"
diff --git a/arch/mips/kernel/Makefile b/arch/mips/kernel/Makefile
index 34e8a256765c..881c467c6982 100644
--- a/arch/mips/kernel/Makefile
+++ b/arch/mips/kernel/Makefile
@@ -13,6 +13,8 @@ binfmt_irix-objs := irixelf.o irixinv.o irixioctl.o irixsig.o \
13 13
14obj-$(CONFIG_MODULES) += mips_ksyms.o module.o 14obj-$(CONFIG_MODULES) += mips_ksyms.o module.o
15 15
16obj-$(CONFIG_APM) += apm.o
17
16obj-$(CONFIG_CPU_R3000) += r2300_fpu.o r2300_switch.o 18obj-$(CONFIG_CPU_R3000) += r2300_fpu.o r2300_switch.o
17obj-$(CONFIG_CPU_TX39XX) += r2300_fpu.o r2300_switch.o 19obj-$(CONFIG_CPU_TX39XX) += r2300_fpu.o r2300_switch.o
18obj-$(CONFIG_CPU_TX49XX) += r4k_fpu.o r4k_switch.o 20obj-$(CONFIG_CPU_TX49XX) += r4k_fpu.o r4k_switch.o
diff --git a/arch/mips/kernel/apm.c b/arch/mips/kernel/apm.c
new file mode 100644
index 000000000000..15f46b4471fd
--- /dev/null
+++ b/arch/mips/kernel/apm.c
@@ -0,0 +1,605 @@
1/*
2 * bios-less APM driver for MIPS Linux
3 * Jamey Hicks <jamey@crl.dec.com>
4 * adapted from the APM BIOS driver for Linux by Stephen Rothwell (sfr@linuxcare.com)
5 *
6 * APM 1.2 Reference:
7 * Intel Corporation, Microsoft Corporation. Advanced Power Management
8 * (APM) BIOS Interface Specification, Revision 1.2, February 1996.
9 *
10 * [This document is available from Microsoft at:
11 * http://www.microsoft.com/hwdev/busbios/amp_12.htm]
12 */
13#include <linux/config.h>
14#include <linux/module.h>
15#include <linux/poll.h>
16#include <linux/timer.h>
17#include <linux/slab.h>
18#include <linux/proc_fs.h>
19#include <linux/miscdevice.h>
20#include <linux/apm_bios.h>
21#include <linux/capability.h>
22#include <linux/sched.h>
23#include <linux/pm.h>
24#include <linux/device.h>
25#include <linux/kernel.h>
26#include <linux/list.h>
27#include <linux/init.h>
28#include <linux/completion.h>
29
30#include <asm/apm.h> /* apm_power_info */
31#include <asm/system.h>
32
33/*
34 * The apm_bios device is one of the misc char devices.
35 * This is its minor number.
36 */
37#define APM_MINOR_DEV 134
38
39/*
40 * See Documentation/Config.help for the configuration options.
41 *
42 * Various options can be changed at boot time as follows:
43 * (We allow underscores for compatibility with the modules code)
44 * apm=on/off enable/disable APM
45 */
46
47/*
48 * Maximum number of events stored
49 */
50#define APM_MAX_EVENTS 16
51
52struct apm_queue {
53 unsigned int event_head;
54 unsigned int event_tail;
55 apm_event_t events[APM_MAX_EVENTS];
56};
57
58/*
59 * The per-file APM data
60 */
61struct apm_user {
62 struct list_head list;
63
64 unsigned int suser: 1;
65 unsigned int writer: 1;
66 unsigned int reader: 1;
67
68 int suspend_result;
69 unsigned int suspend_state;
70#define SUSPEND_NONE 0 /* no suspend pending */
71#define SUSPEND_PENDING 1 /* suspend pending read */
72#define SUSPEND_READ 2 /* suspend read, pending ack */
73#define SUSPEND_ACKED 3 /* suspend acked */
74#define SUSPEND_DONE 4 /* suspend completed */
75
76 struct apm_queue queue;
77};
78
79/*
80 * Local variables
81 */
82static int suspends_pending;
83static int apm_disabled;
84static int mips_apm_active;
85
86static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
87static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
88
89/*
90 * This is a list of everyone who has opened /dev/apm_bios
91 */
92static DECLARE_RWSEM(user_list_lock);
93static LIST_HEAD(apm_user_list);
94
95/*
96 * kapmd info. kapmd provides us a process context to handle
97 * "APM" events within - specifically necessary if we're going
98 * to be suspending the system.
99 */
100static DECLARE_WAIT_QUEUE_HEAD(kapmd_wait);
101static DECLARE_COMPLETION(kapmd_exit);
102static DEFINE_SPINLOCK(kapmd_queue_lock);
103static struct apm_queue kapmd_queue;
104
105
106static const char driver_version[] = "1.13"; /* no spaces */
107
108
109
110/*
111 * Compatibility cruft until the IPAQ people move over to the new
112 * interface.
113 */
114static void __apm_get_power_status(struct apm_power_info *info)
115{
116}
117
118/*
119 * This allows machines to provide their own "apm get power status" function.
120 */
121void (*apm_get_power_status)(struct apm_power_info *) = __apm_get_power_status;
122EXPORT_SYMBOL(apm_get_power_status);
123
124
125/*
126 * APM event queue management.
127 */
128static inline int queue_empty(struct apm_queue *q)
129{
130 return q->event_head == q->event_tail;
131}
132
133static inline apm_event_t queue_get_event(struct apm_queue *q)
134{
135 q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
136 return q->events[q->event_tail];
137}
138
139static void queue_add_event(struct apm_queue *q, apm_event_t event)
140{
141 q->event_head = (q->event_head + 1) % APM_MAX_EVENTS;
142 if (q->event_head == q->event_tail) {
143 static int notified;
144
145 if (notified++ == 0)
146 printk(KERN_ERR "apm: an event queue overflowed\n");
147 q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
148 }
149 q->events[q->event_head] = event;
150}
151
152static void queue_event_one_user(struct apm_user *as, apm_event_t event)
153{
154 if (as->suser && as->writer) {
155 switch (event) {
156 case APM_SYS_SUSPEND:
157 case APM_USER_SUSPEND:
158 /*
159 * If this user already has a suspend pending,
160 * don't queue another one.
161 */
162 if (as->suspend_state != SUSPEND_NONE)
163 return;
164
165 as->suspend_state = SUSPEND_PENDING;
166 suspends_pending++;
167 break;
168 }
169 }
170 queue_add_event(&as->queue, event);
171}
172
173static void queue_event(apm_event_t event, struct apm_user *sender)
174{
175 struct apm_user *as;
176
177 down_read(&user_list_lock);
178 list_for_each_entry(as, &apm_user_list, list) {
179 if (as != sender && as->reader)
180 queue_event_one_user(as, event);
181 }
182 up_read(&user_list_lock);
183 wake_up_interruptible(&apm_waitqueue);
184}
185
186static void apm_suspend(void)
187{
188 struct apm_user *as;
189 int err = pm_suspend(PM_SUSPEND_MEM);
190
191 /*
192 * Anyone on the APM queues will think we're still suspended.
193 * Send a message so everyone knows we're now awake again.
194 */
195 queue_event(APM_NORMAL_RESUME, NULL);
196
197 /*
198 * Finally, wake up anyone who is sleeping on the suspend.
199 */
200 down_read(&user_list_lock);
201 list_for_each_entry(as, &apm_user_list, list) {
202 as->suspend_result = err;
203 as->suspend_state = SUSPEND_DONE;
204 }
205 up_read(&user_list_lock);
206
207 wake_up(&apm_suspend_waitqueue);
208}
209
210static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
211{
212 struct apm_user *as = fp->private_data;
213 apm_event_t event;
214 int i = count, ret = 0;
215
216 if (count < sizeof(apm_event_t))
217 return -EINVAL;
218
219 if (queue_empty(&as->queue) && fp->f_flags & O_NONBLOCK)
220 return -EAGAIN;
221
222 wait_event_interruptible(apm_waitqueue, !queue_empty(&as->queue));
223
224 while ((i >= sizeof(event)) && !queue_empty(&as->queue)) {
225 event = queue_get_event(&as->queue);
226
227 ret = -EFAULT;
228 if (copy_to_user(buf, &event, sizeof(event)))
229 break;
230
231 if (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND)
232 as->suspend_state = SUSPEND_READ;
233
234 buf += sizeof(event);
235 i -= sizeof(event);
236 }
237
238 if (i < count)
239 ret = count - i;
240
241 return ret;
242}
243
244static unsigned int apm_poll(struct file *fp, poll_table * wait)
245{
246 struct apm_user *as = fp->private_data;
247
248 poll_wait(fp, &apm_waitqueue, wait);
249 return queue_empty(&as->queue) ? 0 : POLLIN | POLLRDNORM;
250}
251
252/*
253 * apm_ioctl - handle APM ioctl
254 *
255 * APM_IOC_SUSPEND
256 * This IOCTL is overloaded, and performs two functions. It is used to:
257 * - initiate a suspend
258 * - acknowledge a suspend read from /dev/apm_bios.
259 * Only when everyone who has opened /dev/apm_bios with write permission
260 * has acknowledge does the actual suspend happen.
261 */
262static int
263apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg)
264{
265 struct apm_user *as = filp->private_data;
266 unsigned long flags;
267 int err = -EINVAL;
268
269 if (!as->suser || !as->writer)
270 return -EPERM;
271
272 switch (cmd) {
273 case APM_IOC_SUSPEND:
274 as->suspend_result = -EINTR;
275
276 if (as->suspend_state == SUSPEND_READ) {
277 /*
278 * If we read a suspend command from /dev/apm_bios,
279 * then the corresponding APM_IOC_SUSPEND ioctl is
280 * interpreted as an acknowledge.
281 */
282 as->suspend_state = SUSPEND_ACKED;
283 suspends_pending--;
284 } else {
285 /*
286 * Otherwise it is a request to suspend the system.
287 * Queue an event for all readers, and expect an
288 * acknowledge from all writers who haven't already
289 * acknowledged.
290 */
291 queue_event(APM_USER_SUSPEND, as);
292 }
293
294 /*
295 * If there are no further acknowledges required, suspend
296 * the system.
297 */
298 if (suspends_pending == 0)
299 apm_suspend();
300
301 /*
302 * Wait for the suspend/resume to complete. If there are
303 * pending acknowledges, we wait here for them.
304 *
305 * Note that we need to ensure that the PM subsystem does
306 * not kick us out of the wait when it suspends the threads.
307 */
308 flags = current->flags;
309 current->flags |= PF_NOFREEZE;
310
311 /*
312 * Note: do not allow a thread which is acking the suspend
313 * to escape until the resume is complete.
314 */
315 if (as->suspend_state == SUSPEND_ACKED)
316 wait_event(apm_suspend_waitqueue,
317 as->suspend_state == SUSPEND_DONE);
318 else
319 wait_event_interruptible(apm_suspend_waitqueue,
320 as->suspend_state == SUSPEND_DONE);
321
322 current->flags = flags;
323 err = as->suspend_result;
324 as->suspend_state = SUSPEND_NONE;
325 break;
326 }
327
328 return err;
329}
330
331static int apm_release(struct inode * inode, struct file * filp)
332{
333 struct apm_user *as = filp->private_data;
334 filp->private_data = NULL;
335
336 down_write(&user_list_lock);
337 list_del(&as->list);
338 up_write(&user_list_lock);
339
340 /*
341 * We are now unhooked from the chain. As far as new
342 * events are concerned, we no longer exist. However, we
343 * need to balance suspends_pending, which means the
344 * possibility of sleeping.
345 */
346 if (as->suspend_state != SUSPEND_NONE) {
347 suspends_pending -= 1;
348 if (suspends_pending == 0)
349 apm_suspend();
350 }
351
352 kfree(as);
353 return 0;
354}
355
356static int apm_open(struct inode * inode, struct file * filp)
357{
358 struct apm_user *as;
359
360 as = (struct apm_user *)kzalloc(sizeof(*as), GFP_KERNEL);
361 if (as) {
362 /*
363 * XXX - this is a tiny bit broken, when we consider BSD
364 * process accounting. If the device is opened by root, we
365 * instantly flag that we used superuser privs. Who knows,
366 * we might close the device immediately without doing a
367 * privileged operation -- cevans
368 */
369 as->suser = capable(CAP_SYS_ADMIN);
370 as->writer = (filp->f_mode & FMODE_WRITE) == FMODE_WRITE;
371 as->reader = (filp->f_mode & FMODE_READ) == FMODE_READ;
372
373 down_write(&user_list_lock);
374 list_add(&as->list, &apm_user_list);
375 up_write(&user_list_lock);
376
377 filp->private_data = as;
378 }
379
380 return as ? 0 : -ENOMEM;
381}
382
383static struct file_operations apm_bios_fops = {
384 .owner = THIS_MODULE,
385 .read = apm_read,
386 .poll = apm_poll,
387 .ioctl = apm_ioctl,
388 .open = apm_open,
389 .release = apm_release,
390};
391
392static struct miscdevice apm_device = {
393 .minor = APM_MINOR_DEV,
394 .name = "apm_bios",
395 .fops = &apm_bios_fops
396};
397
398
399#ifdef CONFIG_PROC_FS
400/*
401 * Arguments, with symbols from linux/apm_bios.h.
402 *
403 * 0) Linux driver version (this will change if format changes)
404 * 1) APM BIOS Version. Usually 1.0, 1.1 or 1.2.
405 * 2) APM flags from APM Installation Check (0x00):
406 * bit 0: APM_16_BIT_SUPPORT
407 * bit 1: APM_32_BIT_SUPPORT
408 * bit 2: APM_IDLE_SLOWS_CLOCK
409 * bit 3: APM_BIOS_DISABLED
410 * bit 4: APM_BIOS_DISENGAGED
411 * 3) AC line status
412 * 0x00: Off-line
413 * 0x01: On-line
414 * 0x02: On backup power (BIOS >= 1.1 only)
415 * 0xff: Unknown
416 * 4) Battery status
417 * 0x00: High
418 * 0x01: Low
419 * 0x02: Critical
420 * 0x03: Charging
421 * 0x04: Selected battery not present (BIOS >= 1.2 only)
422 * 0xff: Unknown
423 * 5) Battery flag
424 * bit 0: High
425 * bit 1: Low
426 * bit 2: Critical
427 * bit 3: Charging
428 * bit 7: No system battery
429 * 0xff: Unknown
430 * 6) Remaining battery life (percentage of charge):
431 * 0-100: valid
432 * -1: Unknown
433 * 7) Remaining battery life (time units):
434 * Number of remaining minutes or seconds
435 * -1: Unknown
436 * 8) min = minutes; sec = seconds
437 */
438static int apm_get_info(char *buf, char **start, off_t fpos, int length)
439{
440 struct apm_power_info info;
441 char *units;
442 int ret;
443
444 info.ac_line_status = 0xff;
445 info.battery_status = 0xff;
446 info.battery_flag = 0xff;
447 info.battery_life = -1;
448 info.time = -1;
449 info.units = -1;
450
451 if (apm_get_power_status)
452 apm_get_power_status(&info);
453
454 switch (info.units) {
455 default: units = "?"; break;
456 case 0: units = "min"; break;
457 case 1: units = "sec"; break;
458 }
459
460 ret = sprintf(buf, "%s 1.2 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
461 driver_version, APM_32_BIT_SUPPORT,
462 info.ac_line_status, info.battery_status,
463 info.battery_flag, info.battery_life,
464 info.time, units);
465
466 return ret;
467}
468#endif
469
470static int kapmd(void *arg)
471{
472 daemonize("kapmd");
473 current->flags |= PF_NOFREEZE;
474
475 do {
476 apm_event_t event;
477
478 wait_event_interruptible(kapmd_wait,
479 !queue_empty(&kapmd_queue) || !mips_apm_active);
480
481 if (!mips_apm_active)
482 break;
483
484 spin_lock_irq(&kapmd_queue_lock);
485 event = 0;
486 if (!queue_empty(&kapmd_queue))
487 event = queue_get_event(&kapmd_queue);
488 spin_unlock_irq(&kapmd_queue_lock);
489
490 switch (event) {
491 case 0:
492 break;
493
494 case APM_LOW_BATTERY:
495 case APM_POWER_STATUS_CHANGE:
496 queue_event(event, NULL);
497 break;
498
499 case APM_USER_SUSPEND:
500 case APM_SYS_SUSPEND:
501 queue_event(event, NULL);
502 if (suspends_pending == 0)
503 apm_suspend();
504 break;
505
506 case APM_CRITICAL_SUSPEND:
507 apm_suspend();
508 break;
509 }
510 } while (1);
511
512 complete_and_exit(&kapmd_exit, 0);
513}
514
515static int __init apm_init(void)
516{
517 int ret;
518
519 if (apm_disabled) {
520 printk(KERN_NOTICE "apm: disabled on user request.\n");
521 return -ENODEV;
522 }
523
524 mips_apm_active = 1;
525
526 ret = kernel_thread(kapmd, NULL, CLONE_KERNEL);
527 if (ret < 0) {
528 mips_apm_active = 0;
529 return ret;
530 }
531
532#ifdef CONFIG_PROC_FS
533 create_proc_info_entry("apm", 0, NULL, apm_get_info);
534#endif
535
536 ret = misc_register(&apm_device);
537 if (ret != 0) {
538 remove_proc_entry("apm", NULL);
539
540 mips_apm_active = 0;
541 wake_up(&kapmd_wait);
542 wait_for_completion(&kapmd_exit);
543 }
544
545 return ret;
546}
547
548static void __exit apm_exit(void)
549{
550 misc_deregister(&apm_device);
551 remove_proc_entry("apm", NULL);
552
553 mips_apm_active = 0;
554 wake_up(&kapmd_wait);
555 wait_for_completion(&kapmd_exit);
556}
557
558module_init(apm_init);
559module_exit(apm_exit);
560
561MODULE_AUTHOR("Stephen Rothwell");
562MODULE_DESCRIPTION("Advanced Power Management");
563MODULE_LICENSE("GPL");
564
565#ifndef MODULE
566static int __init apm_setup(char *str)
567{
568 while ((str != NULL) && (*str != '\0')) {
569 if (strncmp(str, "off", 3) == 0)
570 apm_disabled = 1;
571 if (strncmp(str, "on", 2) == 0)
572 apm_disabled = 0;
573 str = strchr(str, ',');
574 if (str != NULL)
575 str += strspn(str, ", \t");
576 }
577 return 1;
578}
579
580__setup("apm=", apm_setup);
581#endif
582
583/**
584 * apm_queue_event - queue an APM event for kapmd
585 * @event: APM event
586 *
587 * Queue an APM event for kapmd to process and ultimately take the
588 * appropriate action. Only a subset of events are handled:
589 * %APM_LOW_BATTERY
590 * %APM_POWER_STATUS_CHANGE
591 * %APM_USER_SUSPEND
592 * %APM_SYS_SUSPEND
593 * %APM_CRITICAL_SUSPEND
594 */
595void apm_queue_event(apm_event_t event)
596{
597 unsigned long flags;
598
599 spin_lock_irqsave(&kapmd_queue_lock, flags);
600 queue_add_event(&kapmd_queue, event);
601 spin_unlock_irqrestore(&kapmd_queue_lock, flags);
602
603 wake_up_interruptible(&kapmd_wait);
604}
605EXPORT_SYMBOL(apm_queue_event);