brintos

brintos / linux-shallow public Read only

0
0
Text · 6.7 KiB · fa8bf82 Raw
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