From 6984a7ce1535422891ce4de57070cc55a486d352 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Thu, 31 Jan 2019 11:05:13 -0500 Subject: RTC: Add support RTC alarms on hosted targets Only AGPTeck Rocker is enabled for now, and it doesn't work properly: * No generic way to determine wakeup reason under Linux * No generic way to be asynchronously notified if the alarm is triggered when we're already awake * Shutting down may clobber RTC wakeup (driver/etc dependent) And finally: * AGPTek kernel's RTC driver has some 24h clock and some timezone-related issues. So, the infrastructure is arguably useful, but the only applicable hardware I have is pathologically brain-dead. Change-Id: Iac6a26a9b6e4efec5d0b3030b87f456eb23fc01d --- firmware/target/hosted/rtc.c | 154 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 146 insertions(+), 8 deletions(-) (limited to 'firmware/target/hosted/rtc.c') diff --git a/firmware/target/hosted/rtc.c b/firmware/target/hosted/rtc.c index 488531c77c..ced298a5c8 100644 --- a/firmware/target/hosted/rtc.c +++ b/firmware/target/hosted/rtc.c @@ -27,9 +27,14 @@ #include #include #include +#include #endif + +#include "config.h" + void rtc_init(void) { + tzset(); } int rtc_read_datetime(struct tm *tm) @@ -46,8 +51,6 @@ int rtc_write_datetime(const struct tm *tm) struct timeval tv; struct tm *tm_time; - int rtc = open("/dev/rtc0", O_WRONLY); - tv.tv_sec = mktime((struct tm *)tm); tv.tv_usec = 0; @@ -58,12 +61,147 @@ int rtc_write_datetime(const struct tm *tm) time_t now = time(NULL); tm_time = gmtime(&now); - ioctl(rtc, RTC_SET_TIME, (struct rtc_time *)tm_time); - close(rtc); - - return 0; + /* Try to write the HW RTC, if present. */ + int rtc = open("/dev/rtc0", O_WRONLY); + if (rtc > 0) { + ioctl(rtc, RTC_SET_TIME, (struct rtc_time *)tm_time); + close(rtc); + } #else - (void)tm; - return -1; + (void)(*tm); #endif + return 0; +} + +#if defined(HAVE_RTC_ALARM) && !defined(SIMULATOR) +void rtc_set_alarm(int h, int m) +{ + struct rtc_time tm; + long sec; + + int rtc = open("/dev/rtc0", O_WRONLY); + if (rtc < 0) + return; + + /* Get RTC time */ + ioctl(rtc, RTC_RD_TIME, &tm); + + /* Convert to seconds into the GMT day. Can be negative! */ + sec = h * 3600 + m * 60 + timezone; + h = sec / 3600; + sec -= h * 3600; + m = sec / 60; + + /* Handle negative or positive wraps */ + while (m < 0) { + m += 60; + h--; + } + while (m > 59) { + m -= 60; + h++; + } + while (h < 0) { + h += 24; + tm.tm_mday--; + } + while (h > 23) { + h -= 24; + tm.tm_mday++; + } + + /* Update the struct */ + tm.tm_sec = 0; + tm.tm_hour = h; + tm.tm_min = m; + + ioctl(rtc, RTC_ALM_SET, &tm); + close(rtc); +} + +void rtc_get_alarm(int *h, int *m) +{ + struct rtc_time tm; + long sec; + + int rtc = open("/dev/rtc0", O_WRONLY); + if (rtc < 0) + return; + + ioctl(rtc, RTC_ALM_READ, &tm); + close(rtc); + + /* Convert RTC from UTC to local time zone.. */ + sec = (tm.tm_min * 60) + (tm.tm_hour * 3600) - timezone; + + /* Handle wrapping and negative offsets */ + *h = (sec / 3600); + sec -= *h * 3600; + *m = sec / 60; + + while (*m < 0) { + *m = *m + 60; + *h = *h - 1; + } + while (*m > 59) { + *m = *m - 60; + *h = *h + 1; + } + while (*h < 0) { + *h = *h + 24; + } + while (*h > 23) { + *h = *h - 24; + } +} + +void rtc_enable_alarm(bool enable) +{ + int rtc = open("/dev/rtc0", O_WRONLY); + if (rtc < 0) + return; + + ioctl(rtc, enable ? RTC_AIE_ON : RTC_AIE_OFF, NULL); + close(rtc); + + /* XXX Note that this may or may not work; Linux may need to be suspended + or shut down in a special way to keep the RTC alarm active */ } + +/* Returns true if alarm was the reason we started up */ +bool rtc_check_alarm_started(bool release_alarm) +{ + int rtc = open("/dev/rtc0", O_WRONLY); + if (rtc < 0) + return false; + + /* XXX There is no generic way of determining wakeup reason. Will + likely need a target-specific hook. */ + + /* Disable alarm if requested */ + if (release_alarm) + ioctl(rtc, RTC_AIE_OFF, NULL); + + close(rtc); + return false; +} + +/* See if we received an alarm. */ +bool rtc_check_alarm_flag(void) +{ + struct rtc_wkalrm alrm; + + int rtc = open("/dev/rtc0", O_WRONLY); + if (rtc < 0) + return false; + + alrm.pending = 0; + /* XXX Documented as "mostly useless on Linux" except with EFI RTCs + Will likely need a target-specific hook. */ + ioctl(rtc, RTC_WKALM_RD, &alrm); + close(rtc); + + return alrm.pending; +} + +#endif /* HAVE_RTC_ALARM */ -- cgit