diff --git a/firmware/export/config/agptekrocker.h b/firmware/export/config/agptekrocker.h index 372287f4b5..6b012730c0 100644 --- a/firmware/export/config/agptekrocker.h +++ b/firmware/export/config/agptekrocker.h @@ -66,6 +66,9 @@ /* define this if you have a real-time clock */ #define CONFIG_RTC APPLICATION +/* Define if the device can wake from an RTC alarm */ +#define HAVE_RTC_ALARM + /* The number of bytes reserved for loadable codecs */ #define CODEC_SIZE 0x80000 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); + /* 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); +#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); - return 0; -#else - (void)tm; - return -1; -#endif + /* 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 */