summaryrefslogtreecommitdiff
path: root/chip/lm4/system.c
diff options
context:
space:
mode:
authorAlec Berg <alecaberg@chromium.org>2013-10-07 12:23:35 -0700
committerchrome-internal-fetch <chrome-internal-fetch@google.com>2013-10-15 00:27:14 +0000
commitc5b90d7e77511e45f2c0021c1a0bc18b09310933 (patch)
treeaf3ab9f0b74cb239808cca51c681be840d32a107 /chip/lm4/system.c
parent193f2298bd1a078f1ac07aacf9dc50132cbb39d3 (diff)
downloadchrome-ec-c5b90d7e77511e45f2c0021c1a0bc18b09310933.tar.gz
lm4: Add a low power idle task.stabilize-4825.B
First implementation of a low power idle task for the LM4 chip. The low power mode is selected by defining CONFIG_LOW_POWER_IDLE in a board.h file. This commit turns it on for Peppy, Slippy, and Falco only because those are the only boards tested. When using the low power idle task, the chip goes in to deep sleep when it can. Deep sleep disables clocks to most peripherals and puts the onboard flash and RAM into a low power mode. The chip is woken out of deep sleep using the RTC in the hibernate module. Increased the idle task stack size to handle more involved idle task. In board.c, the array of GPIO info can be used to select which GPIO points can wake up the EC from deep sleep. Currenlty selected are the power button, lid open, AC present, PCH_SLP_S3, and PCH_SLP_S5. Additionally the port with the KB scan row GPIO point is also enabled to wake up the EC from deep sleep. Signed-off-by: Alec Berg <alecaberg@chromium.org> BUG=None BRANCH=none TEST=Passes all unit tests. Runs on slippy, peppy, and falco with no noticeable side affects. Verified that the power consumed by the EC is lower when in S3, S5 and G3 by scoping the sense resistor powering the chip. Change-Id: I83fa9a159a4b79201b99f2c32678dc4fc8921726 Reviewed-on: https://chromium-review.googlesource.com/172183 Reviewed-by: Vincent Palatin <vpalatin@chromium.org> Reviewed-by: Randall Spangler <rspangler@chromium.org> Commit-Queue: Alec Berg <alecaberg@chromium.org> Tested-by: Alec Berg <alecaberg@chromium.org>
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;