Skip to content

Commit 9895156

Browse files
wangbaolin719alexandrebelloni
authored andcommitted
rtc: Add one offset seconds to expand RTC range
From our investigation for all RTC drivers, 1 driver will be expired before year 2017, 7 drivers will be expired before year 2038, 23 drivers will be expired before year 2069, 72 drivers will be expired before 2100 and 104 drivers will be expired before 2106. Especially for these early expired drivers, we need to expand the RTC range to make the RTC can still work after the expired year. So we can expand the RTC range by adding one offset to the time when reading from hardware, and subtracting it when writing back. For example, if you have an RTC that can do 100 years, and currently is configured to be based in Jan 1 1970, so it can represents times from 1970 to 2069. Then if you change the start year from 1970 to 2000, which means it can represents times from 2000 to 2099. By adding or subtracting the offset produced by moving the wrap point, all times between 1970 and 1999 from RTC hardware could get interpreted as times from 2070 to 2099, but the interpretation of dates between 2000 and 2069 would not change. Signed-off-by: Baolin Wang <baolin.wang@linaro.org> Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
1 parent 4c4e5df commit 9895156

File tree

3 files changed

+131
-1
lines changed

3 files changed

+131
-1
lines changed

drivers/rtc/class.c

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,73 @@ static int rtc_device_get_id(struct device *dev)
211211
return id;
212212
}
213213

214+
static void rtc_device_get_offset(struct rtc_device *rtc)
215+
{
216+
time64_t range_secs;
217+
u32 start_year;
218+
int ret;
219+
220+
/*
221+
* If RTC driver did not implement the range of RTC hardware device,
222+
* then we can not expand the RTC range by adding or subtracting one
223+
* offset.
224+
*/
225+
if (rtc->range_min == rtc->range_max)
226+
return;
227+
228+
ret = device_property_read_u32(rtc->dev.parent, "start-year",
229+
&start_year);
230+
if (!ret) {
231+
rtc->start_secs = mktime64(start_year, 1, 1, 0, 0, 0);
232+
rtc->set_start_time = true;
233+
}
234+
235+
/*
236+
* If user did not implement the start time for RTC driver, then no
237+
* need to expand the RTC range.
238+
*/
239+
if (!rtc->set_start_time)
240+
return;
241+
242+
range_secs = rtc->range_max - rtc->range_min + 1;
243+
244+
/*
245+
* If the start_secs is larger than the maximum seconds (rtc->range_max)
246+
* supported by RTC hardware or the maximum seconds of new expanded
247+
* range (start_secs + rtc->range_max - rtc->range_min) is less than
248+
* rtc->range_min, which means the minimum seconds (rtc->range_min) of
249+
* RTC hardware will be mapped to start_secs by adding one offset, so
250+
* the offset seconds calculation formula should be:
251+
* rtc->offset_secs = rtc->start_secs - rtc->range_min;
252+
*
253+
* If the start_secs is larger than the minimum seconds (rtc->range_min)
254+
* supported by RTC hardware, then there is one region is overlapped
255+
* between the original RTC hardware range and the new expanded range,
256+
* and this overlapped region do not need to be mapped into the new
257+
* expanded range due to it is valid for RTC device. So the minimum
258+
* seconds of RTC hardware (rtc->range_min) should be mapped to
259+
* rtc->range_max + 1, then the offset seconds formula should be:
260+
* rtc->offset_secs = rtc->range_max - rtc->range_min + 1;
261+
*
262+
* If the start_secs is less than the minimum seconds (rtc->range_min),
263+
* which is similar to case 2. So the start_secs should be mapped to
264+
* start_secs + rtc->range_max - rtc->range_min + 1, then the
265+
* offset seconds formula should be:
266+
* rtc->offset_secs = -(rtc->range_max - rtc->range_min + 1);
267+
*
268+
* Otherwise the offset seconds should be 0.
269+
*/
270+
if (rtc->start_secs > rtc->range_max ||
271+
rtc->start_secs + range_secs - 1 < rtc->range_min)
272+
rtc->offset_secs = rtc->start_secs - rtc->range_min;
273+
else if (rtc->start_secs > rtc->range_min)
274+
rtc->offset_secs = range_secs;
275+
else if (rtc->start_secs < rtc->range_min)
276+
rtc->offset_secs = -range_secs;
277+
else
278+
rtc->offset_secs = 0;
279+
}
280+
214281
/**
215282
* rtc_device_register - register w/ RTC class
216283
* @dev: the device to register
@@ -247,6 +314,8 @@ struct rtc_device *rtc_device_register(const char *name, struct device *dev,
247314

248315
dev_set_name(&rtc->dev, "rtc%d", id);
249316

317+
rtc_device_get_offset(rtc);
318+
250319
/* Check to see if there is an ALARM already set in hw */
251320
err = __rtc_read_alarm(rtc, &alrm);
252321

