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 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 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 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 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 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 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 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 */ 16734650f9eSJingoo Han dev_err(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 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 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; 194501385f2SGeliang Tang 195501385f2SGeliang Tang memset(&eft, 0, sizeof(eft)); 196501385f2SGeliang Tang memset(&alm, 0, sizeof(alm)); 197501385f2SGeliang Tang memset(&cap, 0, sizeof(cap)); 198501385f2SGeliang Tang 199501385f2SGeliang Tang efi.get_time(&eft, &cap); 200501385f2SGeliang Tang efi.get_wakeup_time(&enabled, &pending, &alm); 201501385f2SGeliang Tang 202501385f2SGeliang Tang seq_printf(seq, 203501385f2SGeliang Tang "Time\t\t: %u:%u:%u.%09u\n" 204501385f2SGeliang Tang "Date\t\t: %u-%u-%u\n" 205501385f2SGeliang Tang "Daylight\t: %u\n", 206501385f2SGeliang Tang eft.hour, eft.minute, eft.second, eft.nanosecond, 207501385f2SGeliang Tang eft.year, eft.month, eft.day, 208501385f2SGeliang Tang eft.daylight); 209501385f2SGeliang Tang 210501385f2SGeliang Tang if (eft.timezone == EFI_UNSPECIFIED_TIMEZONE) 211501385f2SGeliang Tang seq_puts(seq, "Timezone\t: unspecified\n"); 212501385f2SGeliang Tang else 213501385f2SGeliang Tang /* XXX fixme: convert to string? */ 214501385f2SGeliang Tang seq_printf(seq, "Timezone\t: %u\n", eft.timezone); 215501385f2SGeliang Tang 216501385f2SGeliang Tang seq_printf(seq, 217501385f2SGeliang Tang "Alarm Time\t: %u:%u:%u.%09u\n" 218501385f2SGeliang Tang "Alarm Date\t: %u-%u-%u\n" 219501385f2SGeliang Tang "Alarm Daylight\t: %u\n" 220501385f2SGeliang Tang "Enabled\t\t: %s\n" 221501385f2SGeliang Tang "Pending\t\t: %s\n", 222501385f2SGeliang Tang alm.hour, alm.minute, alm.second, alm.nanosecond, 223501385f2SGeliang Tang alm.year, alm.month, alm.day, 224501385f2SGeliang Tang alm.daylight, 225501385f2SGeliang Tang enabled == 1 ? "yes" : "no", 226501385f2SGeliang Tang pending == 1 ? "yes" : "no"); 227501385f2SGeliang Tang 228501385f2SGeliang Tang if (eft.timezone == EFI_UNSPECIFIED_TIMEZONE) 229501385f2SGeliang Tang seq_puts(seq, "Timezone\t: unspecified\n"); 230501385f2SGeliang Tang else 231501385f2SGeliang Tang /* XXX fixme: convert to string? */ 232501385f2SGeliang Tang seq_printf(seq, "Timezone\t: %u\n", alm.timezone); 233501385f2SGeliang Tang 234501385f2SGeliang Tang /* 235501385f2SGeliang Tang * now prints the capabilities 236501385f2SGeliang Tang */ 237501385f2SGeliang Tang seq_printf(seq, 238501385f2SGeliang Tang "Resolution\t: %u\n" 239501385f2SGeliang Tang "Accuracy\t: %u\n" 240501385f2SGeliang Tang "SetstoZero\t: %u\n", 241501385f2SGeliang Tang cap.resolution, cap.accuracy, cap.sets_to_zero); 242501385f2SGeliang Tang 243501385f2SGeliang Tang return 0; 244501385f2SGeliang Tang } 245501385f2SGeliang Tang 2465e3fd9e5Sdann frazier static const struct rtc_class_ops efi_rtc_ops = { 2475e3fd9e5Sdann frazier .read_time = efi_read_time, 2485e3fd9e5Sdann frazier .set_time = efi_set_time, 2495e3fd9e5Sdann frazier .read_alarm = efi_read_alarm, 2505e3fd9e5Sdann frazier .set_alarm = efi_set_alarm, 251501385f2SGeliang Tang .proc = efi_procfs, 2525e3fd9e5Sdann frazier }; 2535e3fd9e5Sdann frazier 2545e3fd9e5Sdann frazier static int __init efi_rtc_probe(struct platform_device *dev) 2555e3fd9e5Sdann frazier { 2565e3fd9e5Sdann frazier struct rtc_device *rtc; 2577368c69cSAlexander Graf efi_time_t eft; 2587368c69cSAlexander Graf efi_time_cap_t cap; 2597368c69cSAlexander Graf 2607368c69cSAlexander Graf /* First check if the RTC is usable */ 2617368c69cSAlexander Graf if (efi.get_time(&eft, &cap) != EFI_SUCCESS) 2627368c69cSAlexander Graf return -ENODEV; 2635e3fd9e5Sdann frazier 264*8aa74363SAlexandre Belloni rtc = devm_rtc_allocate_device(&dev->dev); 2655e3fd9e5Sdann frazier if (IS_ERR(rtc)) 2665e3fd9e5Sdann frazier return PTR_ERR(rtc); 2675e3fd9e5Sdann frazier 2685e3fd9e5Sdann frazier platform_set_drvdata(dev, rtc); 2695e3fd9e5Sdann frazier 270*8aa74363SAlexandre Belloni rtc->ops = &efi_rtc_ops; 271*8aa74363SAlexandre Belloni rtc->uie_unsupported = 1; 272*8aa74363SAlexandre Belloni 273*8aa74363SAlexandre Belloni return devm_rtc_register_device(rtc); 2745e3fd9e5Sdann frazier } 2755e3fd9e5Sdann frazier 2765e3fd9e5Sdann frazier static struct platform_driver efi_rtc_driver = { 2775e3fd9e5Sdann frazier .driver = { 2785e3fd9e5Sdann frazier .name = "rtc-efi", 2795e3fd9e5Sdann frazier }, 2805e3fd9e5Sdann frazier }; 2815e3fd9e5Sdann frazier 28252c6ecbcSJingoo Han module_platform_driver_probe(efi_rtc_driver, efi_rtc_probe); 2835e3fd9e5Sdann frazier 28437563e5eSdann frazier MODULE_AUTHOR("dann frazier <dannf@dannf.org>"); 2855e3fd9e5Sdann frazier MODULE_LICENSE("GPL"); 2865e3fd9e5Sdann frazier MODULE_DESCRIPTION("EFI RTC driver"); 2873f71f6daSArd Biesheuvel MODULE_ALIAS("platform:rtc-efi"); 288