diff options
author | Kyoung Kim <kyoung.il.kim@intel.com> | 2015-03-23 18:18:36 -0700 |
---|---|---|
committer | ChromeOS Commit Bot <chromeos-commit-bot@chromium.org> | 2015-07-17 18:09:55 +0000 |
commit | 0e53f9d425e9a5bdad084b231438b7ec87432f3a (patch) | |
tree | 43c25ac86f19de0c0d1f812c2052b4e61fbbb456 | |
parent | 2e9ab7cbe48934deab914d96feb9141fdae95197 (diff) | |
download | chrome-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.c | 411 | ||||
-rw-r--r-- | chip/mec1322/registers.h | 3 | ||||
-rw-r--r-- | chip/mec1322/uart.c | 79 | ||||
-rw-r--r-- | core/cortex-m/cpu.h | 6 |
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)) |