@@ -436,6 +505,7 @@ int __rtc_register_device(struct module *owner, struct rtc_device *rtc)
436505
return -EINVAL;
437506

438507
rtc->owner = owner;
508+
rtc_device_get_offset(rtc);
439509

440510
/* Check to see if there is an ALARM already set in hw */
441511
err = __rtc_read_alarm(rtc, &alrm);

drivers/rtc/interface.c

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,61 @@
2323
static int rtc_timer_enqueue(struct rtc_device *rtc, struct rtc_timer *timer);
2424
static void rtc_timer_remove(struct rtc_device *rtc, struct rtc_timer *timer);
2525

26+
static void rtc_add_offset(struct rtc_device *rtc, struct rtc_time *tm)
27+
{
28+
time64_t secs;
29+
30+
if (!rtc->offset_secs)
31+
return;
32+
33+
secs = rtc_tm_to_time64(tm);
34+
35+
/*
36+
* Since the reading time values from RTC device are always in the RTC
37+
* original valid range, but we need to skip the overlapped region
38+
* between expanded range and original range, which is no need to add
39+
* the offset.
40+
*/
41+
if ((rtc->start_secs > rtc->range_min && secs >= rtc->start_secs) ||
42+
(rtc->start_secs < rtc->range_min &&
43+
secs <= (rtc->start_secs + rtc->range_max - rtc->range_min)))
44+
return;
45+
46+
rtc_time64_to_tm(secs + rtc->offset_secs, tm);
47+
}
48+
49+
static void rtc_subtract_offset(struct rtc_device *rtc, struct rtc_time *tm)
50+
{
51+
time64_t secs;
52+
53+
if (!rtc->offset_secs)
54+
return;
55+
56+
secs = rtc_tm_to_time64(tm);
57+
58+
/*
59+
* If the setting time values are in the valid range of RTC hardware
60+
* device, then no need to subtract the offset when setting time to RTC
61+
* device. Otherwise we need to subtract the offset to make the time
62+
* values are valid for RTC hardware device.
63+
*/
64+
if (secs >= rtc->range_min && secs <= rtc->range_max)
65+
return;
66+
67+
rtc_time64_to_tm(secs - rtc->offset_secs, tm);
68+
}
69+
2670
static int rtc_valid_range(struct rtc_device *rtc, struct rtc_time *tm)
2771
{
2872
if (rtc->range_min != rtc->range_max) {
2973
time64_t time = rtc_tm_to_time64(tm);
74+
time64_t range_min = rtc->set_start_time ? rtc->start_secs :
75+
rtc->range_min;
76+
time64_t range_max = rtc->set_start_time ?
77+
(rtc->start_secs + rtc->range_max - rtc->range_min) :
78+
rtc->range_max;
3079

31-
if (time < rtc->range_min || time > rtc->range_max)
80+
if (time < range_min || time > range_max)
3281
return -ERANGE;
3382
}
3483

@@ -51,6 +100,8 @@ static int __rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)
51100
return err;
52101
}
53102

103+
rtc_add_offset(rtc, tm);
104+
54105
err = rtc_valid_tm(tm);
55106
if (err < 0)
56107
dev_dbg(&rtc->dev, "read_time: rtc_time isn't valid\n");
@@ -86,6 +137,8 @@ int rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm)
86137
if (err)
87138
return err;
88139

140+
rtc_subtract_offset(rtc, tm);
141+
89142
err = mutex_lock_interruptible(&rtc->ops_lock);
90143
if (err)
91144
return err;
@@ -355,6 +408,8 @@ static int __rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
355408
err = rtc_valid_tm(&alarm->time);
356409
if (err)
357410
return err;
411+
412+
rtc_subtract_offset(rtc, &alarm->time);
358413
scheduled = rtc_tm_to_time64(&alarm->time);
359414

360415
/* Make sure we're not setting alarms in the past */
@@ -406,6 +461,8 @@ int rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
406461
err = rtc_timer_enqueue(rtc, &rtc->aie_timer);
407462

408463
mutex_unlock(&rtc->ops_lock);
464+
465+
rtc_add_offset(rtc, &alarm->time);
409466
return err;
410467
}
411468
EXPORT_SYMBOL_GPL(rtc_set_alarm);

include/linux/rtc.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ struct rtc_device {
152152

153153
time64_t range_min;
154154
timeu64_t range_max;
155+
time64_t start_secs;
156+
time64_t offset_secs;
157+
bool set_start_time;
155158

156159
#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
157160
struct work_struct uie_task;

0 commit comments

Comments
 (0)