diff options
-rw-r--r-- | board/falco/board.c | 10 | ||||
-rw-r--r-- | board/falco/board.h | 1 | ||||
-rw-r--r-- | board/peppy/board.c | 10 | ||||
-rw-r--r-- | board/peppy/board.h | 1 | ||||
-rw-r--r-- | board/slippy/board.c | 10 | ||||
-rw-r--r-- | board/slippy/board.h | 1 | ||||
-rw-r--r-- | chip/lm4/clock.c | 242 | ||||
-rw-r--r-- | chip/lm4/config_chip.h | 8 | ||||
-rw-r--r-- | chip/lm4/registers.h | 9 | ||||
-rw-r--r-- | chip/lm4/system.c | 200 | ||||
-rw-r--r-- | chip/lm4/uart.c | 16 | ||||
-rw-r--r-- | chip/stm32/clock-stm32f.c | 36 | ||||
-rw-r--r-- | chip/stm32/clock-stm32l.c | 10 | ||||
-rw-r--r-- | chip/stm32/uart.c | 1 | ||||
-rw-r--r-- | common/chipset_haswell.c | 12 | ||||
-rw-r--r-- | common/console_output.c | 1 | ||||
-rw-r--r-- | common/i2c_common.c | 6 | ||||
-rw-r--r-- | common/system_common.c | 32 | ||||
-rw-r--r-- | include/clock.h | 15 | ||||
-rw-r--r-- | include/config.h | 1 | ||||
-rw-r--r-- | include/console.h | 1 | ||||
-rw-r--r-- | include/system.h | 70 |
22 files changed, 552 insertions, 141 deletions
diff --git a/board/falco/board.c b/board/falco/board.c index b7f434d056..c037441214 100644 --- a/board/falco/board.c +++ b/board/falco/board.c @@ -36,19 +36,19 @@ /* GPIO signal list. Must match order from enum gpio_signal. */ const struct gpio_info gpio_list[] = { /* Inputs with interrupt handlers are first for efficiency */ - {"POWER_BUTTON_L", LM4_GPIO_A, (1<<2), GPIO_INT_BOTH, + {"POWER_BUTTON_L", LM4_GPIO_A, (1<<2), GPIO_INT_BOTH_DSLEEP, power_button_interrupt}, - {"LID_OPEN", LM4_GPIO_A, (1<<3), GPIO_INT_BOTH, + {"LID_OPEN", LM4_GPIO_A, (1<<3), GPIO_INT_BOTH_DSLEEP, lid_interrupt}, - {"AC_PRESENT", LM4_GPIO_H, (1<<3), GPIO_INT_BOTH, + {"AC_PRESENT", LM4_GPIO_H, (1<<3), GPIO_INT_BOTH_DSLEEP, extpower_interrupt}, {"PCH_BKLTEN", LM4_GPIO_M, (1<<3), GPIO_INT_BOTH, backlight_interrupt}, {"PCH_SLP_S0_L", LM4_GPIO_G, (1<<6), GPIO_INT_BOTH, x86_interrupt}, - {"PCH_SLP_S3_L", LM4_GPIO_G, (1<<7), GPIO_INT_BOTH, + {"PCH_SLP_S3_L", LM4_GPIO_G, (1<<7), GPIO_INT_BOTH_DSLEEP, x86_interrupt}, - {"PCH_SLP_S5_L", LM4_GPIO_H, (1<<1), GPIO_INT_BOTH, + {"PCH_SLP_S5_L", LM4_GPIO_H, (1<<1), GPIO_INT_BOTH_DSLEEP, x86_interrupt}, {"PCH_SLP_SUS_L", LM4_GPIO_G, (1<<3), GPIO_INT_BOTH, x86_interrupt}, diff --git a/board/falco/board.h b/board/falco/board.h index 5f9ca32e22..bfb692a577 100644 --- a/board/falco/board.h +++ b/board/falco/board.h @@ -28,6 +28,7 @@ #define CONFIG_FAN_POWER_GOOD GPIO_PP5000_PGOOD #define CONFIG_KEYBOARD_BOARD_CONFIG #define CONFIG_KEYBOARD_PROTOCOL_8042 +#define CONFIG_LOW_POWER_IDLE #define CONFIG_POWER_BUTTON #define CONFIG_POWER_BUTTON_X86 #define CONFIG_PWM diff --git a/board/peppy/board.c b/board/peppy/board.c index f1d55dd738..738ea662b0 100644 --- a/board/peppy/board.c +++ b/board/peppy/board.c @@ -33,19 +33,19 @@ /* GPIO signal list. Must match order from enum gpio_signal. */ const struct gpio_info gpio_list[] = { /* Inputs with interrupt handlers are first for efficiency */ - {"POWER_BUTTON_L", LM4_GPIO_A, (1<<2), GPIO_INT_BOTH, + {"POWER_BUTTON_L", LM4_GPIO_A, (1<<2), GPIO_INT_BOTH_DSLEEP, power_button_interrupt}, - {"LID_OPEN", LM4_GPIO_A, (1<<3), GPIO_INT_BOTH, + {"LID_OPEN", LM4_GPIO_A, (1<<3), GPIO_INT_BOTH_DSLEEP, lid_interrupt}, - {"AC_PRESENT", LM4_GPIO_H, (1<<3), GPIO_INT_BOTH, + {"AC_PRESENT", LM4_GPIO_H, (1<<3), GPIO_INT_BOTH_DSLEEP, extpower_interrupt}, {"PCH_BKLTEN", LM4_GPIO_M, (1<<3), GPIO_INT_BOTH, backlight_interrupt}, {"PCH_SLP_S0_L", LM4_GPIO_G, (1<<6), GPIO_INT_BOTH, x86_interrupt}, - {"PCH_SLP_S3_L", LM4_GPIO_G, (1<<7), GPIO_INT_BOTH, + {"PCH_SLP_S3_L", LM4_GPIO_G, (1<<7), GPIO_INT_BOTH_DSLEEP, x86_interrupt}, - {"PCH_SLP_S5_L", LM4_GPIO_H, (1<<1), GPIO_INT_BOTH, + {"PCH_SLP_S5_L", LM4_GPIO_H, (1<<1), GPIO_INT_BOTH_DSLEEP, x86_interrupt}, {"PCH_SLP_SUS_L", LM4_GPIO_G, (1<<3), GPIO_INT_BOTH, x86_interrupt}, diff --git a/board/peppy/board.h b/board/peppy/board.h index 160e6f1d2b..7f00f1bcd4 100644 --- a/board/peppy/board.h +++ b/board/peppy/board.h @@ -31,6 +31,7 @@ #define CONFIG_KEYBOARD_BOARD_CONFIG #define CONFIG_KEYBOARD_PROTOCOL_8042 #define CONFIG_LED_COMMON +#define CONFIG_LOW_POWER_IDLE #define CONFIG_POWER_BUTTON #define CONFIG_POWER_BUTTON_X86 #define CONFIG_PWM diff --git a/board/slippy/board.c b/board/slippy/board.c index cf937ead4f..df731f3610 100644 --- a/board/slippy/board.c +++ b/board/slippy/board.c @@ -33,19 +33,19 @@ /* GPIO signal list. Must match order from enum gpio_signal. */ const struct gpio_info gpio_list[] = { /* Inputs with interrupt handlers are first for efficiency */ - {"POWER_BUTTON_L", LM4_GPIO_A, (1<<2), GPIO_INT_BOTH, + {"POWER_BUTTON_L", LM4_GPIO_A, (1<<2), GPIO_INT_BOTH_DSLEEP, power_button_interrupt}, - {"LID_OPEN", LM4_GPIO_A, (1<<3), GPIO_INT_BOTH, + {"LID_OPEN", LM4_GPIO_A, (1<<3), GPIO_INT_BOTH_DSLEEP, lid_interrupt}, - {"AC_PRESENT", LM4_GPIO_H, (1<<3), GPIO_INT_BOTH, + {"AC_PRESENT", LM4_GPIO_H, (1<<3), GPIO_INT_BOTH_DSLEEP, extpower_interrupt}, {"PCH_BKLTEN", LM4_GPIO_M, (1<<3), GPIO_INT_BOTH, backlight_interrupt}, {"PCH_SLP_S0_L", LM4_GPIO_G, (1<<6), GPIO_INT_BOTH, x86_interrupt}, - {"PCH_SLP_S3_L", LM4_GPIO_G, (1<<7), GPIO_INT_BOTH, + {"PCH_SLP_S3_L", LM4_GPIO_G, (1<<7), GPIO_INT_BOTH_DSLEEP, x86_interrupt}, - {"PCH_SLP_S5_L", LM4_GPIO_H, (1<<1), GPIO_INT_BOTH, + {"PCH_SLP_S5_L", LM4_GPIO_H, (1<<1), GPIO_INT_BOTH_DSLEEP, x86_interrupt}, {"PCH_SLP_SUS_L", LM4_GPIO_G, (1<<3), GPIO_INT_BOTH, x86_interrupt}, diff --git a/board/slippy/board.h b/board/slippy/board.h index 7dc2cb1bd9..2443005ebc 100644 --- a/board/slippy/board.h +++ b/board/slippy/board.h @@ -29,6 +29,7 @@ #define CONFIG_FAN_POWER_GOOD GPIO_PP5000_PGOOD #define CONFIG_KEYBOARD_BOARD_CONFIG #define CONFIG_KEYBOARD_PROTOCOL_8042 +#define CONFIG_LOW_POWER_IDLE #define CONFIG_POWER_BUTTON #define CONFIG_POWER_BUTTON_X86 #define CONFIG_PWM diff --git a/chip/lm4/clock.c b/chip/lm4/clock.c index 2d7365f9ab..478cb796eb 100644 --- a/chip/lm4/clock.c +++ b/chip/lm4/clock.c @@ -11,6 +11,7 @@ #include "cpu.h" #include "gpio.h" #include "hooks.h" +#include "hwtimer.h" #include "registers.h" #include "system.h" #include "task.h" @@ -18,8 +19,26 @@ #include "util.h" #include "watchdog.h" +/* Console output macros */ +#define CPUTS(outstr) cputs(CC_CLOCK, outstr) +#define CPRINTF(format, args...) cprintf(CC_CLOCK, format, ## args) + #define PLL_CLOCK 66666667 /* System clock = 200MHz PLL/3 = 66.667MHz */ +/* + * Length of time for the processor to wake up from deep sleep. Actual + * measurement gives anywhere from 75-200us, so this is conservative. + */ +#define DEEP_SLEEP_RECOVER_TIME_USEC 300 + +/* Low power idle statistics */ +#ifdef CONFIG_LOW_POWER_IDLE +static int idle_sleep_cnt; +static int idle_dsleep_cnt; +static uint64_t idle_dsleep_time_us; +static int dsleep_recovery_margin_us = 1000000; +#endif + static int freq; /** @@ -33,6 +52,17 @@ static void disable_pll(void) LM4_SYSTEM_RCC_PWRDN | LM4_SYSTEM_RCC_OSCSRC(1) | LM4_SYSTEM_RCC_MOSCDIS; + +#ifdef CONFIG_LOW_POWER_IDLE + /* + * If using the low power idle, then set the ACG bit, which specifies + * that the sleep and deep sleep modes are using their own clock gating + * registers SCGC and DCGS respectively instead of using the run mode + * clock gating registers RCGC. + */ + LM4_SYSTEM_RCC |= LM4_SYSTEM_RCC_ACG; +#endif + LM4_SYSTEM_RCC2 &= ~LM4_SYSTEM_RCC2_USERCC2; freq = INTERNAL_CLOCK; @@ -56,6 +86,16 @@ static void enable_pll(void) LM4_SYSTEM_RCC_OSCSRC(1) | LM4_SYSTEM_RCC_MOSCDIS; +#ifdef CONFIG_LOW_POWER_IDLE + /* + * If using the low power idle, then set the ACG bit, which specifies + * that the sleep and deep sleep modes are using their own clock gating + * registers SCGC and DCGS respectively instead of using the run mode + * clock gating registers RCGC. + */ + LM4_SYSTEM_RCC |= LM4_SYSTEM_RCC_ACG; +#endif + /* Wait for the PLL to lock */ clock_wait_cycles(1024); while (!(LM4_SYSTEM_PLLSTAT & 1)) @@ -155,6 +195,105 @@ void clock_disable_peripheral(uint32_t offset, uint32_t mask, uint32_t mode) *(LM4_SYSTEM_DCGC_BASE + offset) &= ~mask; } +/* + * The low power idle task does not support using the EEPROM, + * because it is dangerous to go to deep sleep while EEPROM + * transaction is in progress. To fix, LM4_EEPROM_EEDONE, should + * be checked before going in to deep sleep. + */ +#if defined(CONFIG_LOW_POWER_IDLE) && defined(CONFIG_EEPROM) +#error "Low power idle mode does not support use of EEPROM" +#endif + +#ifdef CONFIG_LOW_POWER_IDLE + +/* Low power idle task. Executed when no tasks are ready to be scheduled. */ +void __idle(void) +{ + timestamp_t t0, t1, rtc_t0, rtc_t1; + int next_delay = 0; + int time_for_dsleep, margin_us; + + /* Enable the hibernate IRQ used to wake up from deep sleep */ + system_enable_hib_interrupt(); + + /* Set SRAM and flash power management to 'low power' in deep sleep. */ + LM4_SYSTEM_DSLPPWRCFG = 0x23; + + /* + * 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. + */ + CPRINTF("[%T low power idle task started]\n"); + + while (1) { + /* + * Disable interrupts before going to deep sleep in order to + * calculate the appropriate time to wake up. Note: the wfi + * instruction waits until an interrupt is pending, so it + * will still wake up even with interrupts disabled. + */ + interrupt_disable(); + + t0 = get_time(); + next_delay = __hw_clock_event_get() - t0.le.lo; + + /* Do we have enough time before next event to deep sleep. */ + time_for_dsleep = next_delay > (DEEP_SLEEP_RECOVER_TIME_USEC + + HIB_SET_RTC_MATCH_DELAY_USEC); + + if (!sleep_mask && time_for_dsleep) { + /* Deep-sleep in STOP mode. */ + idle_dsleep_cnt++; + + /* Set deep sleep bit. */ + CPU_SCB_SYSCTRL |= 0x4; + + /* Record real time before sleeping. */ + rtc_t0 = system_get_rtc(); + + /* + * Set RTC interrupt in time to wake up before + * next event. + */ + system_set_rtc_alarm(0, next_delay - + DEEP_SLEEP_RECOVER_TIME_USEC); + + /* Wait for interrupt: goes into deep sleep. */ + asm("wfi"); + + /* Clear deep sleep bit. */ + CPU_SCB_SYSCTRL &= ~0x4; + + /* Disable and clear RTC interrupt. */ + system_reset_rtc_alarm(); + + /* Fast forward timer according to RTC counter. */ + rtc_t1 = system_get_rtc(); + t1.val = t0.val + (rtc_t1.val - rtc_t0.val); + force_time(t1); + + /* Record time spent in deep sleep. */ + idle_dsleep_time_us += (rtc_t1.val - rtc_t0.val); + + /* Calculate how close we were to missing deadline */ + margin_us = next_delay - (int)(rtc_t1.val - rtc_t0.val); + + /* Record the closest to missing a deadline. */ + if (margin_us < dsleep_recovery_margin_us) + dsleep_recovery_margin_us = margin_us; + } else { + idle_sleep_cnt++; + + /* Normal idle : only CPU clock stopped. */ + asm("wfi"); + } + interrupt_enable(); + } +} +#endif /* CONFIG_LOW_POWER_IDLE */ + /*****************************************************************************/ /* Console commands */ @@ -169,36 +308,56 @@ void clock_disable_peripheral(uint32_t offset, uint32_t mask, uint32_t mode) * 3 : CPU in sleep mode and peripherals gated * 4 : CPU in deep sleep mode * 5 : CPU in deep sleep mode and peripherals gated + * + * Clocks : + * 0 : No change + * 1 : 16MHz + * 2 : 1 MHz + * 3 : 30kHz + * + * SRAM Power Management: + * 0 : Active + * 1 : Standby + * 3 : Low Power + * + * Flash Power Management: + * 0 : Active + * 2 : Low Power */ static int command_sleep(int argc, char **argv) { int level = 0; int clock = 0; + int sram_pm = 0; + int flash_pm = 0; uint32_t uartibrd = 0; uint32_t uartfbrd = 0; - if (argc >= 2) { + if (argc >= 2) level = strtoi(argv[1], NULL, 10); - } - if (argc >= 3) { + if (argc >= 3) clock = strtoi(argv[2], NULL, 10); - } + if (argc >= 4) + sram_pm = strtoi(argv[3], NULL, 10); + if (argc >= 5) + flash_pm = strtoi(argv[4], NULL, 10); #ifdef BOARD_bds - /* remove LED current sink */ + /* Remove LED current sink. */ gpio_set_level(GPIO_DEBUG_LED, 0); #endif - ccprintf("Going to sleep : level %d clock %d...\n", level, clock); + ccprintf("Sleep : level %d, clock %d, sram pm %d, flash_pm %d...\n", + level, clock, sram_pm, flash_pm); cflush(); - /* clock setting */ + /* Set clock speed. */ if (clock) { /* Use ROM code function to set the clock */ void **func_table = (void **)*(uint32_t *)0x01000044; void (*rom_clock_set)(uint32_t rcc) = func_table[23]; - /* disable interrupts */ + /* Disable interrupts. */ asm volatile("cpsid i"); switch (clock) { @@ -219,18 +378,20 @@ static int command_sleep(int argc, char **argv) break; } - /* TODO: move this to the UART module; ugly to have - UARTisms here. Also note this only fixes UART0, - not UART1. */ + /* + * TODO: move this to the UART module; ugly to have + * UARTisms here. Also note this only fixes UART0, + * not UART1. + */ if (uartfbrd) { - /* Disable the port via UARTCTL and add HSE */ + /* Disable the port via UARTCTL and add HSE. */ LM4_UART_CTL(0) = 0x0320; - /* Set the baud rate divisor */ + /* Set the baud rate divisor. */ LM4_UART_IBRD(0) = uartibrd; LM4_UART_FBRD(0) = uartfbrd; /* Poke UARTLCRH to make the new divisor take effect. */ LM4_UART_LCRH(0) = LM4_UART_LCRH(0); - /* Enable the port */ + /* Enable the port. */ LM4_UART_CTL(0) |= 0x0001; } asm volatile("cpsie i"); @@ -241,27 +402,50 @@ static int command_sleep(int argc, char **argv) cflush(); } + /* Enable interrupts. */ asm volatile("cpsid i"); /* gate peripheral clocks */ if (level & 1) { + clock_disable_peripheral(CGC_OFFSET_WD, 0xffffffff, + CGC_MODE_ALL); clock_disable_peripheral(CGC_OFFSET_TIMER, 0xffffffff, CGC_MODE_ALL); clock_disable_peripheral(CGC_OFFSET_GPIO, 0xffffffff, CGC_MODE_ALL); clock_disable_peripheral(CGC_OFFSET_DMA, 0xffffffff, CGC_MODE_ALL); + clock_disable_peripheral(CGC_OFFSET_HIB, 0xffffffff, + CGC_MODE_ALL); clock_disable_peripheral(CGC_OFFSET_UART, 0xffffffff, CGC_MODE_ALL); + clock_disable_peripheral(CGC_OFFSET_SSI, 0xffffffff, + CGC_MODE_ALL); + clock_disable_peripheral(CGC_OFFSET_I2C, 0xffffffff, + CGC_MODE_ALL); + clock_disable_peripheral(CGC_OFFSET_ADC, 0xffffffff, + CGC_MODE_ALL); clock_disable_peripheral(CGC_OFFSET_LPC, 0xffffffff, CGC_MODE_ALL); + clock_disable_peripheral(CGC_OFFSET_PECI, 0xffffffff, + CGC_MODE_ALL); + clock_disable_peripheral(CGC_OFFSET_FAN, 0xffffffff, + CGC_MODE_ALL); + clock_disable_peripheral(CGC_OFFSET_EEPROM, 0xffffffff, + CGC_MODE_ALL); clock_disable_peripheral(CGC_OFFSET_WTIMER, 0xffffffff, CGC_MODE_ALL); } - /* set deep sleep bit */ + + /* Set deep sleep bit. */ if (level >= 4) CPU_SCB_SYSCTRL |= 0x4; - /* go to low power mode (forever ...) */ + + /* Set SRAM and flash PM for sleep and deep sleep. */ + LM4_SYSTEM_SLPPWRCFG = (flash_pm << 4) | sram_pm; + LM4_SYSTEM_DSLPPWRCFG = (flash_pm << 4) | sram_pm; + + /* Go to low power mode (forever ...) */ if (level > 1) while (1) { asm("wfi"); @@ -274,7 +458,7 @@ static int command_sleep(int argc, char **argv) return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(sleep, command_sleep, - "[level [clock]]", + "[level [clock] [sram pm] [flash pm]]", "Drop into sleep", NULL); #endif /* CONFIG_CMD_SLEEP */ @@ -409,3 +593,27 @@ DECLARE_CONSOLE_COMMAND(clockgates, command_clock_gating, NULL); #endif /* CONFIG_CMD_CLOCKGATES */ +#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("Time spent in deep-sleep: %.6lds\n", + idle_dsleep_time_us); + ccprintf("Total time on: %.6lds\n", ts.val); + ccprintf("Deep-sleep closest to wake deadline: %dus\n", + dsleep_recovery_margin_us); + + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(idlestats, command_idle_stats, + "", + "Print last idle stats", + NULL); +#endif /* CONFIG_LOW_POWER_IDLE */ + diff --git a/chip/lm4/config_chip.h b/chip/lm4/config_chip.h index 1ade3365f4..a534bb7a57 100644 --- a/chip/lm4/config_chip.h +++ b/chip/lm4/config_chip.h @@ -25,6 +25,12 @@ /* Number of I2C ports */ #define I2C_PORT_COUNT 6 +/* + * Time it takes to set the RTC match register. This value is conservatively + * set based on measurements around 200us. + */ +#define HIB_SET_RTC_MATCH_DELAY_USEC 300 + /****************************************************************************/ /* Memory mapping */ @@ -35,7 +41,7 @@ #define CONFIG_STACK_SIZE 4096 /* non-standard task stack sizes */ -#define IDLE_TASK_STACK_SIZE 384 +#define IDLE_TASK_STACK_SIZE 512 #define LARGER_TASK_STACK_SIZE 640 /* Default task stack size */ diff --git a/chip/lm4/registers.h b/chip/lm4/registers.h index 9450afea39..ac25724197 100644 --- a/chip/lm4/registers.h +++ b/chip/lm4/registers.h @@ -227,6 +227,7 @@ static inline int lm4_fan_addr(int ch, int offset) #define LM4_SYSTEM_MISC REG32(0x400fe058) #define LM4_SYSTEM_RESC REG32(0x400fe05c) #define LM4_SYSTEM_RCC REG32(0x400fe060) +#define LM4_SYSTEM_RCC_ACG (1 << 27) #define LM4_SYSTEM_RCC_SYSDIV(x) (((x) & 0xf) << 23) #define LM4_SYSTEM_RCC_USESYSDIV (1 << 22) #define LM4_SYSTEM_RCC_PWRDN (1 << 13) @@ -244,9 +245,17 @@ static inline int lm4_fan_addr(int ch, int offset) #define LM4_SYSTEM_RCC2_BYPASS2 (1 << 11) #define LM4_SYSTEM_RCC2_OSCSRC2(x) (((x) & 0x7) << 4) #define LM4_SYSTEM_MOSCCTL REG32(0x400fe07c) +#define LM4_SYSTEM_DSLPCLKCFG REG32(0x400fe144) #define LM4_SYSTEM_PIOSCCAL REG32(0x400fe150) #define LM4_SYSTEM_PIOSCSTAT REG32(0x400fe154) #define LM4_SYSTEM_PLLSTAT REG32(0x400fe168) +#define LM4_SYSTEM_SLPPWRCFG REG32(0x400fe188) +#define LM4_SYSTEM_DSLPPWRCFG REG32(0x400fe18c) +#define LM4_SYSTEM_LDOSPCTL REG32(0x400fe1b4) +#define LM4_SYSTEM_LDOSPCAL REG32(0x400fe1b8) +#define LM4_SYSTEM_LDODPCTL REG32(0x400fe1bc) +#define LM4_SYSTEM_LDODPCAL REG32(0x400fe1c0) +#define LM4_SYSTEM_SPDMST REG32(0x400fe1cc) #define LM4_SYSTEM_BOOTCFG REG32(0x400fe1d0) #define LM4_SYSTEM_BOOTCFG_MASK 0x7fff00ec /* Reserved bits of BOOTCFG reg */ /* Note: USER_REG3 is used to hold pre-programming process data and should not 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; diff --git a/chip/lm4/uart.c b/chip/lm4/uart.c index 13fad41be6..ad9c634947 100644 --- a/chip/lm4/uart.c +++ b/chip/lm4/uart.c @@ -11,6 +11,7 @@ #include "gpio.h" #include "lpc.h" #include "registers.h" +#include "system.h" #include "task.h" #include "uart.h" #include "util.h" @@ -32,6 +33,9 @@ void uart_tx_start(void) if (LM4_UART_IM(0) & 0x20) 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,6 +49,9 @@ void uart_tx_start(void) void uart_tx_stop(void) { LM4_UART_IM(0) &= ~0x20; + + /* Re-allow deep sleep */ + enable_sleep(SLEEP_MASK_UART); } void uart_tx_flush(void) @@ -182,14 +189,19 @@ void uart_init(void) { uint32_t mask = 0; - /* Enable UART0 and Host UART in run, sleep, and deep sleep modes. */ + /* + * Enable UART0 in run, sleep, and deep sleep modes. Enable the Host + * UART in run and sleep modes. + */ mask |= 1; + clock_enable_peripheral(CGC_OFFSET_UART, mask, CGC_MODE_DSLEEP); #ifdef CONFIG_UART_HOST mask |= (1 << CONFIG_UART_HOST); #endif - clock_enable_peripheral(CGC_OFFSET_UART, mask, CGC_MODE_ALL); + clock_enable_peripheral(CGC_OFFSET_UART, mask, + CGC_MODE_RUN | CGC_MODE_SLEEP); gpio_config_module(MODULE_UART, 1); diff --git a/chip/stm32/clock-stm32f.c b/chip/stm32/clock-stm32f.c index e878d7856d..23db9f1f44 100644 --- a/chip/stm32/clock-stm32f.c +++ b/chip/stm32/clock-stm32f.c @@ -35,19 +35,6 @@ #define RTC_FREQ 40000 /* Hz */ #define US_PER_RTC_TICK (1000000 / RTC_FREQ) -/* On-going actions preventing to go into deep-sleep mode */ -static uint32_t sleep_mask; - -void enable_sleep(uint32_t mask) -{ - atomic_clear(&sleep_mask, mask); -} - -void disable_sleep(uint32_t mask) -{ - atomic_or(&sleep_mask, mask); -} - static void wait_rtc_ready(void) { /* wait for Registers Synchronized Flag */ @@ -298,27 +285,4 @@ void clock_init(void) task_enable_irq(STM32_IRQ_RTC_ALARM); } -/*****************************************************************************/ -/* Console commands */ -static int command_sleepmask(int argc, char **argv) -{ - int off; - - if (argc >= 2) { - off = strtoi(argv[1], NULL, 10); - - if (off) - disable_sleep(SLEEP_MASK_FORCE); - else - enable_sleep(SLEEP_MASK_FORCE); - } - - ccprintf("sleep mask: %08x\n", sleep_mask); - - return EC_SUCCESS; -} -DECLARE_CONSOLE_COMMAND(sleepmask, command_sleepmask, - "[0|1]", - "Display/force sleep mack", - NULL); diff --git a/chip/stm32/clock-stm32l.c b/chip/stm32/clock-stm32l.c index 09b8f00704..3cb09f69bf 100644 --- a/chip/stm32/clock-stm32l.c +++ b/chip/stm32/clock-stm32l.c @@ -33,16 +33,6 @@ enum clock_osc { static int freq; static int current_osc; -void enable_sleep(uint32_t mask) -{ - /* low power mode not implemented */ -} - -void disable_sleep(uint32_t mask) -{ - /* low power mode not implemented */ -} - int clock_get_freq(void) { return freq; diff --git a/chip/stm32/uart.c b/chip/stm32/uart.c index 78bbcc3e58..f2650591ed 100644 --- a/chip/stm32/uart.c +++ b/chip/stm32/uart.c @@ -11,6 +11,7 @@ #include "gpio.h" #include "hooks.h" #include "registers.h" +#include "system.h" #include "task.h" #include "uart.h" #include "util.h" diff --git a/common/chipset_haswell.c b/common/chipset_haswell.c index b92bbbf5ce..6bc4b3283d 100644 --- a/common/chipset_haswell.c +++ b/common/chipset_haswell.c @@ -282,6 +282,12 @@ enum x86_state x86_handle_state(enum x86_state state) /* Call hooks now that rails are up */ hook_notify(HOOK_CHIPSET_RESUME); + /* + * Disable idle task deep sleep. This means that the low + * power idle task will not go into deep sleep while in S0. + */ + disable_sleep(SLEEP_MASK_AP_RUN); + /* Wait 99ms after all voltages good */ msleep(99); @@ -314,6 +320,12 @@ enum x86_state x86_handle_state(enum x86_state state) wireless_enable(0); /* + * Enable idle task deep sleep. Allow the low power idle task + * to go into deep sleep in S3 or lower. + */ + enable_sleep(SLEEP_MASK_AP_RUN); + + /* * Deassert prochot since CPU is off and we're about to drop * +VCCP. */ diff --git a/common/console_output.c b/common/console_output.c index 7f70651f04..bad734d67a 100644 --- a/common/console_output.c +++ b/common/console_output.c @@ -22,6 +22,7 @@ static const char * const channel_names[] = { "command", "charger", "chipset", + "clock", "dma", "events", "gpio", diff --git a/common/i2c_common.c b/common/i2c_common.c index a291d5caa4..dcbc4fb73d 100644 --- a/common/i2c_common.c +++ b/common/i2c_common.c @@ -22,17 +22,15 @@ static struct mutex port_mutex[I2C_PORT_COUNT]; void i2c_lock(int port, int lock) { if (lock) { -#ifdef CHIP_stm32 /* Don't allow deep sleep when I2C port is locked */ disable_sleep(SLEEP_MASK_I2C); -#endif + mutex_lock(port_mutex + port); } else { mutex_unlock(port_mutex + port); -#ifdef CHIP_stm32 + /* Allow deep sleep again after I2C port is unlocked */ enable_sleep(SLEEP_MASK_I2C); -#endif } } diff --git a/common/system_common.c b/common/system_common.c index 55ea35d20d..81182ef987 100644 --- a/common/system_common.c +++ b/common/system_common.c @@ -4,7 +4,6 @@ */ /* System module for Chrome EC : common functions */ - #include "clock.h" #include "common.h" #include "console.h" @@ -82,6 +81,9 @@ static int disable_jump; /* Disable ALL jumps if system is locked */ static int force_locked; /* Force system locked even if WP isn't enabled */ static enum ec_reboot_cmd reboot_at_shutdown; +/* On-going actions preventing going into deep-sleep mode */ +uint32_t sleep_mask; + int system_is_locked(void) { if (force_locked) @@ -770,6 +772,34 @@ DECLARE_CONSOLE_COMMAND(syslock, command_system_lock, "Lock the system, even if WP is disabled", NULL); +#ifdef CONFIG_LOW_POWER_IDLE +/** + * Modify and print the sleep mask which controls access to deep sleep + * mode in the idle task. + */ +static int command_sleepmask(int argc, char **argv) +{ + int off; + + if (argc >= 2) { + off = strtoi(argv[1], NULL, 10); + + if (off) + disable_sleep(SLEEP_MASK_FORCE); + else + enable_sleep(SLEEP_MASK_FORCE); + } + + ccprintf("sleep mask: %08x\n", sleep_mask); + + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(sleepmask, command_sleepmask, + "[<sleep_mask>]", + "Display/force sleep mask", + NULL); +#endif + /*****************************************************************************/ /* Host commands */ diff --git a/include/clock.h b/include/clock.h index 343b43ecee..875be91006 100644 --- a/include/clock.h +++ b/include/clock.h @@ -55,18 +55,6 @@ void clock_enable_pll(int enable, int notify); */ void clock_wait_cycles(uint32_t cycles); -/* Low power modes for idle API */ - -enum { - SLEEP_MASK_AP_RUN = (1 << 0), /* the main CPU is running */ - SLEEP_MASK_UART = (1 << 1), /* UART communication on-going */ - SLEEP_MASK_I2C = (1 << 2), /* I2C master communication on-going */ - SLEEP_MASK_CHARGING = (1 << 3), /* Charging loop on-going */ - SLEEP_MASK_USB_PWR = (1 << 4), /* USB power loop on-going */ - - SLEEP_MASK_FORCE = (1 << 31), /* Force disabling low power modes */ -}; - /* Clock gate control modes for clock_enable_peripheral() */ #define CGC_MODE_RUN (1 << 0) #define CGC_MODE_SLEEP (1 << 1) @@ -93,7 +81,4 @@ void clock_enable_peripheral(uint32_t offset, uint32_t mask, uint32_t mode); */ void clock_disable_peripheral(uint32_t offset, uint32_t mask, uint32_t mode); -void enable_sleep(uint32_t mask); -void disable_sleep(uint32_t mask); - #endif /* __CROS_EC_CLOCK_H */ diff --git a/include/config.h b/include/config.h index 72dffe49c2..e44a97e96c 100644 --- a/include/config.h +++ b/include/config.h @@ -214,6 +214,7 @@ #undef CONFIG_CMD_POWERLED #undef CONFIG_CMD_SCRATCHPAD #undef CONFIG_CMD_SLEEP +#undef CONFIG_CMD_RTC_ALARM /*****************************************************************************/ diff --git a/include/console.h b/include/console.h index 623d5a5551..1dd37e42c9 100644 --- a/include/console.h +++ b/include/console.h @@ -30,6 +30,7 @@ enum console_channel { * inside a console command routine. */ CC_CHARGER, CC_CHIPSET, + CC_CLOCK, CC_DMA, CC_EVENTS, CC_GPIO, diff --git a/include/system.h b/include/system.h index 26e61932c0..0c2936a33c 100644 --- a/include/system.h +++ b/include/system.h @@ -8,7 +8,9 @@ #ifndef __CROS_EC_SYSTEM_H #define __CROS_EC_SYSTEM_H +#include "atomic.h" #include "common.h" +#include "timer.h" /* Reset causes */ #define RESET_FLAG_OTHER (1 << 0) /* Other known reason */ @@ -268,4 +270,72 @@ void system_hibernate(uint32_t seconds, uint32_t microseconds); int system_get_console_force_enabled(void); int system_set_console_force_enabled(int enabled); +/** + * Read the real-time clock. + * + * @return The real-time clock value as a timestamp. + */ +timestamp_t system_get_rtc(void); + +/** + * Enable hibernate interrupt + */ +void system_enable_hib_interrupt(void); + +/* Low power modes for idle API */ +enum { + SLEEP_MASK_AP_RUN = (1 << 0), /* the main CPU is running */ + SLEEP_MASK_UART = (1 << 1), /* UART communication on-going */ + SLEEP_MASK_I2C = (1 << 2), /* I2C master communication on-going */ + SLEEP_MASK_CHARGING = (1 << 3), /* Charging loop on-going */ + SLEEP_MASK_USB_PWR = (1 << 4), /* USB power loop on-going */ + + SLEEP_MASK_FORCE = (1 << 31), /* Force disabling low power modes */ +}; + +/* + * Current sleep mask. You may read from this variable, but must NOT + * modify it; use enable_sleep() or disable_sleep() to do that. + */ +extern uint32_t sleep_mask; + +/** + * Enable low power sleep mask. For low power sleep to take affect, all masks + * in the sleep mask enum above must be enabled. + * + * @param Sleep mask to enable. + */ +static inline void enable_sleep(uint32_t mask) +{ + atomic_clear(&sleep_mask, mask); +} + +/** + * Disable low power sleep mask. For low power sleep to take affect, all masks + * in the sleep mask enum above must be enabled. + * + * @param Sleep mask to enable. + */ +static inline void disable_sleep(uint32_t mask) +{ + atomic_or(&sleep_mask, mask); +} + +/** + * Use hibernate module to set up an RTC interrupt at a given + * time from now + * + * Note: If time given is less than HIB_SET_RTC_MATCH_DELAY_USEC, then it will + * set the interrupt at exactly HIB_SET_RTC_MATCH_DELAY_USEC. + * + * @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); + +/** + * Disable and clear the RTC interrupt. + */ +void system_reset_rtc_alarm(void); + #endif /* __CROS_EC_SYSTEM_H */ |