| 1 | |
| 2 | |
| 3 | * rtc-efi: RTC Class Driver for EFI-based systems
|
| 4 | *
|
| 5 | * Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
|
| 6 | *
|
| 7 | * Author: dann frazier <[email protected]>
|
| 8 | * Based on efirtc.c by Stephane Eranian
|
| 9 |
|
| 10 |
|
| 11 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
| 12 |
|
| 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 |
|
| 26 | static inline int
|
| 27 | compute_yday(efi_time_t *eft)
|
| 28 | {
|
| 29 |
|
| 30 | return rtc_year_days(eft->day, eft->month - 1, eft->year);
|
| 31 | }
|
| 32 |
|
| 33 | |
| 34 | * returns day of the week [0-6] 0=Sunday
|
| 35 |
|
| 36 | static int
|
| 37 | compute_wday(efi_time_t *eft, int yday)
|
| 38 | {
|
| 39 | int ndays = eft->year * (365 % 7)
|
| 40 | + (eft->year - 1) / 4
|
| 41 | - (eft->year - 1) / 100
|
| 42 | + (eft->year - 1) / 400
|
| 43 | + yday;
|
| 44 |
|
| 45 | |
| 46 | * 1/1/0000 may or may not have been a Sunday (if it ever existed at
|
| 47 | * all) but assuming it was makes this calculation work correctly.
|
| 48 |
|
| 49 | return ndays % 7;
|
| 50 | }
|
| 51 |
|
| 52 | static void
|
| 53 | convert_to_efi_time(struct rtc_time *wtime, efi_time_t *eft)
|
| 54 | {
|
| 55 | memset(eft, 0, sizeof(*eft));
|
| 56 | eft->year = wtime->tm_year + 1900;
|
| 57 | eft->month = wtime->tm_mon + 1;
|
| 58 | eft->day = wtime->tm_mday;
|
| 59 | eft->hour = wtime->tm_hour;
|
| 60 | eft->minute = wtime->tm_min;
|
| 61 | eft->second = wtime->tm_sec;
|
| 62 | eft->nanosecond = 0;
|
| 63 | eft->daylight = wtime->tm_isdst ? EFI_ISDST : 0;
|
| 64 | eft->timezone = EFI_UNSPECIFIED_TIMEZONE;
|
| 65 | }
|
| 66 |
|
| 67 | static bool
|
| 68 | convert_from_efi_time(efi_time_t *eft, struct rtc_time *wtime)
|
| 69 | {
|
| 70 | memset(wtime, 0, sizeof(*wtime));
|
| 71 |
|
| 72 | if (eft->second >= 60)
|
| 73 | return false;
|
| 74 | wtime->tm_sec = eft->second;
|
| 75 |
|
| 76 | if (eft->minute >= 60)
|
| 77 | return false;
|
| 78 | wtime->tm_min = eft->minute;
|
| 79 |
|
| 80 | if (eft->hour >= 24)
|
| 81 | return false;
|
| 82 | wtime->tm_hour = eft->hour;
|
| 83 |
|
| 84 | if (!eft->day || eft->day > 31)
|
| 85 | return false;
|
| 86 | wtime->tm_mday = eft->day;
|
| 87 |
|
| 88 | if (!eft->month || eft->month > 12)
|
| 89 | return false;
|
| 90 | wtime->tm_mon = eft->month - 1;
|
| 91 |
|
| 92 | if (eft->year < 1900 || eft->year > 9999)
|
| 93 | return false;
|
| 94 | wtime->tm_year = eft->year - 1900;
|
| 95 |
|
| 96 |
|
| 97 | wtime->tm_yday = compute_yday(eft);
|
| 98 |
|
| 99 |
|
| 100 | wtime->tm_wday = compute_wday(eft, wtime->tm_yday);
|
| 101 |
|
| 102 | switch (eft->daylight & EFI_ISDST) {
|
| 103 | case EFI_ISDST:
|
| 104 | wtime->tm_isdst = 1;
|
| 105 | break;
|
| 106 | case EFI_TIME_ADJUST_DAYLIGHT:
|
| 107 | wtime->tm_isdst = 0;
|
| 108 | break;
|
| 109 | default:
|
| 110 | wtime->tm_isdst = -1;
|
| 111 | }
|
| 112 |
|
| 113 | return true;
|
| 114 | }
|
| 115 |
|
| 116 | static int efi_read_alarm(struct device *dev, struct rtc_wkalrm *wkalrm)
|
| 117 | {
|
| 118 | efi_time_t eft;
|
| 119 | efi_status_t status;
|
| 120 |
|
| 121 | memset(&eft, 0, sizeof(eft));
|
| 122 | status = efi.get_wakeup_time((efi_bool_t *)&wkalrm->enabled,
|
| 123 | (efi_bool_t *)&wkalrm->pending, &eft);
|
| 124 | if (status != EFI_SUCCESS)
|
| 125 | return -EINVAL;
|
| 126 |
|
| 127 | if (!convert_from_efi_time(&eft, &wkalrm->time))
|
| 128 | return -EIO;
|
| 129 |
|
| 130 | return rtc_valid_tm(&wkalrm->time);
|
| 131 | }
|
| 132 |
|
| 133 | static int efi_set_alarm(struct device *dev, struct rtc_wkalrm *wkalrm)
|
| 134 | {
|
| 135 | efi_time_t eft;
|
| 136 | efi_status_t status;
|
| 137 |
|
| 138 | convert_to_efi_time(&wkalrm->time, &eft);
|
| 139 |
|
| 140 | status = efi.set_wakeup_time((efi_bool_t)!!wkalrm->enabled, &eft);
|
| 141 |
|
| 142 | return status == EFI_SUCCESS ? 0 : -EINVAL;
|
| 143 | }
|
| 144 |
|
| 145 | static int efi_read_time(struct device *dev, struct rtc_time *tm)
|
| 146 | {
|
| 147 | efi_status_t status;
|
| 148 | efi_time_t eft;
|
| 149 | efi_time_cap_t cap;
|
| 150 |
|
| 151 | status = efi.get_time(&eft, &cap);
|
| 152 |
|
| 153 | if (status != EFI_SUCCESS) {
|
| 154 |
|
| 155 | dev_err_once(dev, "can't read time\n");
|
| 156 | return -EINVAL;
|
| 157 | }
|
| 158 |
|
| 159 | if (!convert_from_efi_time(&eft, tm))
|
| 160 | return -EIO;
|
| 161 |
|
| 162 | return 0;
|
| 163 | }
|
| 164 |
|
| 165 | static int efi_set_time(struct device *dev, struct rtc_time *tm)
|
| 166 | {
|
| 167 | efi_status_t status;
|
| 168 | efi_time_t eft;
|
| 169 |
|
| 170 | convert_to_efi_time(tm, &eft);
|
| 171 |
|
| 172 | status = efi.set_time(&eft);
|
| 173 |
|
| 174 | return status == EFI_SUCCESS ? 0 : -EINVAL;
|
| 175 | }
|
| 176 |
|
| 177 | static int efi_procfs(struct device *dev, struct seq_file *seq)
|
| 178 | {
|
| 179 | efi_time_t eft, alm;
|
| 180 | efi_time_cap_t cap;
|
| 181 | efi_bool_t enabled, pending;
|
| 182 | struct rtc_device *rtc = dev_get_drvdata(dev);
|
| 183 |
|
| 184 | memset(&eft, 0, sizeof(eft));
|
| 185 | memset(&alm, 0, sizeof(alm));
|
| 186 | memset(&cap, 0, sizeof(cap));
|
| 187 |
|
| 188 | efi.get_time(&eft, &cap);
|
| 189 | if (test_bit(RTC_FEATURE_ALARM, rtc->features) &&
|
| 190 | efi.get_wakeup_time(&enabled, &pending, &alm) != EFI_SUCCESS) {
|
| 191 | enabled = 0;
|
| 192 | pending = 0;
|
| 193 | }
|
| 194 |
|
| 195 | seq_printf(seq,
|
| 196 | "Time\t\t: %u:%u:%u.%09u\n"
|
| 197 | "Date\t\t: %u-%u-%u\n"
|
| 198 | "Daylight\t: %u\n",
|
| 199 | eft.hour, eft.minute, eft.second, eft.nanosecond,
|
| 200 | eft.year, eft.month, eft.day,
|
| 201 | eft.daylight);
|
| 202 |
|
| 203 | if (eft.timezone == EFI_UNSPECIFIED_TIMEZONE)
|
| 204 | seq_puts(seq, "Timezone\t: unspecified\n");
|
| 205 | else
|
| 206 |
|
| 207 | seq_printf(seq, "Timezone\t: %u\n", eft.timezone);
|
| 208 |
|
| 209 | if (test_bit(RTC_FEATURE_ALARM, rtc->features)) {
|
| 210 | seq_printf(seq,
|
| 211 | "Alarm Time\t: %u:%u:%u.%09u\n"
|
| 212 | "Alarm Date\t: %u-%u-%u\n"
|
| 213 | "Alarm Daylight\t: %u\n"
|
| 214 | "Enabled\t\t: %s\n"
|
| 215 | "Pending\t\t: %s\n",
|
| 216 | alm.hour, alm.minute, alm.second, alm.nanosecond,
|
| 217 | alm.year, alm.month, alm.day,
|
| 218 | alm.daylight,
|
| 219 | enabled == 1 ? "yes" : "no",
|
| 220 | pending == 1 ? "yes" : "no");
|
| 221 |
|
| 222 | if (alm.timezone == EFI_UNSPECIFIED_TIMEZONE)
|
| 223 | seq_puts(seq, "Alarm Timezone\t: unspecified\n");
|
| 224 | else
|
| 225 | seq_printf(seq, "Alarm Timezone\t: %d\n", alm.timezone);
|
| 226 | }
|
| 227 |
|
| 228 | |
| 229 | * now prints the capabilities
|
| 230 |
|
| 231 | seq_printf(seq,
|
| 232 | "Resolution\t: %u\n"
|
| 233 | "Accuracy\t: %u\n"
|
| 234 | "SetstoZero\t: %u\n",
|
| 235 | cap.resolution, cap.accuracy, cap.sets_to_zero);
|
| 236 |
|
| 237 | return 0;
|
| 238 | }
|
| 239 |
|
| 240 | static const struct rtc_class_ops efi_rtc_ops = {
|
| 241 | .read_time = efi_read_time,
|
| 242 | .set_time = efi_set_time,
|
| 243 | .read_alarm = efi_read_alarm,
|
| 244 | .set_alarm = efi_set_alarm,
|
| 245 | .proc = efi_procfs,
|
| 246 | };
|
| 247 |
|
| 248 | static int __init efi_rtc_probe(struct platform_device *dev)
|
| 249 | {
|
| 250 | struct rtc_device *rtc;
|
| 251 | efi_time_t eft;
|
| 252 | efi_time_cap_t cap;
|
| 253 | efi_bool_t enabled, pending;
|
| 254 |
|
| 255 |
|
| 256 | if (efi.get_time(&eft, &cap) != EFI_SUCCESS)
|
| 257 | return -ENODEV;
|
| 258 |
|
| 259 | rtc = devm_rtc_allocate_device(&dev->dev);
|
| 260 | if (IS_ERR(rtc))
|
| 261 | return PTR_ERR(rtc);
|
| 262 |
|
| 263 | platform_set_drvdata(dev, rtc);
|
| 264 |
|
| 265 | rtc->ops = &efi_rtc_ops;
|
| 266 | clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, rtc->features);
|
| 267 |
|
| 268 | |
| 269 | * The EFI_RT_SUPPORTED_WAKEUP_SERVICES bit defaults to enabled
|
| 270 | * and only gets cleared when the RT_PROP table explicitly says
|
| 271 | * wakeup is unsupported. Many platforms lack an RT_PROP table
|
| 272 | * even though they don't implement the wakeup runtime service,
|
| 273 | * so probe by actually calling GetWakeupTime() to avoid exposing
|
| 274 | * a broken alarm to userspace.
|
| 275 |
|
| 276 | if (efi_rt_services_supported(EFI_RT_SUPPORTED_WAKEUP_SERVICES) &&
|
| 277 | efi.get_wakeup_time(&enabled, &pending, &eft) == EFI_SUCCESS) {
|
| 278 | set_bit(RTC_FEATURE_ALARM, rtc->features);
|
| 279 | set_bit(RTC_FEATURE_ALARM_WAKEUP_ONLY, rtc->features);
|
| 280 | } else {
|
| 281 | clear_bit(RTC_FEATURE_ALARM, rtc->features);
|
| 282 | }
|
| 283 |
|
| 284 | device_init_wakeup(&dev->dev, true);
|
| 285 |
|
| 286 | return devm_rtc_register_device(rtc);
|
| 287 | }
|
| 288 |
|
| 289 | static struct platform_driver efi_rtc_driver = {
|
| 290 | .driver = {
|
| 291 | .name = "rtc-efi",
|
| 292 | },
|
| 293 | };
|
| 294 |
|
| 295 | module_platform_driver_probe(efi_rtc_driver, efi_rtc_probe);
|
| 296 |
|
| 297 | MODULE_AUTHOR("dann frazier <[email protected]>");
|
| 298 | MODULE_LICENSE("GPL");
|
| 299 | MODULE_DESCRIPTION("EFI RTC driver");
|
| 300 | MODULE_ALIAS("platform:rtc-efi");
|