···18901890 bool "Power Management support (EXPERIMENTAL)"18911891 depends on EXPERIMENTAL && SOC_AU1X001892189218931893+config APM18941894+ tristate "Advanced Power Management Emulation"18951895+ depends on PM18961896+ ---help---18971897+ APM is a BIOS specification for saving power using several different18981898+ techniques. This is mostly useful for battery powered systems with18991899+ APM compliant BIOSes. If you say Y here, the system time will be19001900+ reset after a RESUME operation, the /proc/apm device will provide19011901+ battery status information, and user-space programs will receive19021902+ notification of APM "events" (e.g. battery status change).19031903+19041904+ In order to use APM, you will need supporting software. For location19051905+ and more information, read <file:Documentation/pm.txt> and the19061906+ Battery Powered Linux mini-HOWTO, available from19071907+ <http://www.tldp.org/docs.html#howto>.19081908+19091909+ This driver does not spin down disk drives (see the hdparm(8)19101910+ manpage ("man 8 hdparm") for that), and it doesn't turn off19111911+ VESA-compliant "green" monitors.19121912+19131913+ Generally, if you don't have a battery in your machine, there isn't19141914+ much point in using this driver and you should say N. If you get19151915+ random kernel OOPSes or reboots that don't seem to be related to19161916+ anything, try disabling/enabling this option (or disabling/enabling19171917+ APM in your BIOS).19181918+18931919endmenu1894192018951921source "net/Kconfig"
···11+/*22+ * bios-less APM driver for MIPS Linux33+ * Jamey Hicks <jamey@crl.dec.com>44+ * adapted from the APM BIOS driver for Linux by Stephen Rothwell (sfr@linuxcare.com)55+ *66+ * APM 1.2 Reference:77+ * Intel Corporation, Microsoft Corporation. Advanced Power Management88+ * (APM) BIOS Interface Specification, Revision 1.2, February 1996.99+ *1010+ * [This document is available from Microsoft at:1111+ * http://www.microsoft.com/hwdev/busbios/amp_12.htm]1212+ */1313+#include <linux/config.h>1414+#include <linux/module.h>1515+#include <linux/poll.h>1616+#include <linux/timer.h>1717+#include <linux/slab.h>1818+#include <linux/proc_fs.h>1919+#include <linux/miscdevice.h>2020+#include <linux/apm_bios.h>2121+#include <linux/capability.h>2222+#include <linux/sched.h>2323+#include <linux/pm.h>2424+#include <linux/device.h>2525+#include <linux/kernel.h>2626+#include <linux/list.h>2727+#include <linux/init.h>2828+#include <linux/completion.h>2929+3030+#include <asm/apm.h> /* apm_power_info */3131+#include <asm/system.h>3232+3333+/*3434+ * The apm_bios device is one of the misc char devices.3535+ * This is its minor number.3636+ */3737+#define APM_MINOR_DEV 1343838+3939+/*4040+ * See Documentation/Config.help for the configuration options.4141+ *4242+ * Various options can be changed at boot time as follows:4343+ * (We allow underscores for compatibility with the modules code)4444+ * apm=on/off enable/disable APM4545+ */4646+4747+/*4848+ * Maximum number of events stored4949+ */5050+#define APM_MAX_EVENTS 165151+5252+struct apm_queue {5353+ unsigned int event_head;5454+ unsigned int event_tail;5555+ apm_event_t events[APM_MAX_EVENTS];5656+};5757+5858+/*5959+ * The per-file APM data6060+ */6161+struct apm_user {6262+ struct list_head list;6363+6464+ unsigned int suser: 1;6565+ unsigned int writer: 1;6666+ unsigned int reader: 1;6767+6868+ int suspend_result;6969+ unsigned int suspend_state;7070+#define SUSPEND_NONE 0 /* no suspend pending */7171+#define SUSPEND_PENDING 1 /* suspend pending read */7272+#define SUSPEND_READ 2 /* suspend read, pending ack */7373+#define SUSPEND_ACKED 3 /* suspend acked */7474+#define SUSPEND_DONE 4 /* suspend completed */7575+7676+ struct apm_queue queue;7777+};7878+7979+/*8080+ * Local variables8181+ */8282+static int suspends_pending;8383+static int apm_disabled;8484+static int mips_apm_active;8585+8686+static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);8787+static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);8888+8989+/*9090+ * This is a list of everyone who has opened /dev/apm_bios9191+ */9292+static DECLARE_RWSEM(user_list_lock);9393+static LIST_HEAD(apm_user_list);9494+9595+/*9696+ * kapmd info. kapmd provides us a process context to handle9797+ * "APM" events within - specifically necessary if we're going9898+ * to be suspending the system.9999+ */100100+static DECLARE_WAIT_QUEUE_HEAD(kapmd_wait);101101+static DECLARE_COMPLETION(kapmd_exit);102102+static DEFINE_SPINLOCK(kapmd_queue_lock);103103+static struct apm_queue kapmd_queue;104104+105105+106106+static const char driver_version[] = "1.13"; /* no spaces */107107+108108+109109+110110+/*111111+ * Compatibility cruft until the IPAQ people move over to the new112112+ * interface.113113+ */114114+static void __apm_get_power_status(struct apm_power_info *info)115115+{116116+}117117+118118+/*119119+ * This allows machines to provide their own "apm get power status" function.120120+ */121121+void (*apm_get_power_status)(struct apm_power_info *) = __apm_get_power_status;122122+EXPORT_SYMBOL(apm_get_power_status);123123+124124+125125+/*126126+ * APM event queue management.127127+ */128128+static inline int queue_empty(struct apm_queue *q)129129+{130130+ return q->event_head == q->event_tail;131131+}132132+133133+static inline apm_event_t queue_get_event(struct apm_queue *q)134134+{135135+ q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;136136+ return q->events[q->event_tail];137137+}138138+139139+static void queue_add_event(struct apm_queue *q, apm_event_t event)140140+{141141+ q->event_head = (q->event_head + 1) % APM_MAX_EVENTS;142142+ if (q->event_head == q->event_tail) {143143+ static int notified;144144+145145+ if (notified++ == 0)146146+ printk(KERN_ERR "apm: an event queue overflowed\n");147147+ q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;148148+ }149149+ q->events[q->event_head] = event;150150+}151151+152152+static void queue_event_one_user(struct apm_user *as, apm_event_t event)153153+{154154+ if (as->suser && as->writer) {155155+ switch (event) {156156+ case APM_SYS_SUSPEND:157157+ case APM_USER_SUSPEND:158158+ /*159159+ * If this user already has a suspend pending,160160+ * don't queue another one.161161+ */162162+ if (as->suspend_state != SUSPEND_NONE)163163+ return;164164+165165+ as->suspend_state = SUSPEND_PENDING;166166+ suspends_pending++;167167+ break;168168+ }169169+ }170170+ queue_add_event(&as->queue, event);171171+}172172+173173+static void queue_event(apm_event_t event, struct apm_user *sender)174174+{175175+ struct apm_user *as;176176+177177+ down_read(&user_list_lock);178178+ list_for_each_entry(as, &apm_user_list, list) {179179+ if (as != sender && as->reader)180180+ queue_event_one_user(as, event);181181+ }182182+ up_read(&user_list_lock);183183+ wake_up_interruptible(&apm_waitqueue);184184+}185185+186186+static void apm_suspend(void)187187+{188188+ struct apm_user *as;189189+ int err = pm_suspend(PM_SUSPEND_MEM);190190+191191+ /*192192+ * Anyone on the APM queues will think we're still suspended.193193+ * Send a message so everyone knows we're now awake again.194194+ */195195+ queue_event(APM_NORMAL_RESUME, NULL);196196+197197+ /*198198+ * Finally, wake up anyone who is sleeping on the suspend.199199+ */200200+ down_read(&user_list_lock);201201+ list_for_each_entry(as, &apm_user_list, list) {202202+ as->suspend_result = err;203203+ as->suspend_state = SUSPEND_DONE;204204+ }205205+ up_read(&user_list_lock);206206+207207+ wake_up(&apm_suspend_waitqueue);208208+}209209+210210+static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)211211+{212212+ struct apm_user *as = fp->private_data;213213+ apm_event_t event;214214+ int i = count, ret = 0;215215+216216+ if (count < sizeof(apm_event_t))217217+ return -EINVAL;218218+219219+ if (queue_empty(&as->queue) && fp->f_flags & O_NONBLOCK)220220+ return -EAGAIN;221221+222222+ wait_event_interruptible(apm_waitqueue, !queue_empty(&as->queue));223223+224224+ while ((i >= sizeof(event)) && !queue_empty(&as->queue)) {225225+ event = queue_get_event(&as->queue);226226+227227+ ret = -EFAULT;228228+ if (copy_to_user(buf, &event, sizeof(event)))229229+ break;230230+231231+ if (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND)232232+ as->suspend_state = SUSPEND_READ;233233+234234+ buf += sizeof(event);235235+ i -= sizeof(event);236236+ }237237+238238+ if (i < count)239239+ ret = count - i;240240+241241+ return ret;242242+}243243+244244+static unsigned int apm_poll(struct file *fp, poll_table * wait)245245+{246246+ struct apm_user *as = fp->private_data;247247+248248+ poll_wait(fp, &apm_waitqueue, wait);249249+ return queue_empty(&as->queue) ? 0 : POLLIN | POLLRDNORM;250250+}251251+252252+/*253253+ * apm_ioctl - handle APM ioctl254254+ *255255+ * APM_IOC_SUSPEND256256+ * This IOCTL is overloaded, and performs two functions. It is used to:257257+ * - initiate a suspend258258+ * - acknowledge a suspend read from /dev/apm_bios.259259+ * Only when everyone who has opened /dev/apm_bios with write permission260260+ * has acknowledge does the actual suspend happen.261261+ */262262+static int263263+apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg)264264+{265265+ struct apm_user *as = filp->private_data;266266+ unsigned long flags;267267+ int err = -EINVAL;268268+269269+ if (!as->suser || !as->writer)270270+ return -EPERM;271271+272272+ switch (cmd) {273273+ case APM_IOC_SUSPEND:274274+ as->suspend_result = -EINTR;275275+276276+ if (as->suspend_state == SUSPEND_READ) {277277+ /*278278+ * If we read a suspend command from /dev/apm_bios,279279+ * then the corresponding APM_IOC_SUSPEND ioctl is280280+ * interpreted as an acknowledge.281281+ */282282+ as->suspend_state = SUSPEND_ACKED;283283+ suspends_pending--;284284+ } else {285285+ /*286286+ * Otherwise it is a request to suspend the system.287287+ * Queue an event for all readers, and expect an288288+ * acknowledge from all writers who haven't already289289+ * acknowledged.290290+ */291291+ queue_event(APM_USER_SUSPEND, as);292292+ }293293+294294+ /*295295+ * If there are no further acknowledges required, suspend296296+ * the system.297297+ */298298+ if (suspends_pending == 0)299299+ apm_suspend();300300+301301+ /*302302+ * Wait for the suspend/resume to complete. If there are303303+ * pending acknowledges, we wait here for them.304304+ *305305+ * Note that we need to ensure that the PM subsystem does306306+ * not kick us out of the wait when it suspends the threads.307307+ */308308+ flags = current->flags;309309+ current->flags |= PF_NOFREEZE;310310+311311+ /*312312+ * Note: do not allow a thread which is acking the suspend313313+ * to escape until the resume is complete.314314+ */315315+ if (as->suspend_state == SUSPEND_ACKED)316316+ wait_event(apm_suspend_waitqueue,317317+ as->suspend_state == SUSPEND_DONE);318318+ else319319+ wait_event_interruptible(apm_suspend_waitqueue,320320+ as->suspend_state == SUSPEND_DONE);321321+322322+ current->flags = flags;323323+ err = as->suspend_result;324324+ as->suspend_state = SUSPEND_NONE;325325+ break;326326+ }327327+328328+ return err;329329+}330330+331331+static int apm_release(struct inode * inode, struct file * filp)332332+{333333+ struct apm_user *as = filp->private_data;334334+ filp->private_data = NULL;335335+336336+ down_write(&user_list_lock);337337+ list_del(&as->list);338338+ up_write(&user_list_lock);339339+340340+ /*341341+ * We are now unhooked from the chain. As far as new342342+ * events are concerned, we no longer exist. However, we343343+ * need to balance suspends_pending, which means the344344+ * possibility of sleeping.345345+ */346346+ if (as->suspend_state != SUSPEND_NONE) {347347+ suspends_pending -= 1;348348+ if (suspends_pending == 0)349349+ apm_suspend();350350+ }351351+352352+ kfree(as);353353+ return 0;354354+}355355+356356+static int apm_open(struct inode * inode, struct file * filp)357357+{358358+ struct apm_user *as;359359+360360+ as = (struct apm_user *)kzalloc(sizeof(*as), GFP_KERNEL);361361+ if (as) {362362+ /*363363+ * XXX - this is a tiny bit broken, when we consider BSD364364+ * process accounting. If the device is opened by root, we365365+ * instantly flag that we used superuser privs. Who knows,366366+ * we might close the device immediately without doing a367367+ * privileged operation -- cevans368368+ */369369+ as->suser = capable(CAP_SYS_ADMIN);370370+ as->writer = (filp->f_mode & FMODE_WRITE) == FMODE_WRITE;371371+ as->reader = (filp->f_mode & FMODE_READ) == FMODE_READ;372372+373373+ down_write(&user_list_lock);374374+ list_add(&as->list, &apm_user_list);375375+ up_write(&user_list_lock);376376+377377+ filp->private_data = as;378378+ }379379+380380+ return as ? 0 : -ENOMEM;381381+}382382+383383+static struct file_operations apm_bios_fops = {384384+ .owner = THIS_MODULE,385385+ .read = apm_read,386386+ .poll = apm_poll,387387+ .ioctl = apm_ioctl,388388+ .open = apm_open,389389+ .release = apm_release,390390+};391391+392392+static struct miscdevice apm_device = {393393+ .minor = APM_MINOR_DEV,394394+ .name = "apm_bios",395395+ .fops = &apm_bios_fops396396+};397397+398398+399399+#ifdef CONFIG_PROC_FS400400+/*401401+ * Arguments, with symbols from linux/apm_bios.h.402402+ *403403+ * 0) Linux driver version (this will change if format changes)404404+ * 1) APM BIOS Version. Usually 1.0, 1.1 or 1.2.405405+ * 2) APM flags from APM Installation Check (0x00):406406+ * bit 0: APM_16_BIT_SUPPORT407407+ * bit 1: APM_32_BIT_SUPPORT408408+ * bit 2: APM_IDLE_SLOWS_CLOCK409409+ * bit 3: APM_BIOS_DISABLED410410+ * bit 4: APM_BIOS_DISENGAGED411411+ * 3) AC line status412412+ * 0x00: Off-line413413+ * 0x01: On-line414414+ * 0x02: On backup power (BIOS >= 1.1 only)415415+ * 0xff: Unknown416416+ * 4) Battery status417417+ * 0x00: High418418+ * 0x01: Low419419+ * 0x02: Critical420420+ * 0x03: Charging421421+ * 0x04: Selected battery not present (BIOS >= 1.2 only)422422+ * 0xff: Unknown423423+ * 5) Battery flag424424+ * bit 0: High425425+ * bit 1: Low426426+ * bit 2: Critical427427+ * bit 3: Charging428428+ * bit 7: No system battery429429+ * 0xff: Unknown430430+ * 6) Remaining battery life (percentage of charge):431431+ * 0-100: valid432432+ * -1: Unknown433433+ * 7) Remaining battery life (time units):434434+ * Number of remaining minutes or seconds435435+ * -1: Unknown436436+ * 8) min = minutes; sec = seconds437437+ */438438+static int apm_get_info(char *buf, char **start, off_t fpos, int length)439439+{440440+ struct apm_power_info info;441441+ char *units;442442+ int ret;443443+444444+ info.ac_line_status = 0xff;445445+ info.battery_status = 0xff;446446+ info.battery_flag = 0xff;447447+ info.battery_life = -1;448448+ info.time = -1;449449+ info.units = -1;450450+451451+ if (apm_get_power_status)452452+ apm_get_power_status(&info);453453+454454+ switch (info.units) {455455+ default: units = "?"; break;456456+ case 0: units = "min"; break;457457+ case 1: units = "sec"; break;458458+ }459459+460460+ ret = sprintf(buf, "%s 1.2 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",461461+ driver_version, APM_32_BIT_SUPPORT,462462+ info.ac_line_status, info.battery_status,463463+ info.battery_flag, info.battery_life,464464+ info.time, units);465465+466466+ return ret;467467+}468468+#endif469469+470470+static int kapmd(void *arg)471471+{472472+ daemonize("kapmd");473473+ current->flags |= PF_NOFREEZE;474474+475475+ do {476476+ apm_event_t event;477477+478478+ wait_event_interruptible(kapmd_wait,479479+ !queue_empty(&kapmd_queue) || !mips_apm_active);480480+481481+ if (!mips_apm_active)482482+ break;483483+484484+ spin_lock_irq(&kapmd_queue_lock);485485+ event = 0;486486+ if (!queue_empty(&kapmd_queue))487487+ event = queue_get_event(&kapmd_queue);488488+ spin_unlock_irq(&kapmd_queue_lock);489489+490490+ switch (event) {491491+ case 0:492492+ break;493493+494494+ case APM_LOW_BATTERY:495495+ case APM_POWER_STATUS_CHANGE:496496+ queue_event(event, NULL);497497+ break;498498+499499+ case APM_USER_SUSPEND:500500+ case APM_SYS_SUSPEND:501501+ queue_event(event, NULL);502502+ if (suspends_pending == 0)503503+ apm_suspend();504504+ break;505505+506506+ case APM_CRITICAL_SUSPEND:507507+ apm_suspend();508508+ break;509509+ }510510+ } while (1);511511+512512+ complete_and_exit(&kapmd_exit, 0);513513+}514514+515515+static int __init apm_init(void)516516+{517517+ int ret;518518+519519+ if (apm_disabled) {520520+ printk(KERN_NOTICE "apm: disabled on user request.\n");521521+ return -ENODEV;522522+ }523523+524524+ mips_apm_active = 1;525525+526526+ ret = kernel_thread(kapmd, NULL, CLONE_KERNEL);527527+ if (ret < 0) {528528+ mips_apm_active = 0;529529+ return ret;530530+ }531531+532532+#ifdef CONFIG_PROC_FS533533+ create_proc_info_entry("apm", 0, NULL, apm_get_info);534534+#endif535535+536536+ ret = misc_register(&apm_device);537537+ if (ret != 0) {538538+ remove_proc_entry("apm", NULL);539539+540540+ mips_apm_active = 0;541541+ wake_up(&kapmd_wait);542542+ wait_for_completion(&kapmd_exit);543543+ }544544+545545+ return ret;546546+}547547+548548+static void __exit apm_exit(void)549549+{550550+ misc_deregister(&apm_device);551551+ remove_proc_entry("apm", NULL);552552+553553+ mips_apm_active = 0;554554+ wake_up(&kapmd_wait);555555+ wait_for_completion(&kapmd_exit);556556+}557557+558558+module_init(apm_init);559559+module_exit(apm_exit);560560+561561+MODULE_AUTHOR("Stephen Rothwell");562562+MODULE_DESCRIPTION("Advanced Power Management");563563+MODULE_LICENSE("GPL");564564+565565+#ifndef MODULE566566+static int __init apm_setup(char *str)567567+{568568+ while ((str != NULL) && (*str != '\0')) {569569+ if (strncmp(str, "off", 3) == 0)570570+ apm_disabled = 1;571571+ if (strncmp(str, "on", 2) == 0)572572+ apm_disabled = 0;573573+ str = strchr(str, ',');574574+ if (str != NULL)575575+ str += strspn(str, ", \t");576576+ }577577+ return 1;578578+}579579+580580+__setup("apm=", apm_setup);581581+#endif582582+583583+/**584584+ * apm_queue_event - queue an APM event for kapmd585585+ * @event: APM event586586+ *587587+ * Queue an APM event for kapmd to process and ultimately take the588588+ * appropriate action. Only a subset of events are handled:589589+ * %APM_LOW_BATTERY590590+ * %APM_POWER_STATUS_CHANGE591591+ * %APM_USER_SUSPEND592592+ * %APM_SYS_SUSPEND593593+ * %APM_CRITICAL_SUSPEND594594+ */595595+void apm_queue_event(apm_event_t event)596596+{597597+ unsigned long flags;598598+599599+ spin_lock_irqsave(&kapmd_queue_lock, flags);600600+ queue_add_event(&kapmd_queue, event);601601+ spin_unlock_irqrestore(&kapmd_queue_lock, flags);602602+603603+ wake_up_interruptible(&kapmd_wait);604604+}605605+EXPORT_SYMBOL(apm_queue_event);
+65
include/asm-mips/apm.h
···11+/* -*- linux-c -*-22+ *33+ * (C) 2003 zecke@handhelds.org44+ *55+ * GPL version 266+ *77+ * based on arch/arm/kernel/apm.c88+ * factor out the information needed by architectures to provide99+ * apm status1010+ *1111+ *1212+ */1313+#ifndef MIPS_ASM_SA1100_APM_H1414+#define MIPS_ASM_SA1100_APM_H1515+1616+#include <linux/config.h>1717+#include <linux/apm_bios.h>1818+1919+/*2020+ * This structure gets filled in by the machine specific 'get_power_status'2121+ * implementation. Any fields which are not set default to a safe value.2222+ */2323+struct apm_power_info {2424+ unsigned char ac_line_status;2525+#define APM_AC_OFFLINE 02626+#define APM_AC_ONLINE 12727+#define APM_AC_BACKUP 22828+#define APM_AC_UNKNOWN 0xff2929+3030+ unsigned char battery_status;3131+#define APM_BATTERY_STATUS_HIGH 03232+#define APM_BATTERY_STATUS_LOW 13333+#define APM_BATTERY_STATUS_CRITICAL 23434+#define APM_BATTERY_STATUS_CHARGING 33535+#define APM_BATTERY_STATUS_NOT_PRESENT 43636+#define APM_BATTERY_STATUS_UNKNOWN 0xff3737+3838+ unsigned char battery_flag;3939+#define APM_BATTERY_FLAG_HIGH (1 << 0)4040+#define APM_BATTERY_FLAG_LOW (1 << 1)4141+#define APM_BATTERY_FLAG_CRITICAL (1 << 2)4242+#define APM_BATTERY_FLAG_CHARGING (1 << 3)4343+#define APM_BATTERY_FLAG_NOT_PRESENT (1 << 7)4444+#define APM_BATTERY_FLAG_UNKNOWN 0xff4545+4646+ int battery_life;4747+ int time;4848+ int units;4949+#define APM_UNITS_MINS 05050+#define APM_UNITS_SECS 15151+#define APM_UNITS_UNKNOWN -15252+5353+};5454+5555+/*5656+ * This allows machines to provide their own "apm get power status" function.5757+ */5858+extern void (*apm_get_power_status)(struct apm_power_info *);5959+6060+/*6161+ * Queue an event (APM_SYS_SUSPEND or APM_CRITICAL_SUSPEND)6262+ */6363+void apm_queue_event(apm_event_t event);6464+6565+#endif