Merge branch 'timers-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
[pandora-kernel.git] / kernel / time / timekeeping.c
index 8ad5d57..2b021b0 100644 (file)
@@ -595,6 +595,64 @@ void __init timekeeping_init(void)
 /* time in seconds when suspend began */
 static struct timespec timekeeping_suspend_time;
 
+/**
+ * __timekeeping_inject_sleeptime - Internal function to add sleep interval
+ * @delta: pointer to a timespec delta value
+ *
+ * Takes a timespec offset measuring a suspend interval and properly
+ * adds the sleep offset to the timekeeping variables.
+ */
+static void __timekeeping_inject_sleeptime(struct timespec *delta)
+{
+       if (!timespec_valid(delta)) {
+               printk(KERN_WARNING "__timekeeping_inject_sleeptime: Invalid "
+                                       "sleep delta value!\n");
+               return;
+       }
+
+       xtime = timespec_add(xtime, *delta);
+       wall_to_monotonic = timespec_sub(wall_to_monotonic, *delta);
+       total_sleep_time = timespec_add(total_sleep_time, *delta);
+}
+
+
+/**
+ * timekeeping_inject_sleeptime - Adds suspend interval to timeekeeping values
+ * @delta: pointer to a timespec delta value
+ *
+ * This hook is for architectures that cannot support read_persistent_clock
+ * because their RTC/persistent clock is only accessible when irqs are enabled.
+ *
+ * This function should only be called by rtc_resume(), and allows
+ * a suspend offset to be injected into the timekeeping values.
+ */
+void timekeeping_inject_sleeptime(struct timespec *delta)
+{
+       unsigned long flags;
+       struct timespec ts;
+
+       /* Make sure we don't set the clock twice */
+       read_persistent_clock(&ts);
+       if (!(ts.tv_sec == 0 && ts.tv_nsec == 0))
+               return;
+
+       write_seqlock_irqsave(&xtime_lock, flags);
+       timekeeping_forward_now();
+
+       __timekeeping_inject_sleeptime(delta);
+
+       timekeeper.ntp_error = 0;
+       ntp_clear();
+       update_vsyscall(&xtime, &wall_to_monotonic, timekeeper.clock,
+                               timekeeper.mult);
+
+       write_sequnlock_irqrestore(&xtime_lock, flags);
+
+       /* signal hrtimers about time change */
+       clock_was_set();
+}
+
+
 /**
  * timekeeping_resume - Resumes the generic timekeeping subsystem.
  *
@@ -615,9 +673,7 @@ static void timekeeping_resume(void)
 
        if (timespec_compare(&ts, &timekeeping_suspend_time) > 0) {
                ts = timespec_sub(ts, timekeeping_suspend_time);
-               xtime = timespec_add(xtime, ts);
-               wall_to_monotonic = timespec_sub(wall_to_monotonic, ts);
-               total_sleep_time = timespec_add(total_sleep_time, ts);
+               __timekeeping_inject_sleeptime(&ts);
        }
        /* re-base the last cycle value */
        timekeeper.clock->cycle_last = timekeeper.clock->read(timekeeper.clock);
@@ -630,18 +686,40 @@ static void timekeeping_resume(void)
        clockevents_notify(CLOCK_EVT_NOTIFY_RESUME, NULL);
 
        /* Resume hrtimers */
-       hres_timers_resume();
+       hrtimers_resume();
 }
 
 static int timekeeping_suspend(void)
 {
        unsigned long flags;
+       struct timespec         delta, delta_delta;
+       static struct timespec  old_delta;
 
        read_persistent_clock(&timekeeping_suspend_time);
 
        write_seqlock_irqsave(&xtime_lock, flags);
        timekeeping_forward_now();
        timekeeping_suspended = 1;
+
+       /*
+        * 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 persistent_clock time stays close to constant.
+        */
+       delta = timespec_sub(xtime, timekeeping_suspend_time);
+       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 */
+               timekeeping_suspend_time =
+                       timespec_add(timekeeping_suspend_time, delta_delta);
+       }
        write_sequnlock_irqrestore(&xtime_lock, flags);
 
        clockevents_notify(CLOCK_EVT_NOTIFY_SUSPEND, NULL);
@@ -1048,6 +1126,21 @@ void get_xtime_and_monotonic_and_sleep_offset(struct timespec *xtim,
        } while (read_seqretry(&xtime_lock, seq));
 }
 
+/**
+ * ktime_get_monotonic_offset() - get wall_to_monotonic in ktime_t format
+ */
+ktime_t ktime_get_monotonic_offset(void)
+{
+       unsigned long seq;
+       struct timespec wtom;
+
+       do {
+               seq = read_seqbegin(&xtime_lock);
+               wtom = wall_to_monotonic;
+       } while (read_seqretry(&xtime_lock, seq));
+       return timespec_to_ktime(wtom);
+}
+
 /**
  * xtime_update() - advances the timekeeping infrastructure
  * @ticks:     number of ticks, that have elapsed since the last call.