summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRandall Spangler <rspangler@chromium.org>2012-08-06 16:12:20 -0700
committerGerrit <chrome-bot@google.com>2012-08-07 19:06:35 -0700
commited3f2af50fe8971a3b95f7e13d71754c108e02f4 (patch)
tree762640acbf9081a73b4e4b304ff420e85590ee51
parent45cd8463a3e1611e4721ccb9f1beef8f4ab897af (diff)
downloadchrome-ec-ed3f2af50fe8971a3b95f7e13d71754c108e02f4.tar.gz
Add real-time clock support
BUG=chrome-os-partner:12290 TEST=manual From EC console, rtcget (wait a few sec) rtcget hibernate 3 (wait for wake) rtcget (hold power+refresh; wait for reboot) rtcget rtcset 20000 rtcget (wait a few sec) rtcget Each rtcget should be a few seconds after the previous one. Pull the battery and remove AC power. Then restore AC power and rtcget (wait a few sec) rtcget Should be close to 0. That is, it should have reset to 0 when power was lost. From root shell, ectool rtcget should match the time from rtcget, truncated to the nearest second. ectool rtcset 30000 should set the time (do a rtcget to check). Change-Id: I535097feb7af8aa6583c8ef50ade66bb19bdff8f Signed-off-by: Randall Spangler <rspangler@chromium.org> Reviewed-on: https://gerrit.chromium.org/gerrit/29349 Reviewed-by: Bill Richardson <wfrichar@chromium.org>
-rw-r--r--chip/lm4/registers.h6
-rw-r--r--chip/lm4/system.c156
-rw-r--r--include/ec_commands.h20
-rw-r--r--util/ectool.c45
4 files changed, 215 insertions, 12 deletions
diff --git a/chip/lm4/registers.h b/chip/lm4/registers.h
index 6578e104e7..2a481abe2b 100644
--- a/chip/lm4/registers.h
+++ b/chip/lm4/registers.h
@@ -188,6 +188,12 @@ static inline int lm4_fan_addr(int ch, int offset)
#define LM4_HIBERNATE_HIBRTCM0 LM4REG(0x400fc004)
#define LM4_HIBERNATE_HIBRTCLD LM4REG(0x400fc00c)
#define LM4_HIBERNATE_HIBCTL LM4REG(0x400fc010)
+#define LM4_HIBCTL_WRC (1 << 31)
+#define LM4_HIBCTL_CLK32EN (1 << 6)
+#define LM4_HIBCTL_PINWEN (1 << 4)
+#define LM4_HIBCTL_RTCWEN (1 << 3)
+#define LM4_HIBCTL_HIBREQ (1 << 1)
+#define LM4_HIBCTL_RTCEN (1 << 0)
#define LM4_HIBERNATE_HIBIM LM4REG(0x400fc014)
#define LM4_HIBERNATE_HIBRIS LM4REG(0x400fc018)
#define LM4_HIBERNATE_HIBIC LM4REG(0x400fc020)
diff --git a/chip/lm4/system.c b/chip/lm4/system.c
index 4d7a36972e..8cd789c31f 100644
--- a/chip/lm4/system.c
+++ b/chip/lm4/system.c
@@ -6,10 +6,13 @@
/* System module for Chrome EC : LM4 hardware specific implementation */
#include "common.h"
+#include "console.h"
#include "cpu.h"
+#include "host_command.h"
#include "registers.h"
#include "system.h"
#include "task.h"
+#include "util.h"
/* Indices for hibernate data registers */
enum hibdata_index {
@@ -23,12 +26,17 @@ enum hibdata_index {
#define HIBDATA_WAKE_HARD_RESET (1 << 1) /* Hard reset via short RTC alarm */
#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
+
static int wait_for_hibctl_wc(void)
{
int i;
/* Wait for write-capable */
for (i = 0; i < 1000000; i++) {
- if (LM4_HIBERNATE_HIBCTL & 0x80000000)
+ if (LM4_HIBERNATE_HIBCTL & LM4_HIBCTL_WRC)
return EC_SUCCESS;
}
return EC_ERROR_UNKNOWN;
@@ -158,6 +166,46 @@ void __attribute__((section(".iram.text"))) __enter_hibernate(int hibctl)
}
/**
+ * Read the real-time clock.
+ *
+ * @param ss_ptr Destination for sub-seconds value, if not null.
+ *
+ * @return the real-time clock seconds value.
+ */
+uint32_t system_get_rtc(uint32_t *ss_ptr)
+{
+ uint32_t rtc, rtc2;
+ uint32_t rtcss;
+
+ /*
+ * The hibernate module isn't synchronized, so need to read repeatedly
+ * to guarantee a valid read.
+ */
+ do {
+ rtc = LM4_HIBERNATE_HIBRTCC;
+ rtcss = LM4_HIBERNATE_HIBRTCSS & 0x7fff;
+ rtc2 = LM4_HIBERNATE_HIBRTCC;
+ } while (rtc != rtc2);
+
+ if (ss_ptr)
+ *ss_ptr = rtcss;
+
+ return rtc;
+}
+
+/**
+ * Set the real-time clock.
+ *
+ * @param seconds New clock value.
+ */
+void system_set_rtc(uint32_t seconds)
+{
+ wait_for_hibctl_wc();
+ LM4_HIBERNATE_HIBRTCLD = seconds;
+ wait_for_hibctl_wc();
+}
+
+/**
* Internal hibernate function.
*
* @param seconds Number of seconds to sleep before RTC alarm
@@ -166,6 +214,9 @@ void __attribute__((section(".iram.text"))) __enter_hibernate(int hibctl)
*/
static void hibernate(uint32_t seconds, uint32_t microseconds, uint32_t flags)
{
+ uint32_t rtc, rtcss;
+ uint32_t hibctl;
+
/* Store hibernate flags */
hibdata_write(HIBDATA_INDEX_WAKE, flags);
@@ -177,19 +228,37 @@ static void hibernate(uint32_t seconds, uint32_t microseconds, uint32_t flags)
/* TODO: If sleeping forever, only wake on wake pin. */
+ /* 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 = seconds;
+ LM4_HIBERNATE_HIBRTCM0 = rtc;
wait_for_hibctl_wc();
- LM4_HIBERNATE_HIBRTCSS = (microseconds * 512 / 15625) << 16;
-
- /* Start counting toward the alarm */
- wait_for_hibctl_wc();
- LM4_HIBERNATE_HIBRTCLD = 0;
+ LM4_HIBERNATE_HIBRTCSS = rtcss << 16;
/* Go to hibernation and wake on RTC match or WAKE pin */
+ hibctl = (LM4_HIBERNATE_HIBCTL | LM4_HIBCTL_RTCWEN |
+ LM4_HIBCTL_PINWEN | LM4_HIBCTL_HIBREQ);
wait_for_hibctl_wc();
- __enter_hibernate(0x5B);
+ __enter_hibernate(hibctl);
}
@@ -208,21 +277,27 @@ int system_pre_init(void)
scratch = LM4_SYSTEM_RCGCHIB;
/*
- * Enable the hibernation oscillator, if it's not already enabled. We
- * use this to hold our scratchpad value across reboots.
+ * Enable the hibernation oscillator, if it's not already enabled.
+ * This should only need setting if the EC completely lost power (for
+ * example, the battery was pulled).
*/
- if (!(LM4_HIBERNATE_HIBCTL & 0x40)) {
+ if (!(LM4_HIBERNATE_HIBCTL & LM4_HIBCTL_CLK32EN)) {
int i;
/* Enable clock to hibernate module */
wait_for_hibctl_wc();
- LM4_HIBERNATE_HIBCTL |= 0x40;
+ LM4_HIBERNATE_HIBCTL |= LM4_HIBCTL_CLK32EN;
/* Wait for write-complete */
for (i = 0; i < 1000000; i++) {
if (LM4_HIBERNATE_HIBRIS & 0x10)
break;
}
+
+ /* Enable and reset RTC */
+ wait_for_hibctl_wc();
+ LM4_HIBERNATE_HIBCTL |= LM4_HIBCTL_RTCEN;
+ system_set_rtc(0);
}
/*
@@ -321,3 +396,60 @@ const char *system_get_chip_revision(void)
return rev;
}
+
+/*****************************************************************************/
+/* Console commands */
+
+static int command_system_rtc(int argc, char **argv)
+{
+ uint32_t rtc;
+ uint32_t rtcss;
+
+ if (argc == 3 && !strcasecmp(argv[1], "set")) {
+ char *e;
+ uint32_t t = strtoi(argv[2], &e, 0);
+ if (*e)
+ return EC_ERROR_PARAM2;
+
+ system_set_rtc(t);
+ } else if (argc > 1) {
+ return EC_ERROR_INVAL;
+ }
+
+ rtc = system_get_rtc(&rtcss);
+ ccprintf("RTC: 0x%08x.%04x (%d.%06d s)\n",
+ rtc, rtcss, rtc, (rtcss * (1000000/64)) / (32768/64));
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(rtc, command_system_rtc,
+ "[set <seconds>]",
+ "Get/set real-time clock",
+ NULL);
+
+/*****************************************************************************/
+/* Host commands */
+
+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);
+ args->response_size = sizeof(*r);
+
+ return EC_RES_SUCCESS;
+}
+DECLARE_HOST_COMMAND(EC_CMD_RTC_GET_VALUE,
+ system_rtc_get_value,
+ EC_VER_MASK(0));
+
+static int system_rtc_set_value(struct host_cmd_handler_args *args)
+{
+ const struct ec_params_rtc *p = args->params;
+
+ system_set_rtc(p->time);
+ return EC_RES_SUCCESS;
+}
+DECLARE_HOST_COMMAND(EC_CMD_RTC_SET_VALUE,
+ system_rtc_set_value,
+ EC_VER_MASK(0));
diff --git a/include/ec_commands.h b/include/ec_commands.h
index 53db5e626f..3a602ea1ba 100644
--- a/include/ec_commands.h
+++ b/include/ec_commands.h
@@ -716,6 +716,26 @@ struct ec_params_pstore_write {
} __packed;
/*****************************************************************************/
+/* Real-time clock */
+
+/* RTC params and response structures */
+struct ec_params_rtc {
+ uint32_t time;
+} __packed;
+
+struct ec_response_rtc {
+ uint32_t time;
+} __packed;
+
+/* These use ec_response_rtc */
+#define EC_CMD_RTC_GET_VALUE 0x44
+#define EC_CMD_RTC_GET_ALARM 0x45
+
+/* These all use ec_params_rtc */
+#define EC_CMD_RTC_SET_VALUE 0x46
+#define EC_CMD_RTC_SET_ALARM 0x47
+
+/*****************************************************************************/
/* Thermal engine commands */
/* Set thershold value */
diff --git a/util/ectool.c b/util/ectool.c
index a76feb2cd3..b0375c1e56 100644
--- a/util/ectool.c
+++ b/util/ectool.c
@@ -102,6 +102,10 @@ const char help_str[] =
" Reads a pattern from the EC via LPC\n"
" reboot_ec <RO|A|disable-jump> [at-shutdown]\n"
" Reboot EC to RO or RW\n"
+ " rtcget\n"
+ " Print real-time clock\n"
+ " rtcset <time>\n"
+ " Set real-time clock\n"
" sertest\n"
" Serial output test for COM2\n"
" switches\n"
@@ -2030,6 +2034,45 @@ int cmd_ec_hash(int argc, char *argv[])
}
+int cmd_rtc_get(int argc, char *argv[])
+{
+ struct ec_response_rtc r;
+ int rv;
+
+ rv = ec_command(EC_CMD_RTC_GET_VALUE, 0, NULL, 0, &r, sizeof(r));
+ if (rv < 0)
+ return rv;
+
+ printf("Current time: 0x%08x (%d)\n", r.time, r.time);
+ return 0;
+}
+
+
+int cmd_rtc_set(int argc, char *argv[])
+{
+ struct ec_params_rtc p;
+ char *e;
+ int rv;
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s <time>\n", argv[0]);
+ return -1;
+ }
+ p.time = strtol(argv[1], &e, 0);
+ if (e && *e) {
+ fprintf(stderr, "Bad time.\n");
+ return -1;
+ }
+
+ rv = ec_command(EC_CMD_RTC_SET_VALUE, 0, &p, sizeof(p), NULL, 0);
+ if (rv < 0)
+ return rv;
+
+ printf("Time set.\n");
+ return 0;
+}
+
+
struct command {
const char *name;
int (*handler)(int argc, char *argv[]);
@@ -2076,6 +2119,8 @@ const struct command commands[] = {
{"pwmsetkblight", cmd_pwm_set_keyboard_backlight},
{"readtest", cmd_read_test},
{"reboot_ec", cmd_reboot_ec},
+ {"rtcget", cmd_rtc_get},
+ {"rtcset", cmd_rtc_set},
{"sertest", cmd_serial_test},
{"switches", cmd_switches},
{"temps", cmd_temperature},