297 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * rtc-efi: RTC Class Driver for EFI-based systems4 *5 * Copyright (C) 2009 Hewlett-Packard Development Company, L.P.6 *7 * Author: dann frazier <dannf@dannf.org>8 * Based on efirtc.c by Stephane Eranian9 */10 11#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt12 13#include <linux/kernel.h>14#include <linux/module.h>15#include <linux/stringify.h>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)22 23/*24 * returns day of the year [0-365]25 */26static inline int27compute_yday(efi_time_t *eft)28{29 /* efi_time_t.month is in the [1-12] so, we need -1 */30 return rtc_year_days(eft->day, eft->month - 1, eft->year);31}32 33/*34 * returns day of the week [0-6] 0=Sunday35 */36static int37compute_wday(efi_time_t *eft, int yday)38{39 int ndays = eft->year * (365 % 7)40 + (eft->year - 1) / 441 - (eft->year - 1) / 10042 + (eft->year - 1) / 40043 + yday;44 45 /*46 * 1/1/0000 may or may not have been a Sunday (if it ever existed at47 * all) but assuming it was makes this calculation work correctly.48 */49 return ndays % 7;50}51 52static void53convert_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;60 eft->second = wtime->tm_sec;61 eft->nanosecond = 0;62 eft->daylight = wtime->tm_isdst ? EFI_ISDST : 0;63 eft->timezone = EFI_UNSPECIFIED_TIMEZONE;64}65 66static bool67convert_from_efi_time(efi_time_t *eft, struct rtc_time *wtime)68{69 memset(wtime, 0, sizeof(*wtime));70 71 if (eft->second >= 60)72 return false;73 wtime->tm_sec = eft->second;74 75 if (eft->minute >= 60)76 return false;77 wtime->tm_min = eft->minute;78 79 if (eft->hour >= 24)80 return false;81 wtime->tm_hour = eft->hour;82 83 if (!eft->day || eft->day > 31)84 return false;85 wtime->tm_mday = eft->day;86 87 if (!eft->month || eft->month > 12)88 return false;89 wtime->tm_mon = eft->month - 1;90 91 if (eft->year < 1900 || eft->year > 9999)92 return false;93 wtime->tm_year = eft->year - 1900;94 95 /* day in the year [1-365]*/96 wtime->tm_yday = compute_yday(eft);97 98 /* day of the week [0-6], Sunday=0 */99 wtime->tm_wday = compute_wday(eft, wtime->tm_yday);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 }111 112 return true;113}114 115static 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 status122 */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 129 if (!convert_from_efi_time(&eft, &wkalrm->time))130 return -EIO;131 132 return rtc_valid_tm(&wkalrm->time);133}134 135static 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 my145 * machine this call does not seem to work quite146 * right147 *148 * As of v1.10, this call always returns an unsupported status149 */150 status = efi.set_wakeup_time((efi_bool_t)wkalrm->enabled, &eft);151 152 dev_warn(dev, "write status is %d\n", (int)status);153 154 return status == EFI_SUCCESS ? 0 : -EINVAL;155}156 157static 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 */167 dev_err_once(dev, "can't read time\n");168 return -EINVAL;169 }170 171 if (!convert_from_efi_time(&eft, tm))172 return -EIO;173 174 return 0;175}176 177static 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 189static int efi_procfs(struct device *dev, struct seq_file *seq)190{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);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 else214 /* XXX fixme: convert to string? */215 seq_printf(seq, "Timezone\t: %u\n", eft.timezone);216 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 230 if (alm.timezone == EFI_UNSPECIFIED_TIMEZONE)231 seq_puts(seq, "Timezone\t: unspecified\n");232 else233 /* XXX fixme: convert to string? */234 seq_printf(seq, "Timezone\t: %u\n", alm.timezone);235 }236 237 /*238 * now prints the capabilities239 */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 249static const struct rtc_class_ops efi_rtc_ops = {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,255};256 257static int __init efi_rtc_probe(struct platform_device *dev)258{259 struct rtc_device *rtc;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;266 267 rtc = devm_rtc_allocate_device(&dev->dev);268 if (IS_ERR(rtc))269 return PTR_ERR(rtc);270 271 platform_set_drvdata(dev, rtc);272 273 rtc->ops = &efi_rtc_ops;274 clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, rtc->features);275 if (efi_rt_services_supported(EFI_RT_SUPPORTED_WAKEUP_SERVICES))276 set_bit(RTC_FEATURE_ALARM_WAKEUP_ONLY, rtc->features);277 else278 clear_bit(RTC_FEATURE_ALARM, rtc->features);279 280 device_init_wakeup(&dev->dev, true);281 282 return devm_rtc_register_device(rtc);283}284 285static struct platform_driver efi_rtc_driver = {286 .driver = {287 .name = "rtc-efi",288 },289};290 291module_platform_driver_probe(efi_rtc_driver, efi_rtc_probe);292 293MODULE_AUTHOR("dann frazier <dannf@dannf.org>");294MODULE_LICENSE("GPL");295MODULE_DESCRIPTION("EFI RTC driver");296MODULE_ALIAS("platform:rtc-efi");297