summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKyoung Kim <kyoung.il.kim@intel.com>2015-03-23 18:18:36 -0700
committerChromeOS Commit Bot <chromeos-commit-bot@chromium.org>2015-07-17 18:09:55 +0000
commit0e53f9d425e9a5bdad084b231438b7ec87432f3a (patch)
tree43c25ac86f19de0c0d1f812c2052b4e61fbbb456
parent2e9ab7cbe48934deab914d96feb9141fdae95197 (diff)
downloadchrome-ec-0e53f9d425e9a5bdad084b231438b7ec87432f3a.tar.gz
mec1322: heavysleep in idle task and console deepsleep
Implemented mec1322's heavysleep in idle task to reduce further EC power down on S3. MEC1322 needs sleep-enabled for all blocks to acheive max power down including UART. Real heavysleep will be effective only when console/uart is not active. To enable this commit, board-specific commit is required. For example, check commit, "Enabling heavysleep idle task at S3". Test: 1. Put device into S3 mode by typing 'powerd_dbus_suspend" in Linux shell. 2. wait at least 1 min till EC console sleeps 3. measure EC power. Since idle task is continuously scheduled, EC will enters/exits to/from heavy sleep mode frequently in S3 and power consumption will be changed dynamically. For acurate power measurement, high-sampling-rate measurement system might be required and using DMM might not give accurate number. BUG=None TEST=Tested on evt1p0/evt1p7/DVT BRANCH=None Change-Id: I435ca347cab2f4d51cefeee802c3bf30fb393fa1 Signed-off-by: Kyoung Kim <kyoung.il.kim@intel.com> Reviewed-on: https://chromium-review.googlesource.com/283603 Reviewed-by: Alec Berg <alecaberg@chromium.org> Tested-by: Alec Berg <alecaberg@chromium.org> Commit-Queue: Alec Berg <alecaberg@chromium.org>
-rw-r--r--chip/mec1322/clock.c411
-rw-r--r--chip/mec1322/registers.h3
-rw-r--r--chip/mec1322/uart.c79
-rw-r--r--core/cortex-m/cpu.h6
4 files changed, 497 insertions, 2 deletions
diff --git a/chip/mec1322/clock.c b/chip/mec1322/clock.c
index f838dd0197..71c10e481c 100644
--- a/chip/mec1322/clock.c
+++ b/chip/mec1322/clock.c
@@ -8,13 +8,39 @@
#include "clock.h"
#include "common.h"
#include "console.h"
+#include "cpu.h"
+#include "hwtimer.h"
#include "registers.h"
+#include "shared_mem.h"
+#include "system.h"
+#include "task.h"
+#include "timer.h"
+#include "uart.h"
#include "util.h"
/* Console output macros */
#define CPUTS(outstr) cputs(CC_CLOCK, outstr)
#define CPRINTS(format, args...) cprints(CC_CLOCK, format, ## args)
+#ifdef CONFIG_LOW_POWER_IDLE
+/* Recovery time for HvySlp2 is 0 usec */
+#define HEAVY_SLEEP_RECOVER_TIME_USEC 75
+
+#define SET_HTIMER_DELAY_USEC 200
+
+static int idle_sleep_cnt;
+static int idle_dsleep_cnt;
+static uint64_t total_idle_dsleep_time_us;
+
+/*
+ * Fixed amount of time to keep the console in use flag true after boot in
+ * order to give a permanent window in which the heavy sleep mode is not used.
+ */
+#define CONSOLE_IN_USE_ON_BOOT_TIME (15*SECOND)
+static int console_in_use_timeout_sec = 60;
+static timestamp_t console_expire_time;
+#endif /*CONFIG_LOW_POWER_IDLE */
+
static int freq = 48000000;
void clock_wait_cycles(uint32_t cycles)
@@ -47,3 +73,388 @@ void clock_init(void)
;
#endif
}
+
+#ifdef CONFIG_LOW_POWER_IDLE
+/**
+ * initialization of Hibernation timer
+ */
+static void htimer_init(void)
+{
+ MEC1322_INT_BLK_EN |= 1 << 17;
+ MEC1322_INT_ENABLE(17) |= 1 << 20; /* GIRQ=17, aggregator bit = 20 */
+ MEC1322_HTIMER_PRELOAD = 0; /* disable at begining */
+
+ task_enable_irq(MEC1322_IRQ_HTIMER);
+}
+
+/**
+ * Use hibernate module to set up an htimer interrupt at a given
+ * time from now
+ *
+ * @param seconds Number of seconds before htimer interrupt
+ * @param microseconds Number of microseconds before htimer interrupt
+ */
+static void system_set_htimer_alarm(uint32_t seconds, uint32_t microseconds)
+{
+ if (seconds || microseconds) {
+
+ if (seconds > 2) {
+ /* count from 2 sec to 2 hrs, mec1322 sec 18.10.2 */
+ ASSERT(seconds <= 0xffff / 8);
+ MEC1322_HTIMER_CONTROL = 1; /* 0.125(=1/8) per clock */
+ /* (number of counts to be loaded)
+ * = seconds * ( 8 clocks per second )
+ * + microseconds / 125000
+ * ---> (0 if (microseconds < 125000)
+ */
+ MEC1322_HTIMER_PRELOAD =
+ (seconds * 8 + microseconds / 125000);
+
+ } else { /* count up to 2 sec. */
+
+ MEC1322_HTIMER_CONTROL = 0; /* 30.5(= 2/71) usec */
+
+ /* (number of counts to be loaded)
+ * = (total microseconds) / 30.5;
+ */
+ MEC1322_HTIMER_PRELOAD =
+ (seconds * 1000000 + microseconds) * 2 / 71;
+ }
+ }
+}
+
+/**
+ * return time slept in micro-seconds
+ */
+static timestamp_t system_get_htimer(void)
+{
+ uint16_t count;
+ timestamp_t time;
+
+ count = MEC1322_HTIMER_COUNT;
+
+
+ if (MEC1322_HTIMER_CONTROL == 1) /* if > 2 sec */
+ /* 0.125 sec per count */
+ time.le.lo = (uint32_t)(count * 125000);
+ else /* if < 2 sec */
+ /* 30.5(=71/2)usec per count */
+ time.le.lo = (uint32_t)(count * 71 / 2);
+
+ time.le.hi = 0;
+
+ return time; /* in uSec */
+}
+
+/**
+ * Disable and clear hibernation timer interrupt
+ */
+static void system_reset_htimer_alarm(void)
+{
+ MEC1322_HTIMER_PRELOAD = 0;
+}
+
+/**
+ * This is mec1322 specific and equivalent to ARM Cortex's
+ * 'DeepSleep' via system control block register, CPU_SCB_SYSCTRL
+ */
+static void prepare_for_deep_sleep(void)
+{
+ /* sysTick timer */
+ CPU_NVIC_ST_CTRL &= ~ST_ENABLE;
+ CPU_NVIC_ST_CTRL &= ~ST_COUNTFLAG;
+
+ /* Disable 32KHz clock */
+ MEC1322_VBAT_CE &= ~0x2;
+
+ /* Disable JTAG */
+ MEC1322_EC_JTAG_EN &= ~1;
+ /* Power down ADC VREF, ADC_VREF overrides ADC_CTRL. */
+ MEC1322_EC_ADC_VREF_PD |= 1;
+
+ /* Stop watchdog */
+ MEC1322_WDG_CTL &= ~1;
+
+ /* Stop timers */
+ MEC1322_TMR32_CTL(0) &= ~1;
+ MEC1322_TMR32_CTL(1) &= ~1;
+ MEC1322_TMR16_CTL(0) &= ~1;
+
+ MEC1322_PCR_CHIP_SLP_EN |= 0x3;
+ MEC1322_PCR_EC_SLP_EN = 0xFFFFFFFF;
+ MEC1322_PCR_HOST_SLP_EN = 0xFFFFFFFF;
+ MEC1322_PCR_EC_SLP_EN2 = 0xFFFFFFFF;
+
+ MEC1322_LPC_ACT = 0x0;
+ MEC1322_LPC_CLK_CTRL |= 0x2;
+
+ MEC1322_PCR_SLOW_CLK_CTL &= 0xFFFFFC00;
+
+ MEC1322_PCR_SYS_SLP_CTL = 0x2; /* heavysleep 2 */
+
+ CPU_NVIC_ST_CTRL &= ~ST_TICKINT; /* SYS_TICK_INT_DISABLE */
+}
+
+static void resume_from_deep_sleep(void)
+{
+ CPU_NVIC_ST_CTRL |= ST_TICKINT; /* SYS_TICK_INT_ENABLE */
+ CPU_NVIC_ST_CTRL |= ST_ENABLE;
+
+ MEC1322_EC_JTAG_EN = 1;
+ MEC1322_EC_ADC_VREF_PD &= ~1;
+ /* ADC_VREF_PD overrides ADC_CTRL ! */
+
+ /* Enable timer */
+ MEC1322_TMR32_CTL(0) |= 1;
+ MEC1322_TMR32_CTL(1) |= 1;
+ MEC1322_TMR16_CTL(0) |= 1;
+
+ /* Enable watchdog */
+ MEC1322_WDG_CTL |= 1;
+
+ /* Enable 32KHz clock */
+ MEC1322_VBAT_CE |= 0x2;
+
+ MEC1322_PCR_SLOW_CLK_CTL |= 0x1e0;
+ MEC1322_PCR_CHIP_SLP_EN &= ~0x3;
+ MEC1322_PCR_EC_SLP_EN &= ~0xe0700ff7;
+ MEC1322_PCR_HOST_SLP_EN &= ~0x5f003;
+ MEC1322_PCR_EC_SLP_EN2 &= ~0x1ffffff8;
+
+ MEC1322_PCR_SYS_SLP_CTL = 0xF8; /* default */
+
+ /* Enable UART */
+ MEC1322_LPC_ACT |= 1;
+ MEC1322_LPC_CLK_CTRL &= ~0x2;
+
+ MEC1322_PCR_SLOW_CLK_CTL = 0x1E0;
+}
+
+
+void clock_refresh_console_in_use(void)
+{
+ disable_sleep(SLEEP_MASK_CONSOLE);
+
+ /* Set console in use expire time. */
+ console_expire_time = get_time();
+ console_expire_time.val += console_in_use_timeout_sec * SECOND;
+}
+
+/**
+ * Low power idle task. Executed when no tasks are ready to be scheduled.
+ */
+void __idle(void)
+{
+ timestamp_t t0;
+ timestamp_t t1;
+ timestamp_t ht_t1;
+ uint32_t next_delay;
+ uint32_t max_sleep_time;
+ int time_for_dsleep;
+ int uart_ready_for_deepsleep;
+
+ htimer_init(); /* hibernation timer initialize */
+
+ disable_sleep(SLEEP_MASK_CONSOLE);
+ console_expire_time.val = get_time().val + CONSOLE_IN_USE_ON_BOOT_TIME;
+
+
+ /*
+ * Print when the idle task starts. This is the lowest priority task,
+ * so this only starts once all other tasks have gotten a chance to do
+ * their task inits and have gone to sleep.
+ */
+ CPRINTS("low power idle task started");
+
+ while (1) {
+ /* Disable interrupts */
+ interrupt_disable();
+
+ t0 = get_time(); /* uSec */
+
+ /* __hw_clock_event_get() is next programmed timer event */
+ next_delay = __hw_clock_event_get() - t0.le.lo;
+
+ time_for_dsleep = next_delay > (HEAVY_SLEEP_RECOVER_TIME_USEC +
+ SET_HTIMER_DELAY_USEC);
+
+ max_sleep_time = next_delay - HEAVY_SLEEP_RECOVER_TIME_USEC;
+
+ /* check if there enough time for deep sleep */
+ if (DEEP_SLEEP_ALLOWED && time_for_dsleep) {
+
+
+ /*
+ * Check if the console use has expired and console
+ * sleep is masked by GPIO(UART-RX) interrupt.
+ */
+ if ((sleep_mask & SLEEP_MASK_CONSOLE) &&
+ t0.val > console_expire_time.val) {
+ /* allow console to sleep. */
+ enable_sleep(SLEEP_MASK_CONSOLE);
+
+ /*
+ * Wait one clock before checking if heavy sleep
+ * is allowed to give time for sleep mask
+ * to be updated.
+ */
+ clock_wait_cycles(1);
+
+ if (LOW_SPEED_DEEP_SLEEP_ALLOWED)
+ CPRINTS("Disable console in deepsleep");
+ }
+
+
+ /* UART is not being used */
+ uart_ready_for_deepsleep = LOW_SPEED_DEEP_SLEEP_ALLOWED
+ && !uart_tx_in_progress()
+ && uart_buffer_empty();
+
+ /*
+ * Since MEC1322's heavysleep modes requires all block
+ * to be sleepable, UART/console's readiness is final
+ * decision factor of heavysleep of EC.
+ */
+ if (uart_ready_for_deepsleep) {
+
+ idle_dsleep_cnt++;
+
+ /*
+ * config UART Rx as GPIO wakeup interrupt
+ * source
+ */
+ uart_enter_dsleep();
+
+ /* MEC1322 specific deep-sleep mode */
+ prepare_for_deep_sleep();
+
+ /*
+ * 'max_sleep_time' value should be big
+ * enough so that hibernation timer's interrupt
+ * triggers only after 'wfi' completes its
+ * excution.
+ */
+ max_sleep_time -= (get_time().le.lo - t0.le.lo);
+
+ /* setup/enable htimer wakeup interrupt */
+ system_set_htimer_alarm(0, max_sleep_time);
+ } else {
+ idle_sleep_cnt++;
+ }
+
+ /* Wait for interrupt: goes into deep sleep. */
+ asm("wfi");
+
+ if (uart_ready_for_deepsleep) {
+
+ resume_from_deep_sleep();
+
+ /*
+ * Fast forward timer according to htimer
+ * counter:
+ * Since all blocks including timers will be in
+ * sleep mode, timers stops except hibernate
+ * timer.
+ * And system schedule timer should be corrected
+ * after wakeup by either hibernate timer or
+ * GPIO_UART_RX interrupt.
+ */
+ ht_t1 = system_get_htimer();
+
+ /* disable/clear htimer wakeup interrupt */
+ system_reset_htimer_alarm();
+
+ t1.val = t0.val +
+ (uint64_t)(max_sleep_time - ht_t1.le.lo);
+
+ force_time(t1);
+
+ /* re-eanble UART */
+ uart_exit_dsleep();
+
+ /* Record time spent in deep sleep. */
+ total_idle_dsleep_time_us +=
+ (uint64_t)(max_sleep_time - ht_t1.le.lo);
+ }
+
+ } else { /* CPU 'Sleep' mode */
+
+ idle_sleep_cnt++;
+
+ asm("wfi");
+
+ }
+
+ interrupt_enable();
+ } /* while(1) */
+}
+#endif /*CONFIG_LOW_POWER_IDLE*/
+
+#ifdef CONFIG_LOW_POWER_IDLE
+/**
+ * Print low power idle statistics
+ */
+static int command_idle_stats(int argc, char **argv)
+{
+ timestamp_t ts = get_time();
+
+ ccprintf("Num idle calls that sleep: %d\n", idle_sleep_cnt);
+ ccprintf("Num idle calls that deep-sleep: %d\n", idle_dsleep_cnt);
+
+ ccprintf("Total Time spent in deep-sleep(sec): %.6ld(s)\n",
+ total_idle_dsleep_time_us);
+ ccprintf("Total time on: %.6lds\n\n", ts.val);
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(idlestats, command_idle_stats,
+ "",
+ "Print last idle stats",
+ NULL);
+
+/**
+ * Configure deep sleep clock settings.
+ */
+static int command_dsleep(int argc, char **argv)
+{
+ int v;
+
+ if (argc > 1) {
+ if (parse_bool(argv[1], &v)) {
+ /*
+ * Force deep sleep not to use heavy sleep mode or
+ * allow it to use the heavy sleep mode.
+ */
+ if (v) /* 'on' */
+ disable_sleep(SLEEP_MASK_FORCE_NO_LOW_SPEED);
+ else /* 'off' */
+ enable_sleep(SLEEP_MASK_FORCE_NO_LOW_SPEED);
+ } else {
+ /* Set console in use timeout. */
+ char *e;
+ v = strtoi(argv[1], &e, 10);
+ if (*e)
+ return EC_ERROR_PARAM1;
+
+ console_in_use_timeout_sec = v;
+
+ /* Refresh console in use to use new timeout. */
+ clock_refresh_console_in_use();
+ }
+ }
+
+ ccprintf("Sleep mask: %08x\n", sleep_mask);
+ ccprintf("Console in use timeout: %d sec\n",
+ console_in_use_timeout_sec);
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(dsleep, command_dsleep,
+ "[ on | off | <timeout> sec]",
+ "Deep sleep clock settings:\nUse 'on' to force deep "
+ "sleep NOT to enter heavysleep mode.\nUse 'off' to "
+ "allow deep sleep to use heavysleep whenever conditions"
+ "allow.\n"
+ "Give a timeout value for the console in use timeout.\n"
+ "See also 'sleepmask'.",
+ NULL);
+#endif /* CONFIG_LOW_POWER_IDLE */
diff --git a/chip/mec1322/registers.h b/chip/mec1322/registers.h
index aab3da935c..7da25ae059 100644
--- a/chip/mec1322/registers.h
+++ b/chip/mec1322/registers.h
@@ -89,6 +89,8 @@
#define MEC1322_UART_MSR REG8(MEC1322_UART_RUNTIME_BASE + 0x6)
#define MEC1322_UART_SCR REG8(MEC1322_UART_RUNTIME_BASE + 0x7)
+/* Bit defines for MEC1322_UART_LSR */
+#define MEC1322_LSR_TX_EMPTY (1 << 5)
/* GPIO */
#define MEC1322_GPIO_BASE 0x40081000
@@ -161,6 +163,7 @@ static inline uintptr_t gpio_port_base(int port_id)
#define MEC1322_LPC_RT_BASE 0x400f3100
#define MEC1322_LPC_BUS_MONITOR REG32(MEC1322_LPC_RT_BASE + 0x4)
+#define MEC1322_LPC_CLK_CTRL REG32(MEC1322_LPC_RT_BASE + 0x10)
#define MEC1322_LPC_MEM_HOST_CFG REG32(MEC1322_LPC_RT_BASE + 0xfc)
diff --git a/chip/mec1322/uart.c b/chip/mec1322/uart.c
index fe47ee262d..3fdb156188 100644
--- a/chip/mec1322/uart.c
+++ b/chip/mec1322/uart.c
@@ -32,6 +32,9 @@ void uart_tx_start(void)
if (MEC1322_UART_IER & (1 << 1))
return;
+ /* Do not allow deep sleep while transmit in progress */
+ disable_sleep(SLEEP_MASK_UART);
+
/*
* Re-enable the transmit interrupt, then forcibly trigger the
* interrupt. This works around a hardware problem with the
@@ -45,12 +48,15 @@ void uart_tx_start(void)
void uart_tx_stop(void)
{
MEC1322_UART_IER &= ~(1 << 1);
+
+ /* Re-allow deep sleep */
+ enable_sleep(SLEEP_MASK_UART);
}
void uart_tx_flush(void)
{
/* Wait for transmit FIFO empty */
- while (!(MEC1322_UART_LSR & (1 << 5)))
+ while (!(MEC1322_UART_LSR & MEC1322_LSR_TX_EMPTY))
;
}
@@ -60,7 +66,13 @@ int uart_tx_ready(void)
* We have no indication of free space in transmit FIFO. To work around
* this, we check transmit FIFO empty bit every 16 characters written.
*/
- return tx_fifo_used != 0 || MEC1322_UART_LSR & (1 << 5);
+ return tx_fifo_used != 0 || (MEC1322_UART_LSR & MEC1322_LSR_TX_EMPTY);
+}
+
+int uart_tx_in_progress(void)
+{
+ /* return 0: FIFO is empty, 1: FIFO NOT Empty */
+ return !(MEC1322_UART_LSR & MEC1322_LSR_TX_EMPTY);
}
int uart_rx_available(void)
@@ -156,3 +168,66 @@ void uart_init(void)
init_done = 1;
}
+
+#ifdef CONFIG_LOW_POWER_IDLE
+void uart_enter_dsleep(void)
+{
+ const struct gpio_info g = gpio_list[GPIO_UART0_RX];
+
+ /* Disable the UART interrupt. */
+ task_disable_irq(MEC1322_IRQ_UART); /* NVIC interrupt for UART=13 */
+
+ /*
+ * Set the UART0 RX pin to be a GPIO-162(fixed pin) interrupt
+ * with the flags defined in the gpio.inc file.
+ */
+ gpio_set_flags_by_mask(g.port, g.mask, g.flags);
+ gpio_set_alternate_function(g.port, g.mask, -1);
+
+ /* power-down/de-activate UART0 */
+ MEC1322_UART_ACT &= ~(1 << 0);
+
+ /* Clear pending interrupts on GPIO_UART0_RX(GPIO162, girq=8, bit=18) */
+ MEC1322_INT_SOURCE(8) = (1<<18);
+
+ /* Enable GPIO interrupts on the UART0 RX pin. */
+ gpio_enable_interrupt(GPIO_UART0_RX);
+}
+
+
+void uart_exit_dsleep(void)
+{
+ /*
+ * If the UART0 RX GPIO interrupt has not fired, then no edge has been
+ * detected. Disable the GPIO interrupt so that switching the pin over
+ * to a UART pin doesn't inadvertently cause a GPIO edge interrupt.
+ * Note: we can't disable this interrupt if it has already fired
+ * because then the IRQ will not run at all.
+ */
+ if (!((1 << 18) & MEC1322_INT_SOURCE(8))) /* if edge interrupt */
+ gpio_disable_interrupt(GPIO_UART0_RX);
+
+ /* Configure UART0 pins for use in UART peripheral. */
+ gpio_config_module(MODULE_UART, 1);
+
+ /* Clear pending interrupts on UART peripheral and enable interrupts. */
+ uart_clear_rx_fifo(0);
+ task_enable_irq(MEC1322_IRQ_UART); /* NVIC interrupt for UART = 13 */
+
+ /* power-up/activate UART0 */
+ MEC1322_UART_ACT |= (1 << 0);
+}
+
+void uart_deepsleep_interrupt(enum gpio_signal signal)
+{
+ /*
+ * Activity seen on UART RX pin while UART was disabled for deep sleep.
+ * The console won't see that character because the UART is disabled,
+ * so we need to inform the clock module of UART activity ourselves.
+ */
+ clock_refresh_console_in_use();
+
+ /* Disable interrupts on UART0 RX pin to avoid repeated interrupts. */
+ gpio_disable_interrupt(GPIO_UART0_RX);
+}
+#endif /* CONFIG_LOW_POWER_IDLE */
diff --git a/core/cortex-m/cpu.h b/core/cortex-m/cpu.h
index e170d42782..2951f4b5e0 100644
--- a/core/cortex-m/cpu.h
+++ b/core/cortex-m/cpu.h
@@ -13,6 +13,12 @@
/* Macro to access 32-bit registers */
#define CPUREG(addr) (*(volatile uint32_t*)(addr))
+#define CPU_NVIC_ST_CTRL CPUREG(0xE000E010)
+#define ST_ENABLE (1 << 0)
+#define ST_TICKINT (1 << 1)
+#define ST_CLKSOURCE (1 << 2)
+#define ST_COUNTFLAG (1 << 16)
+
/* Nested Vectored Interrupt Controller */
#define CPU_NVIC_EN(x) CPUREG(0xe000e100 + 4 * (x))
#define CPU_NVIC_DIS(x) CPUREG(0xe000e180 + 4 * (x))