blob: d86f1bc5a792dfa161cd22f00431c75c812c4f34 [file] [log] [blame]
/* Copyright (c) 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 |= 1 << 17;
MEC1322_INT_ENABLE(17) |= 1 << 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 | <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'.");
#endif /* CONFIG_LOW_POWER_IDLE */