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
This commit is contained in:
Solomon Peachy 2019-01-31 11:05:13 -05:00
parent 02d347bc6f
commit 6984a7ce15
2 changed files with 149 additions and 8 deletions

View file

@ -66,6 +66,9 @@
/* define this if you have a real-time clock */ /* define this if you have a real-time clock */
#define CONFIG_RTC APPLICATION #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 */ /* The number of bytes reserved for loadable codecs */
#define CODEC_SIZE 0x80000 #define CODEC_SIZE 0x80000

View file

@ -27,9 +27,14 @@
#include <linux/rtc.h> #include <linux/rtc.h>
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
#include <stdbool.h>
#endif #endif
#include "config.h"
void rtc_init(void) void rtc_init(void)
{ {
tzset();
} }
int rtc_read_datetime(struct tm *tm) int rtc_read_datetime(struct tm *tm)
@ -46,8 +51,6 @@ int rtc_write_datetime(const struct tm *tm)
struct timeval tv; struct timeval tv;
struct tm *tm_time; struct tm *tm_time;
int rtc = open("/dev/rtc0", O_WRONLY);
tv.tv_sec = mktime((struct tm *)tm); tv.tv_sec = mktime((struct tm *)tm);
tv.tv_usec = 0; tv.tv_usec = 0;
@ -58,12 +61,147 @@ int rtc_write_datetime(const struct tm *tm)
time_t now = time(NULL); time_t now = time(NULL);
tm_time = gmtime(&now); tm_time = gmtime(&now);
/* 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); ioctl(rtc, RTC_SET_TIME, (struct rtc_time *)tm_time);
close(rtc); close(rtc);
}
return 0;
#else #else
(void)tm; (void)(*tm);
return -1;
#endif #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 */