/* Copyright 2013 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ /* Clocks and power management settings */ #include "clock.h" #include "common.h" #include "console.h" #include "cpu.h" #include "hooks.h" #include "hwtimer.h" #include "pwm.h" #include "pwm_chip.h" #include "registers.h" #include "shared_mem.h" #include "system.h" #include "task.h" #include "timer.h" #include "uart.h" #include "util.h" #include "vboot_hash.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) { asm volatile("1: subs %0, #1\n" " bne 1b\n" : "+r"(cycles)); } int clock_get_freq(void) { return freq; } void clock_init(void) { #ifdef CONFIG_CLOCK_CRYSTAL /* XOSEL: 0 = Parallel resonant crystal */ MEC1322_VBAT_CE &= ~0x1; #else /* XOSEL: 1 = Single ended clock source */ MEC1322_VBAT_CE |= 0x1; #endif /* 32K clock enable */ MEC1322_VBAT_CE |= 0x2; #ifdef CONFIG_CLOCK_CRYSTAL /* Wait for crystal to stabilize (OSC_LOCK == 1) */ while (!(MEC1322_PCR_CHIP_OSC_ID & 0x100)) ; #endif } /** * Speed through boot + vboot hash calculation, dropping our processor clock * only after vboot hashing is completed. */ static void clock_turbo_disable(void); DECLARE_DEFERRED(clock_turbo_disable); static void clock_turbo_disable(void) { #ifdef CONFIG_VBOOT_HASH if (vboot_hash_in_progress()) hook_call_deferred(&clock_turbo_disable_data, 100 * MSEC); else #endif /* Use 12 MHz processor clock for power savings */ MEC1322_PCR_PROC_CLK_CTL = 4; } DECLARE_HOOK(HOOK_INIT, clock_turbo_disable, HOOK_PRIO_INIT_VBOOT_HASH + 1); #ifdef CONFIG_LOW_POWER_IDLE /** * initialization of Hibernation timer */ static void htimer_init(void) { MEC1322_INT_BLK_EN |= BIT(17); MEC1322_INT_ENABLE(17) |= BIT(20); /* GIRQ=17, aggregator bit = 20 */ MEC1322_HTIMER_PRELOAD = 0; /* disable at beginning */ 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/61) usec */ /* (number of counts to be loaded) * = (total microseconds) / 30.5; */ MEC1322_HTIMER_PRELOAD = (seconds * 1000000 + microseconds) * 2 / 61; } } } /** * 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(=61/2)usec per count */ time.le.lo = (uint32_t)(count * 61 / 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) { uint32_t ec_slp_en = MEC1322_PCR_EC_SLP_EN | MEC1322_PCR_EC_SLP_EN_SLEEP; /* sysTick timer */ CPU_NVIC_ST_CTRL &= ~ST_ENABLE; CPU_NVIC_ST_CTRL &= ~ST_COUNTFLAG; /* 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; #ifdef CONFIG_PWM if (pwm_get_keep_awake_mask()) ec_slp_en &= ~pwm_get_keep_awake_mask(); else #endif /* Disable 100 Khz clock */ MEC1322_PCR_SLOW_CLK_CTL &= 0xFFFFFC00; MEC1322_PCR_EC_SLP_EN = ec_slp_en; MEC1322_PCR_HOST_SLP_EN |= MEC1322_PCR_HOST_SLP_EN_SLEEP; MEC1322_PCR_EC_SLP_EN2 |= MEC1322_PCR_EC_SLP_EN2_SLEEP; #ifndef CONFIG_POWER_S0IX MEC1322_LPC_ACT = 0x0; #endif 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; MEC1322_PCR_SLOW_CLK_CTL |= 0x1e0; MEC1322_PCR_CHIP_SLP_EN &= ~0x3; MEC1322_PCR_EC_SLP_EN &= MEC1322_PCR_EC_SLP_EN_WAKE; MEC1322_PCR_HOST_SLP_EN &= MEC1322_PCR_HOST_SLP_EN_WAKE; MEC1322_PCR_EC_SLP_EN2 &= MEC1322_PCR_EC_SLP_EN2_WAKE; MEC1322_PCR_SYS_SLP_CTL = 0xF8; /* default */ #ifndef CONFIG_POWER_S0IX /* Enable LPC */ MEC1322_LPC_ACT |= 1; #endif } 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) */ } #ifdef CONFIG_CMD_IDLE_STATS /** * 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"); #endif /* defined(CONFIG_CMD_IDLE_STATS) */ /** * 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 | 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'."); #endif /* CONFIG_LOW_POWER_IDLE */