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