|
@@ -788,22 +788,66 @@ void timekeeping_inject_sleeptime(struct timespec *delta)
|
|
|
static void timekeeping_resume(void)
|
|
|
{
|
|
|
struct timekeeper *tk = &timekeeper;
|
|
|
+ struct clocksource *clock = tk->clock;
|
|
|
unsigned long flags;
|
|
|
- struct timespec ts;
|
|
|
+ struct timespec ts_new, ts_delta;
|
|
|
+ cycle_t cycle_now, cycle_delta;
|
|
|
+ bool suspendtime_found = false;
|
|
|
|
|
|
- read_persistent_clock(&ts);
|
|
|
+ read_persistent_clock(&ts_new);
|
|
|
|
|
|
clockevents_resume();
|
|
|
clocksource_resume();
|
|
|
|
|
|
write_seqlock_irqsave(&tk->lock, flags);
|
|
|
|
|
|
- if (timespec_compare(&ts, &timekeeping_suspend_time) > 0) {
|
|
|
- ts = timespec_sub(ts, timekeeping_suspend_time);
|
|
|
- __timekeeping_inject_sleeptime(tk, &ts);
|
|
|
+ /*
|
|
|
+ * After system resumes, we need to calculate the suspended time and
|
|
|
+ * compensate it for the OS time. There are 3 sources that could be
|
|
|
+ * used: Nonstop clocksource during suspend, persistent clock and rtc
|
|
|
+ * device.
|
|
|
+ *
|
|
|
+ * One specific platform may have 1 or 2 or all of them, and the
|
|
|
+ * preference will be:
|
|
|
+ * suspend-nonstop clocksource -> persistent clock -> rtc
|
|
|
+ * The less preferred source will only be tried if there is no better
|
|
|
+ * usable source. The rtc part is handled separately in rtc core code.
|
|
|
+ */
|
|
|
+ cycle_now = clock->read(clock);
|
|
|
+ if ((clock->flags & CLOCK_SOURCE_SUSPEND_NONSTOP) &&
|
|
|
+ cycle_now > clock->cycle_last) {
|
|
|
+ u64 num, max = ULLONG_MAX;
|
|
|
+ u32 mult = clock->mult;
|
|
|
+ u32 shift = clock->shift;
|
|
|
+ s64 nsec = 0;
|
|
|
+
|
|
|
+ cycle_delta = (cycle_now - clock->cycle_last) & clock->mask;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * "cycle_delta * mutl" may cause 64 bits overflow, if the
|
|
|
+ * suspended time is too long. In that case we need do the
|
|
|
+ * 64 bits math carefully
|
|
|
+ */
|
|
|
+ do_div(max, mult);
|
|
|
+ if (cycle_delta > max) {
|
|
|
+ num = div64_u64(cycle_delta, max);
|
|
|
+ nsec = (((u64) max * mult) >> shift) * num;
|
|
|
+ cycle_delta -= num * max;
|
|
|
+ }
|
|
|
+ nsec += ((u64) cycle_delta * mult) >> shift;
|
|
|
+
|
|
|
+ ts_delta = ns_to_timespec(nsec);
|
|
|
+ suspendtime_found = true;
|
|
|
+ } else if (timespec_compare(&ts_new, &timekeeping_suspend_time) > 0) {
|
|
|
+ ts_delta = timespec_sub(ts_new, timekeeping_suspend_time);
|
|
|
+ suspendtime_found = true;
|
|
|
}
|
|
|
- /* re-base the last cycle value */
|
|
|
- tk->clock->cycle_last = tk->clock->read(tk->clock);
|
|
|
+
|
|
|
+ if (suspendtime_found)
|
|
|
+ __timekeeping_inject_sleeptime(tk, &ts_delta);
|
|
|
+
|
|
|
+ /* Re-base the last cycle value */
|
|
|
+ clock->cycle_last = cycle_now;
|
|
|
tk->ntp_error = 0;
|
|
|
timekeeping_suspended = 0;
|
|
|
timekeeping_update(tk, false);
|