summaryrefslogtreecommitdiff
path: root/chip/lm4/system.c
diff options
context:
space:
mode:
Diffstat (limited to 'chip/lm4/system.c')
-rw-r--r--chip/lm4/system.c200
1 files changed, 160 insertions, 40 deletions
diff --git a/chip/lm4/system.c b/chip/lm4/system.c
index 33bfd0227c..05f6def1bd 100644
--- a/chip/lm4/system.c
+++ b/chip/lm4/system.c
@@ -13,6 +13,7 @@
#include "registers.h"
#include "system.h"
#include "task.h"
+#include "timer.h"
#include "util.h"
/* Indices for hibernate data registers */
@@ -28,18 +29,20 @@ enum hibdata_index {
#define HIBDATA_WAKE_PIN (1 << 2) /* Wake pin */
/*
- * Time it takes wait_for_hibctl_wc() to return. Experimentally verified to
- * be ~200 us; the value below is somewhat conservative.
- */
-#define HIB_WAIT_USEC 1000
-
-/*
* Time to hibernate to trigger a power-on reset. 50 ms is sufficient for the
* EC itself, but we need a longer delay to ensure the rest of the components
* on the same power rail are reset and 5VALW has dropped.
*/
#define HIB_RESET_USEC 1000000
+/*
+ * Convert between microseconds and the hibernation module RTC subsecond
+ * register which has 15-bit resolution. Divide down both numerator and
+ * denominator to avoid integer overflow while keeping the math accurate.
+ */
+#define HIB_RTC_USEC_TO_SUBSEC(us) ((us) * (32768/64) / (1000000/64))
+#define HIB_RTC_SUBSEC_TO_USEC(ss) ((ss) * (1000000/64) / (32768/64))
+
/**
* Wait for a write to commit to a hibernate register.
*
@@ -187,7 +190,7 @@ void __attribute__((section(".iram.text"))) __enter_hibernate(int hibctl)
*
* @return the real-time clock seconds value.
*/
-uint32_t system_get_rtc(uint32_t *ss_ptr)
+static uint32_t system_get_rtc_sec_subsec(uint32_t *ss_ptr)
{
uint32_t rtc, rtc2;
uint32_t rtcss, rtcss2;
@@ -209,6 +212,17 @@ uint32_t system_get_rtc(uint32_t *ss_ptr)
return rtc;
}
+timestamp_t system_get_rtc(void)
+{
+ uint32_t rtc, rtc_ss;
+ timestamp_t time;
+
+ rtc = system_get_rtc_sec_subsec(&rtc_ss);
+
+ time.val = ((uint64_t)rtc) * SECOND + HIB_RTC_SUBSEC_TO_USEC(rtc_ss);
+ return time;
+}
+
/**
* Set the real-time clock.
*
@@ -222,6 +236,91 @@ void system_set_rtc(uint32_t seconds)
}
/**
+ * Set the hibernate RTC match time at a given time from now
+ *
+ * @param seconds Number of seconds from now for RTC match
+ * @param microseconds Number of microseconds from now for RTC match
+ */
+static void set_hibernate_rtc_match_time(uint32_t seconds,
+ uint32_t microseconds)
+{
+ uint32_t rtc, rtcss;
+
+ /*
+ * Make sure that the requested delay is not less then the
+ * amount of time it takes to set the RTC match registers,
+ * otherwise, the match event could be missed.
+ */
+ if (seconds == 0 && microseconds < HIB_SET_RTC_MATCH_DELAY_USEC)
+ microseconds = HIB_SET_RTC_MATCH_DELAY_USEC;
+
+ /* Calculate the wake match */
+ rtc = system_get_rtc_sec_subsec(&rtcss) + seconds;
+ rtcss += HIB_RTC_USEC_TO_SUBSEC(microseconds);
+ if (rtcss > 0x7fff) {
+ rtc += rtcss >> 15;
+ rtcss &= 0x7fff;
+ }
+
+ /* Set RTC alarm match */
+ wait_for_hibctl_wc();
+ LM4_HIBERNATE_HIBRTCM0 = rtc;
+ wait_for_hibctl_wc();
+ LM4_HIBERNATE_HIBRTCSS = rtcss << 16;
+ wait_for_hibctl_wc();
+}
+
+/**
+ * Use hibernate module to set up an RTC interrupt at a given
+ * time from now
+ *
+ * @param seconds Number of seconds before RTC interrupt
+ * @param microseconds Number of microseconds before RTC interrupt
+ */
+void system_set_rtc_alarm(uint32_t seconds, uint32_t microseconds)
+{
+ /* Clear pending interrupt */
+ wait_for_hibctl_wc();
+ LM4_HIBERNATE_HIBIC = LM4_HIBERNATE_HIBRIS;
+
+ /* Set match time */
+ set_hibernate_rtc_match_time(seconds, microseconds);
+
+ /* Enable RTC interrupt on match */
+ wait_for_hibctl_wc();
+ LM4_HIBERNATE_HIBIM = 1;
+}
+
+/**
+ * Disable and clear the RTC interrupt.
+ */
+void system_reset_rtc_alarm(void)
+{
+ /* Disable hibernate interrupts */
+ LM4_HIBERNATE_HIBIM = 0;
+
+ /* Clear interrupts */
+ LM4_HIBERNATE_HIBIC = LM4_HIBERNATE_HIBRIS;
+}
+
+/**
+ * Hibernate module interrupt
+ */
+static void __hibernate_irq(void)
+{
+ system_reset_rtc_alarm();
+}
+DECLARE_IRQ(LM4_IRQ_HIBERNATE, __hibernate_irq, 1);
+
+/**
+ * Enable hibernate interrupt
+ */
+void system_enable_hib_interrupt(void)
+{
+ task_enable_irq(LM4_IRQ_HIBERNATE);
+}
+
+/**
* Internal hibernate function.
*
* @param seconds Number of seconds to sleep before RTC alarm
@@ -230,7 +329,6 @@ void system_set_rtc(uint32_t seconds)
*/
static void hibernate(uint32_t seconds, uint32_t microseconds, uint32_t flags)
{
- uint32_t rtc, rtcss;
uint32_t hibctl;
/* Set up wake reasons and hibernate flags */
@@ -244,46 +342,21 @@ static void hibernate(uint32_t seconds, uint32_t microseconds, uint32_t flags)
if (seconds || microseconds) {
hibctl |= LM4_HIBCTL_RTCWEN;
flags |= HIBDATA_WAKE_RTC;
+
+ set_hibernate_rtc_match_time(seconds, microseconds);
} else {
hibctl &= ~LM4_HIBCTL_RTCWEN;
}
wait_for_hibctl_wc();
LM4_HIBERNATE_HIBCTL = hibctl;
- /* Store hibernate flags */
- hibdata_write(HIBDATA_INDEX_WAKE, flags);
-
/* Clear pending interrupt */
wait_for_hibctl_wc();
LM4_HIBERNATE_HIBIC = LM4_HIBERNATE_HIBRIS;
- /* Add expected overhead for hibernate register writes */
- microseconds += HIB_WAIT_USEC * 4;
-
- /*
- * The code below must run uninterrupted to make sure we accurately
- * calculate the RTC match value.
- */
- interrupt_disable();
-
- /*
- * Calculate the wake match, compensating for additional delays caused
- * by writing to the hibernate register.
- */
- rtc = system_get_rtc(&rtcss) + seconds;
- rtcss += microseconds * (32768/64) / (1000000/64);
- if (rtcss > 0x7fff) {
- rtc += rtcss >> 15;
- rtcss &= 0x7fff;
- }
-
- /* Set RTC alarm match */
- wait_for_hibctl_wc();
- LM4_HIBERNATE_HIBRTCM0 = rtc;
- wait_for_hibctl_wc();
- LM4_HIBERNATE_HIBRTCSS = rtcss << 16;
+ /* Store hibernate flags */
+ hibdata_write(HIBDATA_INDEX_WAKE, flags);
- wait_for_hibctl_wc();
__enter_hibernate(hibctl | LM4_HIBCTL_HIBREQ);
}
@@ -296,6 +369,8 @@ void system_hibernate(uint32_t seconds, uint32_t microseconds)
void system_pre_init(void)
{
+ uint32_t hibctl;
+
/*
* Enable clocks to the hibernation module in run, sleep,
* and deep sleep modes.
@@ -331,6 +406,16 @@ void system_pre_init(void)
}
/*
+ * Set wake reasons to RTC match and WAKE pin by default.
+ * Before going in to hibernate, these may change.
+ */
+ hibctl = LM4_HIBERNATE_HIBCTL;
+ hibctl |= LM4_HIBCTL_RTCWEN;
+ hibctl |= LM4_HIBCTL_PINWEN;
+ wait_for_hibctl_wc();
+ LM4_HIBERNATE_HIBCTL = hibctl;
+
+ /*
* Initialize registers after reset to work around LM4 chip errata
* (still present in A3 chip stepping).
*/
@@ -504,9 +589,9 @@ static int command_system_rtc(int argc, char **argv)
return EC_ERROR_INVAL;
}
- rtc = system_get_rtc(&rtcss);
+ rtc = system_get_rtc_sec_subsec(&rtcss);
ccprintf("RTC: 0x%08x.%04x (%d.%06d s)\n",
- rtc, rtcss, rtc, (rtcss * (1000000/64)) / (32768/64));
+ rtc, rtcss, rtc, HIB_RTC_SUBSEC_TO_USEC(rtcss));
return EC_SUCCESS;
}
@@ -515,6 +600,41 @@ DECLARE_CONSOLE_COMMAND(rtc, command_system_rtc,
"Get/set real-time clock",
NULL);
+#ifdef CONFIG_CMD_RTC_ALARM
+/**
+ * Test the RTC alarm by setting an interrupt on RTC match.
+ */
+static int command_rtc_alarm_test(int argc, char **argv)
+{
+ int s = 1, us = 0;
+ char *e;
+
+ ccprintf("Setting RTC alarm\n");
+ system_enable_hib_interrupt();
+
+ if (argc > 1) {
+ s = strtoi(argv[1], &e, 10);
+ if (*e)
+ return EC_ERROR_PARAM1;
+
+ }
+ if (argc > 2) {
+ us = strtoi(argv[2], &e, 10);
+ if (*e)
+ return EC_ERROR_PARAM2;
+
+ }
+
+ system_set_rtc_alarm(s, us);
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(rtc_alarm, command_rtc_alarm_test,
+ "[seconds [microseconds]]",
+ "Test alarm",
+ NULL);
+#endif /* CONFIG_CMD_RTC_ALARM */
+
/*****************************************************************************/
/* Host commands */
@@ -522,7 +642,7 @@ static int system_rtc_get_value(struct host_cmd_handler_args *args)
{
struct ec_response_rtc *r = args->response;
- r->time = system_get_rtc(NULL);
+ r->time = system_get_rtc_sec_subsec(NULL);
args->response_size = sizeof(*r);
return EC_RES_SUCCESS;