diff options
-rw-r--r-- | drivers/rtc/class.c | 23 | ||||
-rw-r--r-- | include/linux/time.h | 1 | ||||
-rw-r--r-- | kernel/time/timekeeping.c | 56 |
3 files changed, 63 insertions, 17 deletions
diff --git a/drivers/rtc/class.c b/drivers/rtc/class.c index 39013867cbd6..4194e59e14cd 100644 --- a/drivers/rtc/class.c +++ b/drivers/rtc/class.c | |||
@@ -41,26 +41,21 @@ static void rtc_device_release(struct device *dev) | |||
41 | * system's wall clock; restore it on resume(). | 41 | * system's wall clock; restore it on resume(). |
42 | */ | 42 | */ |
43 | 43 | ||
44 | static struct timespec delta; | ||
45 | static time_t oldtime; | 44 | static time_t oldtime; |
45 | static struct timespec oldts; | ||
46 | 46 | ||
47 | static int rtc_suspend(struct device *dev, pm_message_t mesg) | 47 | static int rtc_suspend(struct device *dev, pm_message_t mesg) |
48 | { | 48 | { |
49 | struct rtc_device *rtc = to_rtc_device(dev); | 49 | struct rtc_device *rtc = to_rtc_device(dev); |
50 | struct rtc_time tm; | 50 | struct rtc_time tm; |
51 | struct timespec ts = current_kernel_time(); | ||
52 | 51 | ||
53 | if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0) | 52 | if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0) |
54 | return 0; | 53 | return 0; |
55 | 54 | ||
56 | rtc_read_time(rtc, &tm); | 55 | rtc_read_time(rtc, &tm); |
56 | ktime_get_ts(&oldts); | ||
57 | rtc_tm_to_time(&tm, &oldtime); | 57 | rtc_tm_to_time(&tm, &oldtime); |
58 | 58 | ||
59 | /* RTC precision is 1 second; adjust delta for avg 1/2 sec err */ | ||
60 | set_normalized_timespec(&delta, | ||
61 | ts.tv_sec - oldtime, | ||
62 | ts.tv_nsec - (NSEC_PER_SEC >> 1)); | ||
63 | |||
64 | return 0; | 59 | return 0; |
65 | } | 60 | } |
66 | 61 | ||
@@ -70,10 +65,12 @@ static int rtc_resume(struct device *dev) | |||
70 | struct rtc_time tm; | 65 | struct rtc_time tm; |
71 | time_t newtime; | 66 | time_t newtime; |
72 | struct timespec time; | 67 | struct timespec time; |
68 | struct timespec newts; | ||
73 | 69 | ||
74 | if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0) | 70 | if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0) |
75 | return 0; | 71 | return 0; |
76 | 72 | ||
73 | ktime_get_ts(&newts); | ||
77 | rtc_read_time(rtc, &tm); | 74 | rtc_read_time(rtc, &tm); |
78 | if (rtc_valid_tm(&tm) != 0) { | 75 | if (rtc_valid_tm(&tm) != 0) { |
79 | pr_debug("%s: bogus resume time\n", dev_name(&rtc->dev)); | 76 | pr_debug("%s: bogus resume time\n", dev_name(&rtc->dev)); |
@@ -85,15 +82,13 @@ static int rtc_resume(struct device *dev) | |||
85 | pr_debug("%s: time travel!\n", dev_name(&rtc->dev)); | 82 | pr_debug("%s: time travel!\n", dev_name(&rtc->dev)); |
86 | return 0; | 83 | return 0; |
87 | } | 84 | } |
85 | /* calculate the RTC time delta */ | ||
86 | set_normalized_timespec(&time, newtime - oldtime, 0); | ||
88 | 87 | ||
89 | /* restore wall clock using delta against this RTC; | 88 | /* subtract kernel time between rtc_suspend to rtc_resume */ |
90 | * adjust again for avg 1/2 second RTC sampling error | 89 | time = timespec_sub(time, timespec_sub(newts, oldts)); |
91 | */ | ||
92 | set_normalized_timespec(&time, | ||
93 | newtime + delta.tv_sec, | ||
94 | (NSEC_PER_SEC >> 1) + delta.tv_nsec); | ||
95 | do_settimeofday(&time); | ||
96 | 90 | ||
91 | timekeeping_inject_sleeptime(&time); | ||
97 | return 0; | 92 | return 0; |
98 | } | 93 | } |
99 | 94 | ||
diff --git a/include/linux/time.h b/include/linux/time.h index 454a26205787..4ea5a75fcacd 100644 --- a/include/linux/time.h +++ b/include/linux/time.h | |||
@@ -126,6 +126,7 @@ struct timespec __current_kernel_time(void); /* does not take xtime_lock */ | |||
126 | struct timespec get_monotonic_coarse(void); | 126 | struct timespec get_monotonic_coarse(void); |
127 | void get_xtime_and_monotonic_and_sleep_offset(struct timespec *xtim, | 127 | void get_xtime_and_monotonic_and_sleep_offset(struct timespec *xtim, |
128 | struct timespec *wtom, struct timespec *sleep); | 128 | struct timespec *wtom, struct timespec *sleep); |
129 | void timekeeping_inject_sleeptime(struct timespec *delta); | ||
129 | 130 | ||
130 | #define CURRENT_TIME (current_kernel_time()) | 131 | #define CURRENT_TIME (current_kernel_time()) |
131 | #define CURRENT_TIME_SEC ((struct timespec) { get_seconds(), 0 }) | 132 | #define CURRENT_TIME_SEC ((struct timespec) { get_seconds(), 0 }) |
diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index 8ad5d576755e..8e6a05a5915a 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c | |||
@@ -596,6 +596,58 @@ void __init timekeeping_init(void) | |||
596 | static struct timespec timekeeping_suspend_time; | 596 | static struct timespec timekeeping_suspend_time; |
597 | 597 | ||
598 | /** | 598 | /** |
599 | * __timekeeping_inject_sleeptime - Internal function to add sleep interval | ||
600 | * @delta: pointer to a timespec delta value | ||
601 | * | ||
602 | * Takes a timespec offset measuring a suspend interval and properly | ||
603 | * adds the sleep offset to the timekeeping variables. | ||
604 | */ | ||
605 | static void __timekeeping_inject_sleeptime(struct timespec *delta) | ||
606 | { | ||
607 | xtime = timespec_add(xtime, *delta); | ||
608 | wall_to_monotonic = timespec_sub(wall_to_monotonic, *delta); | ||
609 | total_sleep_time = timespec_add(total_sleep_time, *delta); | ||
610 | } | ||
611 | |||
612 | |||
613 | /** | ||
614 | * timekeeping_inject_sleeptime - Adds suspend interval to timeekeeping values | ||
615 | * @delta: pointer to a timespec delta value | ||
616 | * | ||
617 | * This hook is for architectures that cannot support read_persistent_clock | ||
618 | * because their RTC/persistent clock is only accessible when irqs are enabled. | ||
619 | * | ||
620 | * This function should only be called by rtc_resume(), and allows | ||
621 | * a suspend offset to be injected into the timekeeping values. | ||
622 | */ | ||
623 | void timekeeping_inject_sleeptime(struct timespec *delta) | ||
624 | { | ||
625 | unsigned long flags; | ||
626 | struct timespec ts; | ||
627 | |||
628 | /* Make sure we don't set the clock twice */ | ||
629 | read_persistent_clock(&ts); | ||
630 | if (!(ts.tv_sec == 0 && ts.tv_nsec == 0)) | ||
631 | return; | ||
632 | |||
633 | write_seqlock_irqsave(&xtime_lock, flags); | ||
634 | timekeeping_forward_now(); | ||
635 | |||
636 | __timekeeping_inject_sleeptime(delta); | ||
637 | |||
638 | timekeeper.ntp_error = 0; | ||
639 | ntp_clear(); | ||
640 | update_vsyscall(&xtime, &wall_to_monotonic, timekeeper.clock, | ||
641 | timekeeper.mult); | ||
642 | |||
643 | write_sequnlock_irqrestore(&xtime_lock, flags); | ||
644 | |||
645 | /* signal hrtimers about time change */ | ||
646 | clock_was_set(); | ||
647 | } | ||
648 | |||
649 | |||
650 | /** | ||
599 | * timekeeping_resume - Resumes the generic timekeeping subsystem. | 651 | * timekeeping_resume - Resumes the generic timekeeping subsystem. |
600 | * | 652 | * |
601 | * This is for the generic clocksource timekeeping. | 653 | * This is for the generic clocksource timekeeping. |
@@ -615,9 +667,7 @@ static void timekeeping_resume(void) | |||
615 | 667 | ||
616 | if (timespec_compare(&ts, &timekeeping_suspend_time) > 0) { | 668 | if (timespec_compare(&ts, &timekeeping_suspend_time) > 0) { |
617 | ts = timespec_sub(ts, timekeeping_suspend_time); | 669 | ts = timespec_sub(ts, timekeeping_suspend_time); |
618 | xtime = timespec_add(xtime, ts); | 670 | __timekeeping_inject_sleeptime(&ts); |
619 | wall_to_monotonic = timespec_sub(wall_to_monotonic, ts); | ||
620 | total_sleep_time = timespec_add(total_sleep_time, ts); | ||
621 | } | 671 | } |
622 | /* re-base the last cycle value */ | 672 | /* re-base the last cycle value */ |
623 | timekeeper.clock->cycle_last = timekeeper.clock->read(timekeeper.clock); | 673 | timekeeper.clock->cycle_last = timekeeper.clock->read(timekeeper.clock); |