18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * rtc-efi: RTC Class Driver for EFI-based systems 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2009 Hewlett-Packard Development Company, L.P. 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Author: dann frazier <dannf@dannf.org> 88c2ecf20Sopenharmony_ci * Based on efirtc.c by Stephane Eranian 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/kernel.h> 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include <linux/stringify.h> 168c2ecf20Sopenharmony_ci#include <linux/time.h> 178c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 188c2ecf20Sopenharmony_ci#include <linux/rtc.h> 198c2ecf20Sopenharmony_ci#include <linux/efi.h> 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#define EFI_ISDST (EFI_TIME_ADJUST_DAYLIGHT|EFI_TIME_IN_DAYLIGHT) 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci/* 248c2ecf20Sopenharmony_ci * returns day of the year [0-365] 258c2ecf20Sopenharmony_ci */ 268c2ecf20Sopenharmony_cistatic inline int 278c2ecf20Sopenharmony_cicompute_yday(efi_time_t *eft) 288c2ecf20Sopenharmony_ci{ 298c2ecf20Sopenharmony_ci /* efi_time_t.month is in the [1-12] so, we need -1 */ 308c2ecf20Sopenharmony_ci return rtc_year_days(eft->day, eft->month - 1, eft->year); 318c2ecf20Sopenharmony_ci} 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci/* 348c2ecf20Sopenharmony_ci * returns day of the week [0-6] 0=Sunday 358c2ecf20Sopenharmony_ci */ 368c2ecf20Sopenharmony_cistatic int 378c2ecf20Sopenharmony_cicompute_wday(efi_time_t *eft, int yday) 388c2ecf20Sopenharmony_ci{ 398c2ecf20Sopenharmony_ci int ndays = eft->year * (365 % 7) 408c2ecf20Sopenharmony_ci + (eft->year - 1) / 4 418c2ecf20Sopenharmony_ci - (eft->year - 1) / 100 428c2ecf20Sopenharmony_ci + (eft->year - 1) / 400 438c2ecf20Sopenharmony_ci + yday; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci /* 468c2ecf20Sopenharmony_ci * 1/1/0000 may or may not have been a Sunday (if it ever existed at 478c2ecf20Sopenharmony_ci * all) but assuming it was makes this calculation work correctly. 488c2ecf20Sopenharmony_ci */ 498c2ecf20Sopenharmony_ci return ndays % 7; 508c2ecf20Sopenharmony_ci} 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic void 538c2ecf20Sopenharmony_ciconvert_to_efi_time(struct rtc_time *wtime, efi_time_t *eft) 548c2ecf20Sopenharmony_ci{ 558c2ecf20Sopenharmony_ci eft->year = wtime->tm_year + 1900; 568c2ecf20Sopenharmony_ci eft->month = wtime->tm_mon + 1; 578c2ecf20Sopenharmony_ci eft->day = wtime->tm_mday; 588c2ecf20Sopenharmony_ci eft->hour = wtime->tm_hour; 598c2ecf20Sopenharmony_ci eft->minute = wtime->tm_min; 608c2ecf20Sopenharmony_ci eft->second = wtime->tm_sec; 618c2ecf20Sopenharmony_ci eft->nanosecond = 0; 628c2ecf20Sopenharmony_ci eft->daylight = wtime->tm_isdst ? EFI_ISDST : 0; 638c2ecf20Sopenharmony_ci eft->timezone = EFI_UNSPECIFIED_TIMEZONE; 648c2ecf20Sopenharmony_ci} 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_cistatic bool 678c2ecf20Sopenharmony_ciconvert_from_efi_time(efi_time_t *eft, struct rtc_time *wtime) 688c2ecf20Sopenharmony_ci{ 698c2ecf20Sopenharmony_ci memset(wtime, 0, sizeof(*wtime)); 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci if (eft->second >= 60) 728c2ecf20Sopenharmony_ci return false; 738c2ecf20Sopenharmony_ci wtime->tm_sec = eft->second; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci if (eft->minute >= 60) 768c2ecf20Sopenharmony_ci return false; 778c2ecf20Sopenharmony_ci wtime->tm_min = eft->minute; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci if (eft->hour >= 24) 808c2ecf20Sopenharmony_ci return false; 818c2ecf20Sopenharmony_ci wtime->tm_hour = eft->hour; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci if (!eft->day || eft->day > 31) 848c2ecf20Sopenharmony_ci return false; 858c2ecf20Sopenharmony_ci wtime->tm_mday = eft->day; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci if (!eft->month || eft->month > 12) 888c2ecf20Sopenharmony_ci return false; 898c2ecf20Sopenharmony_ci wtime->tm_mon = eft->month - 1; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci if (eft->year < 1900 || eft->year > 9999) 928c2ecf20Sopenharmony_ci return false; 938c2ecf20Sopenharmony_ci wtime->tm_year = eft->year - 1900; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci /* day in the year [1-365]*/ 968c2ecf20Sopenharmony_ci wtime->tm_yday = compute_yday(eft); 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci /* day of the week [0-6], Sunday=0 */ 998c2ecf20Sopenharmony_ci wtime->tm_wday = compute_wday(eft, wtime->tm_yday); 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci switch (eft->daylight & EFI_ISDST) { 1028c2ecf20Sopenharmony_ci case EFI_ISDST: 1038c2ecf20Sopenharmony_ci wtime->tm_isdst = 1; 1048c2ecf20Sopenharmony_ci break; 1058c2ecf20Sopenharmony_ci case EFI_TIME_ADJUST_DAYLIGHT: 1068c2ecf20Sopenharmony_ci wtime->tm_isdst = 0; 1078c2ecf20Sopenharmony_ci break; 1088c2ecf20Sopenharmony_ci default: 1098c2ecf20Sopenharmony_ci wtime->tm_isdst = -1; 1108c2ecf20Sopenharmony_ci } 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci return true; 1138c2ecf20Sopenharmony_ci} 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_cistatic int efi_read_alarm(struct device *dev, struct rtc_wkalrm *wkalrm) 1168c2ecf20Sopenharmony_ci{ 1178c2ecf20Sopenharmony_ci efi_time_t eft; 1188c2ecf20Sopenharmony_ci efi_status_t status; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci /* 1218c2ecf20Sopenharmony_ci * As of EFI v1.10, this call always returns an unsupported status 1228c2ecf20Sopenharmony_ci */ 1238c2ecf20Sopenharmony_ci status = efi.get_wakeup_time((efi_bool_t *)&wkalrm->enabled, 1248c2ecf20Sopenharmony_ci (efi_bool_t *)&wkalrm->pending, &eft); 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci if (status != EFI_SUCCESS) 1278c2ecf20Sopenharmony_ci return -EINVAL; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci if (!convert_from_efi_time(&eft, &wkalrm->time)) 1308c2ecf20Sopenharmony_ci return -EIO; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci return rtc_valid_tm(&wkalrm->time); 1338c2ecf20Sopenharmony_ci} 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_cistatic int efi_set_alarm(struct device *dev, struct rtc_wkalrm *wkalrm) 1368c2ecf20Sopenharmony_ci{ 1378c2ecf20Sopenharmony_ci efi_time_t eft; 1388c2ecf20Sopenharmony_ci efi_status_t status; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci convert_to_efi_time(&wkalrm->time, &eft); 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci /* 1438c2ecf20Sopenharmony_ci * XXX Fixme: 1448c2ecf20Sopenharmony_ci * As of EFI 0.92 with the firmware I have on my 1458c2ecf20Sopenharmony_ci * machine this call does not seem to work quite 1468c2ecf20Sopenharmony_ci * right 1478c2ecf20Sopenharmony_ci * 1488c2ecf20Sopenharmony_ci * As of v1.10, this call always returns an unsupported status 1498c2ecf20Sopenharmony_ci */ 1508c2ecf20Sopenharmony_ci status = efi.set_wakeup_time((efi_bool_t)wkalrm->enabled, &eft); 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci dev_warn(dev, "write status is %d\n", (int)status); 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci return status == EFI_SUCCESS ? 0 : -EINVAL; 1558c2ecf20Sopenharmony_ci} 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_cistatic int efi_read_time(struct device *dev, struct rtc_time *tm) 1588c2ecf20Sopenharmony_ci{ 1598c2ecf20Sopenharmony_ci efi_status_t status; 1608c2ecf20Sopenharmony_ci efi_time_t eft; 1618c2ecf20Sopenharmony_ci efi_time_cap_t cap; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci status = efi.get_time(&eft, &cap); 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci if (status != EFI_SUCCESS) { 1668c2ecf20Sopenharmony_ci /* should never happen */ 1678c2ecf20Sopenharmony_ci dev_err(dev, "can't read time\n"); 1688c2ecf20Sopenharmony_ci return -EINVAL; 1698c2ecf20Sopenharmony_ci } 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci if (!convert_from_efi_time(&eft, tm)) 1728c2ecf20Sopenharmony_ci return -EIO; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci return 0; 1758c2ecf20Sopenharmony_ci} 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_cistatic int efi_set_time(struct device *dev, struct rtc_time *tm) 1788c2ecf20Sopenharmony_ci{ 1798c2ecf20Sopenharmony_ci efi_status_t status; 1808c2ecf20Sopenharmony_ci efi_time_t eft; 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci convert_to_efi_time(tm, &eft); 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci status = efi.set_time(&eft); 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci return status == EFI_SUCCESS ? 0 : -EINVAL; 1878c2ecf20Sopenharmony_ci} 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_cistatic int efi_procfs(struct device *dev, struct seq_file *seq) 1908c2ecf20Sopenharmony_ci{ 1918c2ecf20Sopenharmony_ci efi_time_t eft, alm; 1928c2ecf20Sopenharmony_ci efi_time_cap_t cap; 1938c2ecf20Sopenharmony_ci efi_bool_t enabled, pending; 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci memset(&eft, 0, sizeof(eft)); 1968c2ecf20Sopenharmony_ci memset(&alm, 0, sizeof(alm)); 1978c2ecf20Sopenharmony_ci memset(&cap, 0, sizeof(cap)); 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci efi.get_time(&eft, &cap); 2008c2ecf20Sopenharmony_ci efi.get_wakeup_time(&enabled, &pending, &alm); 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci seq_printf(seq, 2038c2ecf20Sopenharmony_ci "Time\t\t: %u:%u:%u.%09u\n" 2048c2ecf20Sopenharmony_ci "Date\t\t: %u-%u-%u\n" 2058c2ecf20Sopenharmony_ci "Daylight\t: %u\n", 2068c2ecf20Sopenharmony_ci eft.hour, eft.minute, eft.second, eft.nanosecond, 2078c2ecf20Sopenharmony_ci eft.year, eft.month, eft.day, 2088c2ecf20Sopenharmony_ci eft.daylight); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci if (eft.timezone == EFI_UNSPECIFIED_TIMEZONE) 2118c2ecf20Sopenharmony_ci seq_puts(seq, "Timezone\t: unspecified\n"); 2128c2ecf20Sopenharmony_ci else 2138c2ecf20Sopenharmony_ci /* XXX fixme: convert to string? */ 2148c2ecf20Sopenharmony_ci seq_printf(seq, "Timezone\t: %u\n", eft.timezone); 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci seq_printf(seq, 2178c2ecf20Sopenharmony_ci "Alarm Time\t: %u:%u:%u.%09u\n" 2188c2ecf20Sopenharmony_ci "Alarm Date\t: %u-%u-%u\n" 2198c2ecf20Sopenharmony_ci "Alarm Daylight\t: %u\n" 2208c2ecf20Sopenharmony_ci "Enabled\t\t: %s\n" 2218c2ecf20Sopenharmony_ci "Pending\t\t: %s\n", 2228c2ecf20Sopenharmony_ci alm.hour, alm.minute, alm.second, alm.nanosecond, 2238c2ecf20Sopenharmony_ci alm.year, alm.month, alm.day, 2248c2ecf20Sopenharmony_ci alm.daylight, 2258c2ecf20Sopenharmony_ci enabled == 1 ? "yes" : "no", 2268c2ecf20Sopenharmony_ci pending == 1 ? "yes" : "no"); 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci if (eft.timezone == EFI_UNSPECIFIED_TIMEZONE) 2298c2ecf20Sopenharmony_ci seq_puts(seq, "Timezone\t: unspecified\n"); 2308c2ecf20Sopenharmony_ci else 2318c2ecf20Sopenharmony_ci /* XXX fixme: convert to string? */ 2328c2ecf20Sopenharmony_ci seq_printf(seq, "Timezone\t: %u\n", alm.timezone); 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci /* 2358c2ecf20Sopenharmony_ci * now prints the capabilities 2368c2ecf20Sopenharmony_ci */ 2378c2ecf20Sopenharmony_ci seq_printf(seq, 2388c2ecf20Sopenharmony_ci "Resolution\t: %u\n" 2398c2ecf20Sopenharmony_ci "Accuracy\t: %u\n" 2408c2ecf20Sopenharmony_ci "SetstoZero\t: %u\n", 2418c2ecf20Sopenharmony_ci cap.resolution, cap.accuracy, cap.sets_to_zero); 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci return 0; 2448c2ecf20Sopenharmony_ci} 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_cistatic const struct rtc_class_ops efi_rtc_ops = { 2478c2ecf20Sopenharmony_ci .read_time = efi_read_time, 2488c2ecf20Sopenharmony_ci .set_time = efi_set_time, 2498c2ecf20Sopenharmony_ci .read_alarm = efi_read_alarm, 2508c2ecf20Sopenharmony_ci .set_alarm = efi_set_alarm, 2518c2ecf20Sopenharmony_ci .proc = efi_procfs, 2528c2ecf20Sopenharmony_ci}; 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_cistatic int __init efi_rtc_probe(struct platform_device *dev) 2558c2ecf20Sopenharmony_ci{ 2568c2ecf20Sopenharmony_ci struct rtc_device *rtc; 2578c2ecf20Sopenharmony_ci efi_time_t eft; 2588c2ecf20Sopenharmony_ci efi_time_cap_t cap; 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci /* First check if the RTC is usable */ 2618c2ecf20Sopenharmony_ci if (efi.get_time(&eft, &cap) != EFI_SUCCESS) 2628c2ecf20Sopenharmony_ci return -ENODEV; 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci rtc = devm_rtc_device_register(&dev->dev, "rtc-efi", &efi_rtc_ops, 2658c2ecf20Sopenharmony_ci THIS_MODULE); 2668c2ecf20Sopenharmony_ci if (IS_ERR(rtc)) 2678c2ecf20Sopenharmony_ci return PTR_ERR(rtc); 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci rtc->uie_unsupported = 1; 2708c2ecf20Sopenharmony_ci platform_set_drvdata(dev, rtc); 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci return 0; 2738c2ecf20Sopenharmony_ci} 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_cistatic struct platform_driver efi_rtc_driver = { 2768c2ecf20Sopenharmony_ci .driver = { 2778c2ecf20Sopenharmony_ci .name = "rtc-efi", 2788c2ecf20Sopenharmony_ci }, 2798c2ecf20Sopenharmony_ci}; 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_cimodule_platform_driver_probe(efi_rtc_driver, efi_rtc_probe); 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ciMODULE_AUTHOR("dann frazier <dannf@dannf.org>"); 2848c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 2858c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("EFI RTC driver"); 2868c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:rtc-efi"); 287