xref: /linux/drivers/rtc/rtc-efi.c (revision 0ea5c948cb64bab5bc7a5516774eb8536f05aa0d)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
25e3fd9e5Sdann frazier /*
35e3fd9e5Sdann frazier  * rtc-efi: RTC Class Driver for EFI-based systems
45e3fd9e5Sdann frazier  *
55e3fd9e5Sdann frazier  * Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
65e3fd9e5Sdann frazier  *
737563e5eSdann frazier  * Author: dann frazier <dannf@dannf.org>
85e3fd9e5Sdann frazier  * Based on efirtc.c by Stephane Eranian
95e3fd9e5Sdann frazier  */
105e3fd9e5Sdann frazier 
1134650f9eSJingoo Han #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1234650f9eSJingoo Han 
135e3fd9e5Sdann frazier #include <linux/kernel.h>
145e3fd9e5Sdann frazier #include <linux/module.h>
156e85bab6SJan Beulich #include <linux/stringify.h>
165e3fd9e5Sdann frazier #include <linux/time.h>
175e3fd9e5Sdann frazier #include <linux/platform_device.h>
185e3fd9e5Sdann frazier #include <linux/rtc.h>
195e3fd9e5Sdann frazier #include <linux/efi.h>
205e3fd9e5Sdann frazier 
215e3fd9e5Sdann frazier #define EFI_ISDST (EFI_TIME_ADJUST_DAYLIGHT|EFI_TIME_IN_DAYLIGHT)
225e3fd9e5Sdann frazier 
235e3fd9e5Sdann frazier /*
245e3fd9e5Sdann frazier  * returns day of the year [0-365]
255e3fd9e5Sdann frazier  */
265e3fd9e5Sdann frazier static inline int
compute_yday(efi_time_t * eft)275e3fd9e5Sdann frazier compute_yday(efi_time_t *eft)
285e3fd9e5Sdann frazier {
295e3fd9e5Sdann frazier 	/* efi_time_t.month is in the [1-12] so, we need -1 */
30809d9627SLee, Chun-Yi 	return rtc_year_days(eft->day, eft->month - 1, eft->year);
315e3fd9e5Sdann frazier }
32b2bd2370SArd Biesheuvel 
335e3fd9e5Sdann frazier /*
345e3fd9e5Sdann frazier  * returns day of the week [0-6] 0=Sunday
355e3fd9e5Sdann frazier  */
365e3fd9e5Sdann frazier static int
compute_wday(efi_time_t * eft,int yday)37b2bd2370SArd Biesheuvel compute_wday(efi_time_t *eft, int yday)
385e3fd9e5Sdann frazier {
39b2bd2370SArd Biesheuvel 	int ndays = eft->year * (365 % 7)
40b2bd2370SArd Biesheuvel 		    + (eft->year - 1) / 4
41b2bd2370SArd Biesheuvel 		    - (eft->year - 1) / 100
42b2bd2370SArd Biesheuvel 		    + (eft->year - 1) / 400
43b2bd2370SArd Biesheuvel 		    + yday;
445e3fd9e5Sdann frazier 
455e3fd9e5Sdann frazier 	/*
46b2bd2370SArd Biesheuvel 	 * 1/1/0000 may or may not have been a Sunday (if it ever existed at
47b2bd2370SArd Biesheuvel 	 * all) but assuming it was makes this calculation work correctly.
485e3fd9e5Sdann frazier 	 */
49b2bd2370SArd Biesheuvel 	return ndays % 7;
505e3fd9e5Sdann frazier }
515e3fd9e5Sdann frazier 
525e3fd9e5Sdann frazier static void
convert_to_efi_time(struct rtc_time * wtime,efi_time_t * eft)535e3fd9e5Sdann frazier convert_to_efi_time(struct rtc_time *wtime, efi_time_t *eft)
545e3fd9e5Sdann frazier {
555e3fd9e5Sdann frazier 	eft->year	= wtime->tm_year + 1900;
565e3fd9e5Sdann frazier 	eft->month	= wtime->tm_mon + 1;
575e3fd9e5Sdann frazier 	eft->day	= wtime->tm_mday;
585e3fd9e5Sdann frazier 	eft->hour	= wtime->tm_hour;
595e3fd9e5Sdann frazier 	eft->minute	= wtime->tm_min;
605e3fd9e5Sdann frazier 	eft->second	= wtime->tm_sec;
615e3fd9e5Sdann frazier 	eft->nanosecond = 0;
625e3fd9e5Sdann frazier 	eft->daylight	= wtime->tm_isdst ? EFI_ISDST : 0;
635e3fd9e5Sdann frazier 	eft->timezone	= EFI_UNSPECIFIED_TIMEZONE;
645e3fd9e5Sdann frazier }
655e3fd9e5Sdann frazier 
666e85bab6SJan Beulich static bool
convert_from_efi_time(efi_time_t * eft,struct rtc_time * wtime)675e3fd9e5Sdann frazier convert_from_efi_time(efi_time_t *eft, struct rtc_time *wtime)
685e3fd9e5Sdann frazier {
695e3fd9e5Sdann frazier 	memset(wtime, 0, sizeof(*wtime));
706e85bab6SJan Beulich 
716e85bab6SJan Beulich 	if (eft->second >= 60)
726e85bab6SJan Beulich 		return false;
735e3fd9e5Sdann frazier 	wtime->tm_sec  = eft->second;
746e85bab6SJan Beulich 
756e85bab6SJan Beulich 	if (eft->minute >= 60)
766e85bab6SJan Beulich 		return false;
775e3fd9e5Sdann frazier 	wtime->tm_min  = eft->minute;
786e85bab6SJan Beulich 
796e85bab6SJan Beulich 	if (eft->hour >= 24)
806e85bab6SJan Beulich 		return false;
815e3fd9e5Sdann frazier 	wtime->tm_hour = eft->hour;
826e85bab6SJan Beulich 
836e85bab6SJan Beulich 	if (!eft->day || eft->day > 31)
846e85bab6SJan Beulich 		return false;
855e3fd9e5Sdann frazier 	wtime->tm_mday = eft->day;
866e85bab6SJan Beulich 
876e85bab6SJan Beulich 	if (!eft->month || eft->month > 12)
886e85bab6SJan Beulich 		return false;
895e3fd9e5Sdann frazier 	wtime->tm_mon  = eft->month - 1;
905e3fd9e5Sdann frazier 
91b2bd2370SArd Biesheuvel 	if (eft->year < 1900 || eft->year > 9999)
926e85bab6SJan Beulich 		return false;
93b2bd2370SArd Biesheuvel 	wtime->tm_year = eft->year - 1900;
945e3fd9e5Sdann frazier 
955e3fd9e5Sdann frazier 	/* day in the year [1-365]*/
965e3fd9e5Sdann frazier 	wtime->tm_yday = compute_yday(eft);
975e3fd9e5Sdann frazier 
98b2bd2370SArd Biesheuvel 	/* day of the week [0-6], Sunday=0 */
99b2bd2370SArd Biesheuvel 	wtime->tm_wday = compute_wday(eft, wtime->tm_yday);
1005e3fd9e5Sdann frazier 
1015e3fd9e5Sdann frazier 	switch (eft->daylight & EFI_ISDST) {
1025e3fd9e5Sdann frazier 	case EFI_ISDST:
1035e3fd9e5Sdann frazier 		wtime->tm_isdst = 1;
1045e3fd9e5Sdann frazier 		break;
1055e3fd9e5Sdann frazier 	case EFI_TIME_ADJUST_DAYLIGHT:
1065e3fd9e5Sdann frazier 		wtime->tm_isdst = 0;
1075e3fd9e5Sdann frazier 		break;
1085e3fd9e5Sdann frazier 	default:
1095e3fd9e5Sdann frazier 		wtime->tm_isdst = -1;
1105e3fd9e5Sdann frazier 	}
1116e85bab6SJan Beulich 
1126e85bab6SJan Beulich 	return true;
1135e3fd9e5Sdann frazier }
1145e3fd9e5Sdann frazier 
efi_read_alarm(struct device * dev,struct rtc_wkalrm * wkalrm)1155e3fd9e5Sdann frazier static int efi_read_alarm(struct device *dev, struct rtc_wkalrm *wkalrm)
1165e3fd9e5Sdann frazier {
1175e3fd9e5Sdann frazier 	efi_time_t eft;
1185e3fd9e5Sdann frazier 	efi_status_t status;
1195e3fd9e5Sdann frazier 
1205e3fd9e5Sdann frazier 	/*
1215e3fd9e5Sdann frazier 	 * As of EFI v1.10, this call always returns an unsupported status
1225e3fd9e5Sdann frazier 	 */
1235e3fd9e5Sdann frazier 	status = efi.get_wakeup_time((efi_bool_t *)&wkalrm->enabled,
1245e3fd9e5Sdann frazier 				     (efi_bool_t *)&wkalrm->pending, &eft);
1255e3fd9e5Sdann frazier 
1265e3fd9e5Sdann frazier 	if (status != EFI_SUCCESS)
1275e3fd9e5Sdann frazier 		return -EINVAL;
1285e3fd9e5Sdann frazier 
1296e85bab6SJan Beulich 	if (!convert_from_efi_time(&eft, &wkalrm->time))
1306e85bab6SJan Beulich 		return -EIO;
1315e3fd9e5Sdann frazier 
1325e3fd9e5Sdann frazier 	return rtc_valid_tm(&wkalrm->time);
1335e3fd9e5Sdann frazier }
1345e3fd9e5Sdann frazier 
efi_set_alarm(struct device * dev,struct rtc_wkalrm * wkalrm)1355e3fd9e5Sdann frazier static int efi_set_alarm(struct device *dev, struct rtc_wkalrm *wkalrm)
1365e3fd9e5Sdann frazier {
1375e3fd9e5Sdann frazier 	efi_time_t eft;
1385e3fd9e5Sdann frazier 	efi_status_t status;
1395e3fd9e5Sdann frazier 
1405e3fd9e5Sdann frazier 	convert_to_efi_time(&wkalrm->time, &eft);
1415e3fd9e5Sdann frazier 
1425e3fd9e5Sdann frazier 	/*
1435e3fd9e5Sdann frazier 	 * XXX Fixme:
1445e3fd9e5Sdann frazier 	 * As of EFI 0.92 with the firmware I have on my
1455e3fd9e5Sdann frazier 	 * machine this call does not seem to work quite
1465e3fd9e5Sdann frazier 	 * right
1475e3fd9e5Sdann frazier 	 *
1485e3fd9e5Sdann frazier 	 * As of v1.10, this call always returns an unsupported status
1495e3fd9e5Sdann frazier 	 */
1505e3fd9e5Sdann frazier 	status = efi.set_wakeup_time((efi_bool_t)wkalrm->enabled, &eft);
1515e3fd9e5Sdann frazier 
15234650f9eSJingoo Han 	dev_warn(dev, "write status is %d\n", (int)status);
1535e3fd9e5Sdann frazier 
1545e3fd9e5Sdann frazier 	return status == EFI_SUCCESS ? 0 : -EINVAL;
1555e3fd9e5Sdann frazier }
1565e3fd9e5Sdann frazier 
efi_read_time(struct device * dev,struct rtc_time * tm)1575e3fd9e5Sdann frazier static int efi_read_time(struct device *dev, struct rtc_time *tm)
1585e3fd9e5Sdann frazier {
1595e3fd9e5Sdann frazier 	efi_status_t status;
1605e3fd9e5Sdann frazier 	efi_time_t eft;
1615e3fd9e5Sdann frazier 	efi_time_cap_t cap;
1625e3fd9e5Sdann frazier 
1635e3fd9e5Sdann frazier 	status = efi.get_time(&eft, &cap);
1645e3fd9e5Sdann frazier 
1655e3fd9e5Sdann frazier 	if (status != EFI_SUCCESS) {
1665e3fd9e5Sdann frazier 		/* should never happen */
167668a2abfSArd Biesheuvel 		dev_err_once(dev, "can't read time\n");
1685e3fd9e5Sdann frazier 		return -EINVAL;
1695e3fd9e5Sdann frazier 	}
1705e3fd9e5Sdann frazier 
1716e85bab6SJan Beulich 	if (!convert_from_efi_time(&eft, tm))
1726e85bab6SJan Beulich 		return -EIO;
1735e3fd9e5Sdann frazier 
17422652ba7SAlexandre Belloni 	return 0;
1755e3fd9e5Sdann frazier }
1765e3fd9e5Sdann frazier 
efi_set_time(struct device * dev,struct rtc_time * tm)1775e3fd9e5Sdann frazier static int efi_set_time(struct device *dev, struct rtc_time *tm)
1785e3fd9e5Sdann frazier {
1795e3fd9e5Sdann frazier 	efi_status_t status;
1805e3fd9e5Sdann frazier 	efi_time_t eft;
1815e3fd9e5Sdann frazier 
1825e3fd9e5Sdann frazier 	convert_to_efi_time(tm, &eft);
1835e3fd9e5Sdann frazier 
1845e3fd9e5Sdann frazier 	status = efi.set_time(&eft);
1855e3fd9e5Sdann frazier 
1865e3fd9e5Sdann frazier 	return status == EFI_SUCCESS ? 0 : -EINVAL;
1875e3fd9e5Sdann frazier }
1885e3fd9e5Sdann frazier 
efi_procfs(struct device * dev,struct seq_file * seq)189501385f2SGeliang Tang static int efi_procfs(struct device *dev, struct seq_file *seq)
190501385f2SGeliang Tang {
191501385f2SGeliang Tang 	efi_time_t        eft, alm;
192501385f2SGeliang Tang 	efi_time_cap_t    cap;
193501385f2SGeliang Tang 	efi_bool_t        enabled, pending;
194101ca8d0SShanker Donthineni 	struct rtc_device *rtc = dev_get_drvdata(dev);
195501385f2SGeliang Tang 
196501385f2SGeliang Tang 	memset(&eft, 0, sizeof(eft));
197501385f2SGeliang Tang 	memset(&alm, 0, sizeof(alm));
198501385f2SGeliang Tang 	memset(&cap, 0, sizeof(cap));
199501385f2SGeliang Tang 
200501385f2SGeliang Tang 	efi.get_time(&eft, &cap);
201501385f2SGeliang Tang 	efi.get_wakeup_time(&enabled, &pending, &alm);
202501385f2SGeliang Tang 
203501385f2SGeliang Tang 	seq_printf(seq,
204501385f2SGeliang Tang 		   "Time\t\t: %u:%u:%u.%09u\n"
205501385f2SGeliang Tang 		   "Date\t\t: %u-%u-%u\n"
206501385f2SGeliang Tang 		   "Daylight\t: %u\n",
207501385f2SGeliang Tang 		   eft.hour, eft.minute, eft.second, eft.nanosecond,
208501385f2SGeliang Tang 		   eft.year, eft.month, eft.day,
209501385f2SGeliang Tang 		   eft.daylight);
210501385f2SGeliang Tang 
211501385f2SGeliang Tang 	if (eft.timezone == EFI_UNSPECIFIED_TIMEZONE)
212501385f2SGeliang Tang 		seq_puts(seq, "Timezone\t: unspecified\n");
213501385f2SGeliang Tang 	else
214501385f2SGeliang Tang 		/* XXX fixme: convert to string? */
215501385f2SGeliang Tang 		seq_printf(seq, "Timezone\t: %u\n", eft.timezone);
216501385f2SGeliang Tang 
217101ca8d0SShanker Donthineni 	if (test_bit(RTC_FEATURE_ALARM, rtc->features)) {
218501385f2SGeliang Tang 		seq_printf(seq,
219501385f2SGeliang Tang 			   "Alarm Time\t: %u:%u:%u.%09u\n"
220501385f2SGeliang Tang 			   "Alarm Date\t: %u-%u-%u\n"
221501385f2SGeliang Tang 			   "Alarm Daylight\t: %u\n"
222501385f2SGeliang Tang 			   "Enabled\t\t: %s\n"
223501385f2SGeliang Tang 			   "Pending\t\t: %s\n",
224501385f2SGeliang Tang 			   alm.hour, alm.minute, alm.second, alm.nanosecond,
225501385f2SGeliang Tang 			   alm.year, alm.month, alm.day,
226501385f2SGeliang Tang 			   alm.daylight,
227501385f2SGeliang Tang 			   enabled == 1 ? "yes" : "no",
228501385f2SGeliang Tang 			   pending == 1 ? "yes" : "no");
229501385f2SGeliang Tang 
230*f5f4c982SMaxim Korotkov 		if (alm.timezone == EFI_UNSPECIFIED_TIMEZONE)
231501385f2SGeliang Tang 			seq_puts(seq, "Timezone\t: unspecified\n");
232501385f2SGeliang Tang 		else
233501385f2SGeliang Tang 			/* XXX fixme: convert to string? */
234501385f2SGeliang Tang 			seq_printf(seq, "Timezone\t: %u\n", alm.timezone);
235101ca8d0SShanker Donthineni 	}
236501385f2SGeliang Tang 
237501385f2SGeliang Tang 	/*
238501385f2SGeliang Tang 	 * now prints the capabilities
239501385f2SGeliang Tang 	 */
240501385f2SGeliang Tang 	seq_printf(seq,
241501385f2SGeliang Tang 		   "Resolution\t: %u\n"
242501385f2SGeliang Tang 		   "Accuracy\t: %u\n"
243501385f2SGeliang Tang 		   "SetstoZero\t: %u\n",
244501385f2SGeliang Tang 		   cap.resolution, cap.accuracy, cap.sets_to_zero);
245501385f2SGeliang Tang 
246501385f2SGeliang Tang 	return 0;
247501385f2SGeliang Tang }
248501385f2SGeliang Tang 
2495e3fd9e5Sdann frazier static const struct rtc_class_ops efi_rtc_ops = {
2505e3fd9e5Sdann frazier 	.read_time	= efi_read_time,
2515e3fd9e5Sdann frazier 	.set_time	= efi_set_time,
2525e3fd9e5Sdann frazier 	.read_alarm	= efi_read_alarm,
2535e3fd9e5Sdann frazier 	.set_alarm	= efi_set_alarm,
254501385f2SGeliang Tang 	.proc		= efi_procfs,
2555e3fd9e5Sdann frazier };
2565e3fd9e5Sdann frazier 
efi_rtc_probe(struct platform_device * dev)2575e3fd9e5Sdann frazier static int __init efi_rtc_probe(struct platform_device *dev)
2585e3fd9e5Sdann frazier {
2595e3fd9e5Sdann frazier 	struct rtc_device *rtc;
2607368c69cSAlexander Graf 	efi_time_t eft;
2617368c69cSAlexander Graf 	efi_time_cap_t cap;
2627368c69cSAlexander Graf 
2637368c69cSAlexander Graf 	/* First check if the RTC is usable */
2647368c69cSAlexander Graf 	if (efi.get_time(&eft, &cap) != EFI_SUCCESS)
2657368c69cSAlexander Graf 		return -ENODEV;
2665e3fd9e5Sdann frazier 
2678aa74363SAlexandre Belloni 	rtc = devm_rtc_allocate_device(&dev->dev);
2685e3fd9e5Sdann frazier 	if (IS_ERR(rtc))
2695e3fd9e5Sdann frazier 		return PTR_ERR(rtc);
2705e3fd9e5Sdann frazier 
2715e3fd9e5Sdann frazier 	platform_set_drvdata(dev, rtc);
2725e3fd9e5Sdann frazier 
2738aa74363SAlexandre Belloni 	rtc->ops = &efi_rtc_ops;
2741350b94cSAlexandre Belloni 	clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, rtc->features);
275101ca8d0SShanker Donthineni 	if (efi_rt_services_supported(EFI_RT_SUPPORTED_WAKEUP_SERVICES))
2761350b94cSAlexandre Belloni 		set_bit(RTC_FEATURE_ALARM_WAKEUP_ONLY, rtc->features);
277101ca8d0SShanker Donthineni 	else
278101ca8d0SShanker Donthineni 		clear_bit(RTC_FEATURE_ALARM, rtc->features);
2798aa74363SAlexandre Belloni 
280eec79501SRiwen Lu 	device_init_wakeup(&dev->dev, true);
281eec79501SRiwen Lu 
2828aa74363SAlexandre Belloni 	return devm_rtc_register_device(rtc);
2835e3fd9e5Sdann frazier }
2845e3fd9e5Sdann frazier 
2855e3fd9e5Sdann frazier static struct platform_driver efi_rtc_driver = {
2865e3fd9e5Sdann frazier 	.driver = {
2875e3fd9e5Sdann frazier 		.name = "rtc-efi",
2885e3fd9e5Sdann frazier 	},
2895e3fd9e5Sdann frazier };
2905e3fd9e5Sdann frazier 
29152c6ecbcSJingoo Han module_platform_driver_probe(efi_rtc_driver, efi_rtc_probe);
2925e3fd9e5Sdann frazier 
29337563e5eSdann frazier MODULE_AUTHOR("dann frazier <dannf@dannf.org>");
2945e3fd9e5Sdann frazier MODULE_LICENSE("GPL");
2955e3fd9e5Sdann frazier MODULE_DESCRIPTION("EFI RTC driver");
2963f71f6daSArd Biesheuvel MODULE_ALIAS("platform:rtc-efi");
297