485 lines
12 KiB
C
485 lines
12 KiB
C
/* 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 | <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 */
|