summaryrefslogtreecommitdiff
path: root/drivers/acpi/sleep/proc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/acpi/sleep/proc.c')
-rw-r--r--drivers/acpi/sleep/proc.c509
1 files changed, 509 insertions, 0 deletions
diff --git a/drivers/acpi/sleep/proc.c b/drivers/acpi/sleep/proc.c
new file mode 100644
index 000000000000..fd7c5a0649af
--- /dev/null
+++ b/drivers/acpi/sleep/proc.c
@@ -0,0 +1,509 @@
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/suspend.h>
+#include <linux/bcd.h>
+#include <asm/uaccess.h>
+
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+
+#ifdef CONFIG_X86
+#include <linux/mc146818rtc.h>
+#endif
+
+#include "sleep.h"
+
+#define ACPI_SYSTEM_FILE_SLEEP "sleep"
+#define ACPI_SYSTEM_FILE_ALARM "alarm"
+#define ACPI_SYSTEM_FILE_WAKEUP_DEVICE "wakeup"
+
+#define _COMPONENT ACPI_SYSTEM_COMPONENT
+ACPI_MODULE_NAME ("sleep")
+
+
+static int acpi_system_sleep_seq_show(struct seq_file *seq, void *offset)
+{
+ int i;
+
+ ACPI_FUNCTION_TRACE("acpi_system_sleep_seq_show");
+
+ for (i = 0; i <= ACPI_STATE_S5; i++) {
+ if (sleep_states[i]) {
+ seq_printf(seq,"S%d ", i);
+ if (i == ACPI_STATE_S4 && acpi_gbl_FACS->S4bios_f)
+ seq_printf(seq, "S4bios ");
+ }
+ }
+
+ seq_puts(seq, "\n");
+
+ return 0;
+}
+
+static int acpi_system_sleep_open_fs(struct inode *inode, struct file *file)
+{
+ return single_open(file, acpi_system_sleep_seq_show, PDE(inode)->data);
+}
+
+static ssize_t
+acpi_system_write_sleep (
+ struct file *file,
+ const char __user *buffer,
+ size_t count,
+ loff_t *ppos)
+{
+ char str[12];
+ u32 state = 0;
+ int error = 0;
+
+ if (count > sizeof(str) - 1)
+ goto Done;
+ memset(str,0,sizeof(str));
+ if (copy_from_user(str, buffer, count))
+ return -EFAULT;
+
+ /* Check for S4 bios request */
+ if (!strcmp(str,"4b")) {
+ error = acpi_suspend(4);
+ goto Done;
+ }
+ state = simple_strtoul(str, NULL, 0);
+#ifdef CONFIG_SOFTWARE_SUSPEND
+ if (state == 4) {
+ error = software_suspend();
+ goto Done;
+ }
+#endif
+ error = acpi_suspend(state);
+ Done:
+ return error ? error : count;
+}
+
+static int acpi_system_alarm_seq_show(struct seq_file *seq, void *offset)
+{
+ u32 sec, min, hr;
+ u32 day, mo, yr;
+ unsigned char rtc_control = 0;
+ unsigned long flags;
+
+ ACPI_FUNCTION_TRACE("acpi_system_alarm_seq_show");
+
+ spin_lock_irqsave(&rtc_lock, flags);
+
+ sec = CMOS_READ(RTC_SECONDS_ALARM);
+ min = CMOS_READ(RTC_MINUTES_ALARM);
+ hr = CMOS_READ(RTC_HOURS_ALARM);
+ rtc_control = CMOS_READ(RTC_CONTROL);
+
+ /* If we ever get an FACP with proper values... */
+ if (acpi_gbl_FADT->day_alrm)
+ /* ACPI spec: only low 6 its should be cared */
+ day = CMOS_READ(acpi_gbl_FADT->day_alrm) & 0x3F;
+ else
+ day = CMOS_READ(RTC_DAY_OF_MONTH);
+ if (acpi_gbl_FADT->mon_alrm)
+ mo = CMOS_READ(acpi_gbl_FADT->mon_alrm);
+ else
+ mo = CMOS_READ(RTC_MONTH);
+ if (acpi_gbl_FADT->century)
+ yr = CMOS_READ(acpi_gbl_FADT->century) * 100 + CMOS_READ(RTC_YEAR);
+ else
+ yr = CMOS_READ(RTC_YEAR);
+
+ spin_unlock_irqrestore(&rtc_lock, flags);
+
+ if (!(rtc_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
+ BCD_TO_BIN(sec);
+ BCD_TO_BIN(min);
+ BCD_TO_BIN(hr);
+ BCD_TO_BIN(day);
+ BCD_TO_BIN(mo);
+ BCD_TO_BIN(yr);
+ }
+
+ /* we're trusting the FADT (see above)*/
+ if (!acpi_gbl_FADT->century)
+ /* If we're not trusting the FADT, we should at least make it
+ * right for _this_ century... ehm, what is _this_ century?
+ *
+ * TBD:
+ * ASAP: find piece of code in the kernel, e.g. star tracker driver,
+ * which we can trust to determine the century correctly. Atom
+ * watch driver would be nice, too...
+ *
+ * if that has not happened, change for first release in 2050:
+ * if (yr<50)
+ * yr += 2100;
+ * else
+ * yr += 2000; // current line of code
+ *
+ * if that has not happened either, please do on 2099/12/31:23:59:59
+ * s/2000/2100
+ *
+ */
+ yr += 2000;
+
+ seq_printf(seq,"%4.4u-", yr);
+ (mo > 12) ? seq_puts(seq, "**-") : seq_printf(seq, "%2.2u-", mo);
+ (day > 31) ? seq_puts(seq, "** ") : seq_printf(seq, "%2.2u ", day);
+ (hr > 23) ? seq_puts(seq, "**:") : seq_printf(seq, "%2.2u:", hr);
+ (min > 59) ? seq_puts(seq, "**:") : seq_printf(seq, "%2.2u:", min);
+ (sec > 59) ? seq_puts(seq, "**\n") : seq_printf(seq, "%2.2u\n", sec);
+
+ return 0;
+}
+
+static int acpi_system_alarm_open_fs(struct inode *inode, struct file *file)
+{
+ return single_open(file, acpi_system_alarm_seq_show, PDE(inode)->data);
+}
+
+
+static int
+get_date_field (
+ char **p,
+ u32 *value)
+{
+ char *next = NULL;
+ char *string_end = NULL;
+ int result = -EINVAL;
+
+ /*
+ * Try to find delimeter, only to insert null. The end of the
+ * string won't have one, but is still valid.
+ */
+ next = strpbrk(*p, "- :");
+ if (next)
+ *next++ = '\0';
+
+ *value = simple_strtoul(*p, &string_end, 10);
+
+ /* Signal success if we got a good digit */
+ if (string_end != *p)
+ result = 0;
+
+ if (next)
+ *p = next;
+
+ return result;
+}
+
+
+static ssize_t
+acpi_system_write_alarm (
+ struct file *file,
+ const char __user *buffer,
+ size_t count,
+ loff_t *ppos)
+{
+ int result = 0;
+ char alarm_string[30] = {'\0'};
+ char *p = alarm_string;
+ u32 sec, min, hr, day, mo, yr;
+ int adjust = 0;
+ unsigned char rtc_control = 0;
+
+ ACPI_FUNCTION_TRACE("acpi_system_write_alarm");
+
+ if (count > sizeof(alarm_string) - 1)
+ return_VALUE(-EINVAL);
+
+ if (copy_from_user(alarm_string, buffer, count))
+ return_VALUE(-EFAULT);
+
+ alarm_string[count] = '\0';
+
+ /* check for time adjustment */
+ if (alarm_string[0] == '+') {
+ p++;
+ adjust = 1;
+ }
+
+ if ((result = get_date_field(&p, &yr)))
+ goto end;
+ if ((result = get_date_field(&p, &mo)))
+ goto end;
+ if ((result = get_date_field(&p, &day)))
+ goto end;
+ if ((result = get_date_field(&p, &hr)))
+ goto end;
+ if ((result = get_date_field(&p, &min)))
+ goto end;
+ if ((result = get_date_field(&p, &sec)))
+ goto end;
+
+ if (sec > 59) {
+ min += 1;
+ sec -= 60;
+ }
+ if (min > 59) {
+ hr += 1;
+ min -= 60;
+ }
+ if (hr > 23) {
+ day += 1;
+ hr -= 24;
+ }
+ if (day > 31) {
+ mo += 1;
+ day -= 31;
+ }
+ if (mo > 12) {
+ yr += 1;
+ mo -= 12;
+ }
+
+ spin_lock_irq(&rtc_lock);
+
+ rtc_control = CMOS_READ(RTC_CONTROL);
+ if (!(rtc_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
+ BIN_TO_BCD(yr);
+ BIN_TO_BCD(mo);
+ BIN_TO_BCD(day);
+ BIN_TO_BCD(hr);
+ BIN_TO_BCD(min);
+ BIN_TO_BCD(sec);
+ }
+
+ if (adjust) {
+ yr += CMOS_READ(RTC_YEAR);
+ mo += CMOS_READ(RTC_MONTH);
+ day += CMOS_READ(RTC_DAY_OF_MONTH);
+ hr += CMOS_READ(RTC_HOURS);
+ min += CMOS_READ(RTC_MINUTES);
+ sec += CMOS_READ(RTC_SECONDS);
+ }
+
+ spin_unlock_irq(&rtc_lock);
+
+ if (!(rtc_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
+ BCD_TO_BIN(yr);
+ BCD_TO_BIN(mo);
+ BCD_TO_BIN(day);
+ BCD_TO_BIN(hr);
+ BCD_TO_BIN(min);
+ BCD_TO_BIN(sec);
+ }
+
+ if (sec > 59) {
+ min++;
+ sec -= 60;
+ }
+ if (min > 59) {
+ hr++;
+ min -= 60;
+ }
+ if (hr > 23) {
+ day++;
+ hr -= 24;
+ }
+ if (day > 31) {
+ mo++;
+ day -= 31;
+ }
+ if (mo > 12) {
+ yr++;
+ mo -= 12;
+ }
+ if (!(rtc_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
+ BIN_TO_BCD(yr);
+ BIN_TO_BCD(mo);
+ BIN_TO_BCD(day);
+ BIN_TO_BCD(hr);
+ BIN_TO_BCD(min);
+ BIN_TO_BCD(sec);
+ }
+
+ spin_lock_irq(&rtc_lock);
+ /*
+ * Disable alarm interrupt before setting alarm timer or else
+ * when ACPI_EVENT_RTC is enabled, a spurious ACPI interrupt occurs
+ */
+ rtc_control &= ~RTC_AIE;
+ CMOS_WRITE(rtc_control, RTC_CONTROL);
+ CMOS_READ(RTC_INTR_FLAGS);
+
+ /* write the fields the rtc knows about */
+ CMOS_WRITE(hr, RTC_HOURS_ALARM);
+ CMOS_WRITE(min, RTC_MINUTES_ALARM);
+ CMOS_WRITE(sec, RTC_SECONDS_ALARM);
+
+ /*
+ * If the system supports an enhanced alarm it will have non-zero
+ * offsets into the CMOS RAM here -- which for some reason are pointing
+ * to the RTC area of memory.
+ */
+ if (acpi_gbl_FADT->day_alrm)
+ CMOS_WRITE(day, acpi_gbl_FADT->day_alrm);
+ if (acpi_gbl_FADT->mon_alrm)
+ CMOS_WRITE(mo, acpi_gbl_FADT->mon_alrm);
+ if (acpi_gbl_FADT->century)
+ CMOS_WRITE(yr/100, acpi_gbl_FADT->century);
+ /* enable the rtc alarm interrupt */
+ rtc_control |= RTC_AIE;
+ CMOS_WRITE(rtc_control, RTC_CONTROL);
+ CMOS_READ(RTC_INTR_FLAGS);
+
+ spin_unlock_irq(&rtc_lock);
+
+ acpi_clear_event(ACPI_EVENT_RTC);
+ acpi_enable_event(ACPI_EVENT_RTC, 0);
+
+ *ppos += count;
+
+ result = 0;
+end:
+ return_VALUE(result ? result : count);
+}
+
+extern struct list_head acpi_wakeup_device_list;
+extern spinlock_t acpi_device_lock;
+
+static int
+acpi_system_wakeup_device_seq_show(struct seq_file *seq, void *offset)
+{
+ struct list_head * node, * next;
+
+ seq_printf(seq, "Device Sleep state Status\n");
+
+ spin_lock(&acpi_device_lock);
+ list_for_each_safe(node, next, &acpi_wakeup_device_list) {
+ struct acpi_device * dev = container_of(node, struct acpi_device, wakeup_list);
+
+ if (!dev->wakeup.flags.valid)
+ continue;
+ spin_unlock(&acpi_device_lock);
+ if (dev->wakeup.flags.run_wake)
+ seq_printf(seq, "%4s %4d %8s\n",
+ dev->pnp.bus_id, (u32) dev->wakeup.sleep_state,
+ dev->wakeup.state.enabled ? "*enabled" : "*disabled");
+ else
+ seq_printf(seq, "%4s %4d %8s\n",
+ dev->pnp.bus_id, (u32) dev->wakeup.sleep_state,
+ dev->wakeup.state.enabled ? "enabled" : "disabled");
+ spin_lock(&acpi_device_lock);
+ }
+ spin_unlock(&acpi_device_lock);
+ return 0;
+}
+
+static ssize_t
+acpi_system_write_wakeup_device (
+ struct file *file,
+ const char __user *buffer,
+ size_t count,
+ loff_t *ppos)
+{
+ struct list_head * node, * next;
+ char strbuf[5];
+ char str[5] = "";
+ int len = count;
+ struct acpi_device *found_dev = NULL;
+
+ if (len > 4) len = 4;
+
+ if (copy_from_user(strbuf, buffer, len))
+ return -EFAULT;
+ strbuf[len] = '\0';
+ sscanf(strbuf, "%s", str);
+
+ spin_lock(&acpi_device_lock);
+ list_for_each_safe(node, next, &acpi_wakeup_device_list) {
+ struct acpi_device * dev = container_of(node, struct acpi_device, wakeup_list);
+ if (!dev->wakeup.flags.valid)
+ continue;
+
+ if (!strncmp(dev->pnp.bus_id, str, 4)) {
+ dev->wakeup.state.enabled = dev->wakeup.state.enabled ? 0:1;
+ found_dev = dev;
+ break;
+ }
+ }
+ if (found_dev) {
+ list_for_each_safe(node, next, &acpi_wakeup_device_list) {
+ struct acpi_device * dev = container_of(node,
+ struct acpi_device, wakeup_list);
+
+ if ((dev != found_dev) &&
+ (dev->wakeup.gpe_number == found_dev->wakeup.gpe_number) &&
+ (dev->wakeup.gpe_device == found_dev->wakeup.gpe_device)) {
+ printk(KERN_WARNING "ACPI: '%s' and '%s' have the same GPE, "
+ "can't disable/enable one seperately\n",
+ dev->pnp.bus_id, found_dev->pnp.bus_id);
+ dev->wakeup.state.enabled = found_dev->wakeup.state.enabled;
+ }
+ }
+ }
+ spin_unlock(&acpi_device_lock);
+ return count;
+}
+
+static int
+acpi_system_wakeup_device_open_fs(struct inode *inode, struct file *file)
+{
+ return single_open(file, acpi_system_wakeup_device_seq_show, PDE(inode)->data);
+}
+
+static struct file_operations acpi_system_wakeup_device_fops = {
+ .open = acpi_system_wakeup_device_open_fs,
+ .read = seq_read,
+ .write = acpi_system_write_wakeup_device,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static struct file_operations acpi_system_sleep_fops = {
+ .open = acpi_system_sleep_open_fs,
+ .read = seq_read,
+ .write = acpi_system_write_sleep,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static struct file_operations acpi_system_alarm_fops = {
+ .open = acpi_system_alarm_open_fs,
+ .read = seq_read,
+ .write = acpi_system_write_alarm,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+
+static u32 rtc_handler(void * context)
+{
+ acpi_clear_event(ACPI_EVENT_RTC);
+ acpi_disable_event(ACPI_EVENT_RTC, 0);
+
+ return ACPI_INTERRUPT_HANDLED;
+}
+
+static int acpi_sleep_proc_init(void)
+{
+ struct proc_dir_entry *entry = NULL;
+
+ if (acpi_disabled)
+ return 0;
+
+ /* 'sleep' [R/W]*/
+ entry = create_proc_entry(ACPI_SYSTEM_FILE_SLEEP,
+ S_IFREG|S_IRUGO|S_IWUSR, acpi_root_dir);
+ if (entry)
+ entry->proc_fops = &acpi_system_sleep_fops;
+
+ /* 'alarm' [R/W] */
+ entry = create_proc_entry(ACPI_SYSTEM_FILE_ALARM,
+ S_IFREG|S_IRUGO|S_IWUSR, acpi_root_dir);
+ if (entry)
+ entry->proc_fops = &acpi_system_alarm_fops;
+
+ /* 'wakeup device' [R/W]*/
+ entry = create_proc_entry(ACPI_SYSTEM_FILE_WAKEUP_DEVICE,
+ S_IFREG|S_IRUGO|S_IWUSR, acpi_root_dir);
+ if (entry)
+ entry->proc_fops = &acpi_system_wakeup_device_fops;
+
+ acpi_install_fixed_event_handler(ACPI_EVENT_RTC, rtc_handler, NULL);
+ return 0;
+}
+
+late_initcall(acpi_sleep_proc_init);