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