Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
[powerpc.git] / drivers / rtc / rtc-sysfs.c
index 2ddd0cf..899ab8c 100644 (file)
@@ -78,6 +78,92 @@ static struct attribute_group rtc_attr_group = {
        .attrs = rtc_attrs,
 };
 
+
+static ssize_t
+rtc_sysfs_show_wakealarm(struct class_device *dev, char *buf)
+{
+       ssize_t retval;
+       unsigned long alarm;
+       struct rtc_wkalrm alm;
+
+       /* Don't show disabled alarms; but the RTC could leave the
+        * alarm enabled after it's already triggered.  Alarms are
+        * conceptually one-shot, even though some common hardware
+        * (PCs) doesn't actually work that way.
+        *
+        * REVISIT maybe we should require RTC implementations to
+        * disable the RTC alarm after it triggers, for uniformity.
+        */
+       retval = rtc_read_alarm(dev, &alm);
+       if (retval == 0 && alm.enabled) {
+               rtc_tm_to_time(&alm.time, &alarm);
+               retval = sprintf(buf, "%lu\n", alarm);
+       }
+
+       return retval;
+}
+
+static ssize_t
+rtc_sysfs_set_wakealarm(struct class_device *dev, const char *buf, size_t n)
+{
+       ssize_t retval;
+       unsigned long now, alarm;
+       struct rtc_wkalrm alm;
+
+       /* Only request alarms that trigger in the future.  Disable them
+        * by writing another time, e.g. 0 meaning Jan 1 1970 UTC.
+        */
+       retval = rtc_read_time(dev, &alm.time);
+       if (retval < 0)
+               return retval;
+       rtc_tm_to_time(&alm.time, &now);
+
+       alarm = simple_strtoul(buf, NULL, 0);
+       if (alarm > now) {
+               /* Avoid accidentally clobbering active alarms; we can't
+                * entirely prevent that here, without even the minimal
+                * locking from the /dev/rtcN api.
+                */
+               retval = rtc_read_alarm(dev, &alm);
+               if (retval < 0)
+                       return retval;
+               if (alm.enabled)
+                       return -EBUSY;
+
+               alm.enabled = 1;
+       } else {
+               alm.enabled = 0;
+
+               /* Provide a valid future alarm time.  Linux isn't EFI,
+                * this time won't be ignored when disabling the alarm.
+                */
+               alarm = now + 300;
+       }
+       rtc_time_to_tm(alarm, &alm.time);
+
+       retval = rtc_set_alarm(dev, &alm);
+       return (retval < 0) ? retval : n;
+}
+static const CLASS_DEVICE_ATTR(wakealarm, S_IRUGO | S_IWUSR,
+               rtc_sysfs_show_wakealarm, rtc_sysfs_set_wakealarm);
+
+
+/* The reason to trigger an alarm with no process watching it (via sysfs)
+ * is its side effect:  waking from a system state like suspend-to-RAM or
+ * suspend-to-disk.  So: no attribute unless that side effect is possible.
+ * (Userspace may disable that mechanism later.)
+ */
+static inline int rtc_does_wakealarm(struct class_device *class_dev)
+{
+       struct rtc_device *rtc;
+
+       if (!device_can_wakeup(class_dev->dev))
+               return 0;
+       rtc = to_rtc_device(class_dev);
+       return rtc->ops->set_alarm != NULL;
+}
+
+
 static int rtc_sysfs_add_device(struct class_device *class_dev,
                                        struct class_interface *class_intf)
 {
@@ -87,8 +173,18 @@ static int rtc_sysfs_add_device(struct class_device *class_dev,
 
        err = sysfs_create_group(&class_dev->kobj, &rtc_attr_group);
        if (err)
-               dev_err(class_dev->dev,
-                       "failed to create sysfs attributes\n");
+               dev_err(class_dev->dev, "failed to create %s\n",
+                               "sysfs attributes");
+       else if (rtc_does_wakealarm(class_dev)) {
+               /* not all RTCs support both alarms and wakeup */
+               err = class_device_create_file(class_dev,
+                                       &class_device_attr_wakealarm);
+               if (err) {
+                       dev_err(class_dev->dev, "failed to create %s\n",
+                                       "alarm attribute");
+                       sysfs_remove_group(&class_dev->kobj, &rtc_attr_group);
+               }
+       }
 
        return err;
 }
@@ -96,6 +192,9 @@ static int rtc_sysfs_add_device(struct class_device *class_dev,
 static void rtc_sysfs_remove_device(struct class_device *class_dev,
                                struct class_interface *class_intf)
 {
+       if (rtc_does_wakealarm(class_dev))
+               class_device_remove_file(class_dev,
+                               &class_device_attr_wakealarm);
        sysfs_remove_group(&class_dev->kobj, &rtc_attr_group);
 }