Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
5e3fd9e5 | 2 | /* |
3 | * rtc-efi: RTC Class Driver for EFI-based systems | |
4 | * | |
5 | * Copyright (C) 2009 Hewlett-Packard Development Company, L.P. | |
6 | * | |
37563e5e | 7 | * Author: dann frazier <dannf@dannf.org> |
5e3fd9e5 | 8 | * Based on efirtc.c by Stephane Eranian |
5e3fd9e5 | 9 | */ |
10 | ||
34650f9e JH |
11 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
12 | ||
5e3fd9e5 | 13 | #include <linux/kernel.h> |
14 | #include <linux/module.h> | |
6e85bab6 | 15 | #include <linux/stringify.h> |
5e3fd9e5 | 16 | #include <linux/time.h> |
17 | #include <linux/platform_device.h> | |
18 | #include <linux/rtc.h> | |
19 | #include <linux/efi.h> | |
20 | ||
21 | #define EFI_ISDST (EFI_TIME_ADJUST_DAYLIGHT|EFI_TIME_IN_DAYLIGHT) | |
5e3fd9e5 | 22 | |
23 | /* | |
24 | * returns day of the year [0-365] | |
25 | */ | |
26 | static inline int | |
27 | compute_yday(efi_time_t *eft) | |
28 | { | |
29 | /* efi_time_t.month is in the [1-12] so, we need -1 */ | |
809d9627 | 30 | return rtc_year_days(eft->day, eft->month - 1, eft->year); |
5e3fd9e5 | 31 | } |
b2bd2370 | 32 | |
5e3fd9e5 | 33 | /* |
34 | * returns day of the week [0-6] 0=Sunday | |
5e3fd9e5 | 35 | */ |
36 | static int | |
b2bd2370 | 37 | compute_wday(efi_time_t *eft, int yday) |
5e3fd9e5 | 38 | { |
b2bd2370 AB |
39 | int ndays = eft->year * (365 % 7) |
40 | + (eft->year - 1) / 4 | |
41 | - (eft->year - 1) / 100 | |
42 | + (eft->year - 1) / 400 | |
43 | + yday; | |
5e3fd9e5 | 44 | |
45 | /* | |
b2bd2370 AB |
46 | * 1/1/0000 may or may not have been a Sunday (if it ever existed at |
47 | * all) but assuming it was makes this calculation work correctly. | |
5e3fd9e5 | 48 | */ |
b2bd2370 | 49 | return ndays % 7; |
5e3fd9e5 | 50 | } |
51 | ||
52 | static void | |
53 | convert_to_efi_time(struct rtc_time *wtime, efi_time_t *eft) | |
54 | { | |
55 | eft->year = wtime->tm_year + 1900; | |
56 | eft->month = wtime->tm_mon + 1; | |
57 | eft->day = wtime->tm_mday; | |
58 | eft->hour = wtime->tm_hour; | |
59 | eft->minute = wtime->tm_min; | |
34650f9e | 60 | eft->second = wtime->tm_sec; |
5e3fd9e5 | 61 | eft->nanosecond = 0; |
62 | eft->daylight = wtime->tm_isdst ? EFI_ISDST : 0; | |
63 | eft->timezone = EFI_UNSPECIFIED_TIMEZONE; | |
64 | } | |
65 | ||
6e85bab6 | 66 | static bool |
5e3fd9e5 | 67 | convert_from_efi_time(efi_time_t *eft, struct rtc_time *wtime) |
68 | { | |
69 | memset(wtime, 0, sizeof(*wtime)); | |
6e85bab6 JB |
70 | |
71 | if (eft->second >= 60) | |
72 | return false; | |
5e3fd9e5 | 73 | wtime->tm_sec = eft->second; |
6e85bab6 JB |
74 | |
75 | if (eft->minute >= 60) | |
76 | return false; | |
5e3fd9e5 | 77 | wtime->tm_min = eft->minute; |
6e85bab6 JB |
78 | |
79 | if (eft->hour >= 24) | |
80 | return false; | |
5e3fd9e5 | 81 | wtime->tm_hour = eft->hour; |
6e85bab6 JB |
82 | |
83 | if (!eft->day || eft->day > 31) | |
84 | return false; | |
5e3fd9e5 | 85 | wtime->tm_mday = eft->day; |
6e85bab6 JB |
86 | |
87 | if (!eft->month || eft->month > 12) | |
88 | return false; | |
5e3fd9e5 | 89 | wtime->tm_mon = eft->month - 1; |
5e3fd9e5 | 90 | |
b2bd2370 | 91 | if (eft->year < 1900 || eft->year > 9999) |
6e85bab6 | 92 | return false; |
b2bd2370 | 93 | wtime->tm_year = eft->year - 1900; |
5e3fd9e5 | 94 | |
95 | /* day in the year [1-365]*/ | |
96 | wtime->tm_yday = compute_yday(eft); | |
97 | ||
b2bd2370 AB |
98 | /* day of the week [0-6], Sunday=0 */ |
99 | wtime->tm_wday = compute_wday(eft, wtime->tm_yday); | |
5e3fd9e5 | 100 | |
101 | switch (eft->daylight & EFI_ISDST) { | |
102 | case EFI_ISDST: | |
103 | wtime->tm_isdst = 1; | |
104 | break; | |
105 | case EFI_TIME_ADJUST_DAYLIGHT: | |
106 | wtime->tm_isdst = 0; | |
107 | break; | |
108 | default: | |
109 | wtime->tm_isdst = -1; | |
110 | } | |
6e85bab6 JB |
111 | |
112 | return true; | |
5e3fd9e5 | 113 | } |
114 | ||
115 | static int efi_read_alarm(struct device *dev, struct rtc_wkalrm *wkalrm) | |
116 | { | |
117 | efi_time_t eft; | |
118 | efi_status_t status; | |
119 | ||
120 | /* | |
121 | * As of EFI v1.10, this call always returns an unsupported status | |
122 | */ | |
123 | status = efi.get_wakeup_time((efi_bool_t *)&wkalrm->enabled, | |
124 | (efi_bool_t *)&wkalrm->pending, &eft); | |
125 | ||
126 | if (status != EFI_SUCCESS) | |
127 | return -EINVAL; | |
128 | ||
6e85bab6 JB |
129 | if (!convert_from_efi_time(&eft, &wkalrm->time)) |
130 | return -EIO; | |
5e3fd9e5 | 131 | |
132 | return rtc_valid_tm(&wkalrm->time); | |
133 | } | |
134 | ||
135 | static int efi_set_alarm(struct device *dev, struct rtc_wkalrm *wkalrm) | |
136 | { | |
137 | efi_time_t eft; | |
138 | efi_status_t status; | |
139 | ||
140 | convert_to_efi_time(&wkalrm->time, &eft); | |
141 | ||
142 | /* | |
143 | * XXX Fixme: | |
144 | * As of EFI 0.92 with the firmware I have on my | |
145 | * machine this call does not seem to work quite | |
146 | * right | |
147 | * | |
148 | * As of v1.10, this call always returns an unsupported status | |
149 | */ | |
150 | status = efi.set_wakeup_time((efi_bool_t)wkalrm->enabled, &eft); | |
151 | ||
34650f9e | 152 | dev_warn(dev, "write status is %d\n", (int)status); |
5e3fd9e5 | 153 | |
154 | return status == EFI_SUCCESS ? 0 : -EINVAL; | |
155 | } | |
156 | ||
157 | static int efi_read_time(struct device *dev, struct rtc_time *tm) | |
158 | { | |
159 | efi_status_t status; | |
160 | efi_time_t eft; | |
161 | efi_time_cap_t cap; | |
162 | ||
163 | status = efi.get_time(&eft, &cap); | |
164 | ||
165 | if (status != EFI_SUCCESS) { | |
166 | /* should never happen */ | |
668a2abf | 167 | dev_err_once(dev, "can't read time\n"); |
5e3fd9e5 | 168 | return -EINVAL; |
169 | } | |
170 | ||
6e85bab6 JB |
171 | if (!convert_from_efi_time(&eft, tm)) |
172 | return -EIO; | |
5e3fd9e5 | 173 | |
22652ba7 | 174 | return 0; |
5e3fd9e5 | 175 | } |
176 | ||
177 | static int efi_set_time(struct device *dev, struct rtc_time *tm) | |
178 | { | |
179 | efi_status_t status; | |
180 | efi_time_t eft; | |
181 | ||
182 | convert_to_efi_time(tm, &eft); | |
183 | ||
184 | status = efi.set_time(&eft); | |
185 | ||
186 | return status == EFI_SUCCESS ? 0 : -EINVAL; | |
187 | } | |
188 | ||
501385f2 GT |
189 | static int efi_procfs(struct device *dev, struct seq_file *seq) |
190 | { | |
101ca8d0 SD |
191 | efi_time_t eft, alm; |
192 | efi_time_cap_t cap; | |
193 | efi_bool_t enabled, pending; | |
194 | struct rtc_device *rtc = dev_get_drvdata(dev); | |
501385f2 GT |
195 | |
196 | memset(&eft, 0, sizeof(eft)); | |
197 | memset(&alm, 0, sizeof(alm)); | |
198 | memset(&cap, 0, sizeof(cap)); | |
199 | ||
200 | efi.get_time(&eft, &cap); | |
201 | efi.get_wakeup_time(&enabled, &pending, &alm); | |
202 | ||
203 | seq_printf(seq, | |
204 | "Time\t\t: %u:%u:%u.%09u\n" | |
205 | "Date\t\t: %u-%u-%u\n" | |
206 | "Daylight\t: %u\n", | |
207 | eft.hour, eft.minute, eft.second, eft.nanosecond, | |
208 | eft.year, eft.month, eft.day, | |
209 | eft.daylight); | |
210 | ||
211 | if (eft.timezone == EFI_UNSPECIFIED_TIMEZONE) | |
212 | seq_puts(seq, "Timezone\t: unspecified\n"); | |
213 | else | |
214 | /* XXX fixme: convert to string? */ | |
215 | seq_printf(seq, "Timezone\t: %u\n", eft.timezone); | |
216 | ||
101ca8d0 SD |
217 | if (test_bit(RTC_FEATURE_ALARM, rtc->features)) { |
218 | seq_printf(seq, | |
219 | "Alarm Time\t: %u:%u:%u.%09u\n" | |
220 | "Alarm Date\t: %u-%u-%u\n" | |
221 | "Alarm Daylight\t: %u\n" | |
222 | "Enabled\t\t: %s\n" | |
223 | "Pending\t\t: %s\n", | |
224 | alm.hour, alm.minute, alm.second, alm.nanosecond, | |
225 | alm.year, alm.month, alm.day, | |
226 | alm.daylight, | |
227 | enabled == 1 ? "yes" : "no", | |
228 | pending == 1 ? "yes" : "no"); | |
229 | ||
f5f4c982 | 230 | if (alm.timezone == EFI_UNSPECIFIED_TIMEZONE) |
101ca8d0 SD |
231 | seq_puts(seq, "Timezone\t: unspecified\n"); |
232 | else | |
233 | /* XXX fixme: convert to string? */ | |
234 | seq_printf(seq, "Timezone\t: %u\n", alm.timezone); | |
235 | } | |
501385f2 GT |
236 | |
237 | /* | |
238 | * now prints the capabilities | |
239 | */ | |
240 | seq_printf(seq, | |
241 | "Resolution\t: %u\n" | |
242 | "Accuracy\t: %u\n" | |
243 | "SetstoZero\t: %u\n", | |
244 | cap.resolution, cap.accuracy, cap.sets_to_zero); | |
245 | ||
246 | return 0; | |
247 | } | |
248 | ||
5e3fd9e5 | 249 | static const struct rtc_class_ops efi_rtc_ops = { |
501385f2 GT |
250 | .read_time = efi_read_time, |
251 | .set_time = efi_set_time, | |
252 | .read_alarm = efi_read_alarm, | |
253 | .set_alarm = efi_set_alarm, | |
254 | .proc = efi_procfs, | |
5e3fd9e5 | 255 | }; |
256 | ||
257 | static int __init efi_rtc_probe(struct platform_device *dev) | |
258 | { | |
259 | struct rtc_device *rtc; | |
7368c69c AG |
260 | efi_time_t eft; |
261 | efi_time_cap_t cap; | |
262 | ||
263 | /* First check if the RTC is usable */ | |
264 | if (efi.get_time(&eft, &cap) != EFI_SUCCESS) | |
265 | return -ENODEV; | |
5e3fd9e5 | 266 | |
8aa74363 | 267 | rtc = devm_rtc_allocate_device(&dev->dev); |
5e3fd9e5 | 268 | if (IS_ERR(rtc)) |
269 | return PTR_ERR(rtc); | |
270 | ||
271 | platform_set_drvdata(dev, rtc); | |
272 | ||
8aa74363 | 273 | rtc->ops = &efi_rtc_ops; |
1350b94c | 274 | clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, rtc->features); |
101ca8d0 SD |
275 | if (efi_rt_services_supported(EFI_RT_SUPPORTED_WAKEUP_SERVICES)) |
276 | set_bit(RTC_FEATURE_ALARM_WAKEUP_ONLY, rtc->features); | |
277 | else | |
278 | clear_bit(RTC_FEATURE_ALARM, rtc->features); | |
8aa74363 | 279 | |
eec79501 RL |
280 | device_init_wakeup(&dev->dev, true); |
281 | ||
8aa74363 | 282 | return devm_rtc_register_device(rtc); |
5e3fd9e5 | 283 | } |
284 | ||
5e3fd9e5 | 285 | static struct platform_driver efi_rtc_driver = { |
286 | .driver = { | |
287 | .name = "rtc-efi", | |
5e3fd9e5 | 288 | }, |
5e3fd9e5 | 289 | }; |
290 | ||
52c6ecbc | 291 | module_platform_driver_probe(efi_rtc_driver, efi_rtc_probe); |
5e3fd9e5 | 292 | |
37563e5e | 293 | MODULE_AUTHOR("dann frazier <dannf@dannf.org>"); |
5e3fd9e5 | 294 | MODULE_LICENSE("GPL"); |
295 | MODULE_DESCRIPTION("EFI RTC driver"); | |
3f71f6da | 296 | MODULE_ALIAS("platform:rtc-efi"); |