Merge branch 'timers-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
[pandora-kernel.git] / drivers / rtc / class.c
index 4194e59..01a7df5 100644 (file)
@@ -41,20 +41,41 @@ static void rtc_device_release(struct device *dev)
  * system's wall clock; restore it on resume().
  */
 
-static time_t          oldtime;
-static struct timespec oldts;
+static struct timespec old_rtc, old_system, old_delta;
+
 
 static int rtc_suspend(struct device *dev, pm_message_t mesg)
 {
        struct rtc_device       *rtc = to_rtc_device(dev);
        struct rtc_time         tm;
-
+       struct timespec         delta, delta_delta;
        if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0)
                return 0;
 
+       /* snapshot the current RTC and system time at suspend*/
        rtc_read_time(rtc, &tm);
-       ktime_get_ts(&oldts);
-       rtc_tm_to_time(&tm, &oldtime);
+       getnstimeofday(&old_system);
+       rtc_tm_to_time(&tm, &old_rtc.tv_sec);
+
+
+       /*
+        * To avoid drift caused by repeated suspend/resumes,
+        * which each can add ~1 second drift error,
+        * try to compensate so the difference in system time
+        * and rtc time stays close to constant.
+        */
+       delta = timespec_sub(old_system, old_rtc);
+       delta_delta = timespec_sub(delta, old_delta);
+       if (abs(delta_delta.tv_sec)  >= 2) {
+               /*
+                * if delta_delta is too large, assume time correction
+                * has occured and set old_delta to the current delta.
+                */
+               old_delta = delta;
+       } else {
+               /* Otherwise try to adjust old_system to compensate */
+               old_system = timespec_sub(old_system, delta_delta);
+       }
 
        return 0;
 }
@@ -63,32 +84,42 @@ static int rtc_resume(struct device *dev)
 {
        struct rtc_device       *rtc = to_rtc_device(dev);
        struct rtc_time         tm;
-       time_t                  newtime;
-       struct timespec         time;
-       struct timespec         newts;
+       struct timespec         new_system, new_rtc;
+       struct timespec         sleep_time;
 
        if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0)
                return 0;
 
-       ktime_get_ts(&newts);
+       /* snapshot the current rtc and system time at resume */
+       getnstimeofday(&new_system);
        rtc_read_time(rtc, &tm);
        if (rtc_valid_tm(&tm) != 0) {
                pr_debug("%s:  bogus resume time\n", dev_name(&rtc->dev));
                return 0;
        }
-       rtc_tm_to_time(&tm, &newtime);
-       if (newtime <= oldtime) {
-               if (newtime < oldtime)
+       rtc_tm_to_time(&tm, &new_rtc.tv_sec);
+       new_rtc.tv_nsec = 0;
+
+       if (new_rtc.tv_sec <= old_rtc.tv_sec) {
+               if (new_rtc.tv_sec < old_rtc.tv_sec)
                        pr_debug("%s:  time travel!\n", dev_name(&rtc->dev));
                return 0;
        }
-       /* calculate the RTC time delta */
-       set_normalized_timespec(&time, newtime - oldtime, 0);
 
-       /* subtract kernel time between rtc_suspend to rtc_resume */
-       time = timespec_sub(time, timespec_sub(newts, oldts));
+       /* calculate the RTC time delta (sleep time)*/
+       sleep_time = timespec_sub(new_rtc, old_rtc);
+
+       /*
+        * Since these RTC suspend/resume handlers are not called
+        * at the very end of suspend or the start of resume,
+        * some run-time may pass on either sides of the sleep time
+        * so subtract kernel run-time between rtc_suspend to rtc_resume
+        * to keep things accurate.
+        */
+       sleep_time = timespec_sub(sleep_time,
+                       timespec_sub(new_system, old_system));
 
-       timekeeping_inject_sleeptime(&time);
+       timekeeping_inject_sleeptime(&sleep_time);
        return 0;
 }