diff options
Diffstat (limited to 'drivers/rtc')
-rw-r--r-- | drivers/rtc/class.c | 65 |
1 files changed, 48 insertions, 17 deletions
diff --git a/drivers/rtc/class.c b/drivers/rtc/class.c index 4194e59e14cd..01a7df5317c1 100644 --- a/drivers/rtc/class.c +++ b/drivers/rtc/class.c | |||
@@ -41,20 +41,41 @@ 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 time_t oldtime; | 44 | static struct timespec old_rtc, old_system, old_delta; |
45 | static struct timespec oldts; | 45 | |
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 | 51 | struct timespec delta, delta_delta; | |
52 | if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0) | 52 | if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0) |
53 | return 0; | 53 | return 0; |
54 | 54 | ||
55 | /* snapshot the current RTC and system time at suspend*/ | ||
55 | rtc_read_time(rtc, &tm); | 56 | rtc_read_time(rtc, &tm); |
56 | ktime_get_ts(&oldts); | 57 | getnstimeofday(&old_system); |
57 | rtc_tm_to_time(&tm, &oldtime); | 58 | rtc_tm_to_time(&tm, &old_rtc.tv_sec); |
59 | |||
60 | |||
61 | /* | ||
62 | * To avoid drift caused by repeated suspend/resumes, | ||
63 | * which each can add ~1 second drift error, | ||
64 | * try to compensate so the difference in system time | ||
65 | * and rtc time stays close to constant. | ||
66 | */ | ||
67 | delta = timespec_sub(old_system, old_rtc); | ||
68 | delta_delta = timespec_sub(delta, old_delta); | ||
69 | if (abs(delta_delta.tv_sec) >= 2) { | ||
70 | /* | ||
71 | * if delta_delta is too large, assume time correction | ||
72 | * has occured and set old_delta to the current delta. | ||
73 | */ | ||
74 | old_delta = delta; | ||
75 | } else { | ||
76 | /* Otherwise try to adjust old_system to compensate */ | ||
77 | old_system = timespec_sub(old_system, delta_delta); | ||
78 | } | ||
58 | 79 | ||
59 | return 0; | 80 | return 0; |
60 | } | 81 | } |
@@ -63,32 +84,42 @@ static int rtc_resume(struct device *dev) | |||
63 | { | 84 | { |
64 | struct rtc_device *rtc = to_rtc_device(dev); | 85 | struct rtc_device *rtc = to_rtc_device(dev); |
65 | struct rtc_time tm; | 86 | struct rtc_time tm; |
66 | time_t newtime; | 87 | struct timespec new_system, new_rtc; |
67 | struct timespec time; | 88 | struct timespec sleep_time; |
68 | struct timespec newts; | ||
69 | 89 | ||
70 | if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0) | 90 | if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0) |
71 | return 0; | 91 | return 0; |
72 | 92 | ||
73 | ktime_get_ts(&newts); | 93 | /* snapshot the current rtc and system time at resume */ |
94 | getnstimeofday(&new_system); | ||
74 | rtc_read_time(rtc, &tm); | 95 | rtc_read_time(rtc, &tm); |
75 | if (rtc_valid_tm(&tm) != 0) { | 96 | if (rtc_valid_tm(&tm) != 0) { |
76 | pr_debug("%s: bogus resume time\n", dev_name(&rtc->dev)); | 97 | pr_debug("%s: bogus resume time\n", dev_name(&rtc->dev)); |
77 | return 0; | 98 | return 0; |
78 | } | 99 | } |
79 | rtc_tm_to_time(&tm, &newtime); | 100 | rtc_tm_to_time(&tm, &new_rtc.tv_sec); |
80 | if (newtime <= oldtime) { | 101 | new_rtc.tv_nsec = 0; |
81 | if (newtime < oldtime) | 102 | |
103 | if (new_rtc.tv_sec <= old_rtc.tv_sec) { | ||
104 | if (new_rtc.tv_sec < old_rtc.tv_sec) | ||
82 | pr_debug("%s: time travel!\n", dev_name(&rtc->dev)); | 105 | pr_debug("%s: time travel!\n", dev_name(&rtc->dev)); |
83 | return 0; | 106 | return 0; |
84 | } | 107 | } |
85 | /* calculate the RTC time delta */ | ||
86 | set_normalized_timespec(&time, newtime - oldtime, 0); | ||
87 | 108 | ||
88 | /* subtract kernel time between rtc_suspend to rtc_resume */ | 109 | /* calculate the RTC time delta (sleep time)*/ |
89 | time = timespec_sub(time, timespec_sub(newts, oldts)); | 110 | sleep_time = timespec_sub(new_rtc, old_rtc); |
111 | |||
112 | /* | ||
113 | * Since these RTC suspend/resume handlers are not called | ||
114 | * at the very end of suspend or the start of resume, | ||
115 | * some run-time may pass on either sides of the sleep time | ||
116 | * so subtract kernel run-time between rtc_suspend to rtc_resume | ||
117 | * to keep things accurate. | ||
118 | */ | ||
119 | sleep_time = timespec_sub(sleep_time, | ||
120 | timespec_sub(new_system, old_system)); | ||
90 | 121 | ||
91 | timekeeping_inject_sleeptime(&time); | 122 | timekeeping_inject_sleeptime(&sleep_time); |
92 | return 0; | 123 | return 0; |
93 | } | 124 | } |
94 | 125 | ||