blob: 8940577f94c6bb6226082f0e7d86d1fac62b525f [file] [log] [blame]
/* Copyright (c) 2014 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 "chipset.h"
#include "clock.h"
#include "common.h"
#include "console.h"
#include "cpu.h"
#include "hooks.h"
#include "hwtimer.h"
#include "registers.h"
#include "system.h"
#include "task.h"
#include "timer.h"
#include "util.h"
/* Console output macros */
#define CPUTS(outstr) cputs(CC_CLOCK, outstr)
#define CPRINTS(format, args...) cprints(CC_CLOCK, format, ## args)
/* use 48Mhz USB-synchronized High-speed oscillator */
#define HSI48_CLOCK 48000000
/* use PLL at 38.4MHz as system clock. */
#define PLL_CLOCK 38400000
/* 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;
/*
* minimum delay to enter stop mode
* STOP_MODE_LATENCY: max time to wake up from STOP mode with regulator in low
* power mode is 5 us + PLL locking time is 200us.
* SET_RTC_MATCH_DELAY: max time to set RTC match alarm. if we set the alarm
* in the past, it will never wake up and cause a watchdog.
* For STM32F3, we are using HSE, which requires additional time to start up.
* Therefore, the latency for STM32F3 is set longer.
*/
#ifdef CHIP_VARIANT_STM32F373
#define STOP_MODE_LATENCY 500 /* us */
#elif defined(CHIP_VARIANT_STM32F05X)
#define STOP_MODE_LATENCY 300 /* us */
#elif (CPU_CLOCK == PLL_CLOCK)
#define STOP_MODE_LATENCY 300 /* us */
#else
#define STOP_MODE_LATENCY 50 /* us */
#endif
#define SET_RTC_MATCH_DELAY 200 /* us */
#endif /* CONFIG_LOW_POWER_IDLE */
/*
* RTC clock frequency (connected to LSI clock)
*
* TODO(crosbug.com/p/12281): Calibrate LSI frequency on a per-chip basis. The
* LSI on any given chip can be between 30 kHz to 60 kHz. Without calibration,
* LSI frequency may be off by as much as 50%. Fortunately, we don't do any
* high-precision delays based solely on LSI.
*/
/*
* Set synchronous clock freq to LSI/2 (20kHz) to maximize subsecond
* resolution. Set asynchronous clock to 1 Hz.
*/
#define RTC_FREQ (40000 / 2) /* Hz */
#define RTC_PREDIV_S (RTC_FREQ - 1)
#define RTC_PREDIV_A 1
#define US_PER_RTC_TICK (1000000 / RTC_FREQ)
/* Lock and unlock RTC write access */
static inline void rtc_lock_regs(void)
{
STM32_RTC_WPR = 0xff;
}
static inline void rtc_unlock_regs(void)
{
STM32_RTC_WPR = 0xca;
STM32_RTC_WPR = 0x53;
}
/* Convert between RTC regs in BCD and seconds */
static inline uint32_t rtc_to_sec(uint32_t rtc)
{
uint32_t sec;
/* convert the hours field */
sec = (((rtc & 0x300000) >> 20) * 10 + ((rtc & 0xf0000) >> 16)) * 3600;
/* convert the minutes field */
sec += (((rtc & 0x7000) >> 12) * 10 + ((rtc & 0xf00) >> 8)) * 60;
/* convert the seconds field */
sec += ((rtc & 0x70) >> 4) * 10 + (rtc & 0xf);
return sec;
}
static inline uint32_t sec_to_rtc(uint32_t sec)
{
uint32_t rtc;
/* convert the hours field */
rtc = ((sec / 36000) << 20) | (((sec / 3600) % 10) << 16);
/* convert the minutes field */
rtc |= (((sec % 3600) / 600) << 12) | (((sec % 600) / 60) << 8);
/* convert the seconds field */
rtc |= (((sec % 60) / 10) << 4) | (sec % 10);
return rtc;
}
/* Return time diff between two rtc readings */
int32_t get_rtc_diff(uint32_t rtc0, uint32_t rtc0ss,
uint32_t rtc1, uint32_t rtc1ss)
{
int32_t diff;
/* Note: this only looks at the diff mod 10 seconds */
diff = ((rtc1 & 0xf) * SECOND +
(RTC_PREDIV_S - rtc1ss) * US_PER_RTC_TICK) -
((rtc0 & 0xf) * SECOND +
(RTC_PREDIV_S - rtc0ss) * US_PER_RTC_TICK);
return (diff < 0) ? (diff + 10*SECOND) : diff;
}
static inline void rtc_read(uint32_t *rtc, uint32_t *rtcss)
{
/* Read current time synchronously */
do {
*rtc = STM32_RTC_TR;
/*
* RTC_SSR must be read twice with identical values because
* glitches may occur for reads close to the RTCCLK edge.
*/
do {
*rtcss = STM32_RTC_SSR;
} while (*rtcss != STM32_RTC_SSR);
} while (*rtc != STM32_RTC_TR);
}
void set_rtc_alarm(uint32_t delay_s, uint32_t delay_us,
uint32_t *rtc, uint32_t *rtcss)
{
uint32_t alarm_sec, alarm_us;
/* Alarm must be within 1 day (86400 seconds) */
ASSERT((delay_s + delay_us / SECOND) < 86400);
rtc_unlock_regs();
/* Make sure alarm is disabled */
STM32_RTC_CR &= ~STM32_RTC_CR_ALRAE;
while (!(STM32_RTC_ISR & STM32_RTC_ISR_ALRAWF))
;
STM32_RTC_ISR &= ~STM32_RTC_ISR_ALRAF;
rtc_read(rtc, rtcss);
/* Calculate alarm time */
alarm_sec = rtc_to_sec(*rtc) + delay_s;
alarm_us = (RTC_PREDIV_S - *rtcss) * US_PER_RTC_TICK + delay_us;
alarm_sec = alarm_sec + alarm_us / SECOND;
alarm_us = alarm_us % 1000000;
/*
* If seconds is greater than 1 day, subtract by 1 day to deal with
* 24-hour rollover.
*/
if (alarm_sec >= 86400)
alarm_sec -= 86400;
/* Set alarm time */
STM32_RTC_ALRMAR = sec_to_rtc(alarm_sec);
STM32_RTC_ALRMASSR = RTC_PREDIV_S - (alarm_us / US_PER_RTC_TICK);
/* Check for match on hours, minutes, seconds, and subsecond */
STM32_RTC_ALRMAR |= 0xc0000000;
STM32_RTC_ALRMASSR |= 0x0f000000;
/* Enable alarm and alarm interrupt */
STM32_EXTI_PR = EXTI_RTC_ALR_EVENT;
STM32_EXTI_IMR |= EXTI_RTC_ALR_EVENT;
STM32_RTC_CR |= STM32_RTC_CR_ALRAE;
rtc_lock_regs();
}
void reset_rtc_alarm(uint32_t *rtc, uint32_t *rtcss)
{
rtc_unlock_regs();
/* Disable alarm */
STM32_RTC_CR &= ~STM32_RTC_CR_ALRAE;
STM32_RTC_ISR &= ~STM32_RTC_ISR_ALRAF;
/* Disable RTC alarm interrupt */
STM32_EXTI_IMR &= ~EXTI_RTC_ALR_EVENT;
STM32_EXTI_PR = EXTI_RTC_ALR_EVENT;
/* Read current time */
rtc_read(rtc, rtcss);
rtc_lock_regs();
}
void __rtc_alarm_irq(void)
{
uint32_t rtc, rtcss;
reset_rtc_alarm(&rtc, &rtcss);
}
DECLARE_IRQ(STM32_IRQ_RTC_ALARM, __rtc_alarm_irq, 1);
static void config_hispeed_clock(void)
{
#ifdef CHIP_FAMILY_STM32F3
/* Ensure that HSE is ON */
if (!(STM32_RCC_CR & (1 << 17))) {
/* Enable HSE */
STM32_RCC_CR |= 1 << 16;
/* Wait for HSE to be ready */
while (!(STM32_RCC_CR & (1 << 17)))
;
}
/*
* HSE = 24MHz, no prescalar, no MCO, with PLL *2 => 48MHz SYSCLK
* HCLK = SYSCLK, PCLK = HCLK / 2 = 24MHz
* ADCCLK = PCLK / 6 = 4MHz
* USB uses SYSCLK = 48MHz
*/
STM32_RCC_CFGR = 0x0041a400;
/* Enable the PLL */
STM32_RCC_CR |= 0x01000000;
/* Wait until the PLL is ready */
while (!(STM32_RCC_CR & 0x02000000))
;
/* Switch SYSCLK to PLL */
STM32_RCC_CFGR |= 0x2;
/* Wait until the PLL is the clock source */
while ((STM32_RCC_CFGR & 0xc) != 0x8)
;
#elif defined(CHIP_VARIANT_STM32F05X)
/* If PLL is the clock source, PLL has already been set up. */
if ((STM32_RCC_CFGR & 0xc) == 0x8)
return;
/* Ensure that HSI is ON */
if (!(STM32_RCC_CR & (1<<1))) {
/* Enable HSI */
STM32_RCC_CR |= (1<<0);
/* Wait for HSI to be ready */
while (!(STM32_RCC_CR & (1<<1)))
;
}
/*
* HSI = 8MHz, HSI/2 with PLL *12 = ~48 MHz
* therefore PCLK = FCLK = SYSCLK = 48MHz
*/
/* Switch the PLL source to HSI/2 */
STM32_RCC_CFGR &= ~(0x00018000);
/*
* Specify HSI/2 clock as input clock to PLL and set PLL (*12).
*/
STM32_RCC_CFGR |= 0x00280000;
/* Enable the PLL. */
STM32_RCC_CR |= 0x01000000;
/* Wait until PLL is ready. */
while (!(STM32_RCC_CR & 0x02000000))
;
/* Switch SYSCLK to PLL. */
STM32_RCC_CFGR |= 0x2;
/* wait until the PLL is the clock source */
while ((STM32_RCC_CFGR & 0xc) != 0x8)
;
#else
/* Ensure that HSI48 is ON */
if (!(STM32_RCC_CR2 & (1 << 17))) {
/* Enable HSI */
STM32_RCC_CR2 |= 1 << 16;
/* Wait for HSI to be ready */
while (!(STM32_RCC_CR2 & (1 << 17)))
;
}
#if (CPU_CLOCK == HSI48_CLOCK)
/*
* HSI48 = 48MHz, no prescaler, no MCO, no PLL
* therefore PCLK = FCLK = SYSCLK = 48MHz
* USB uses HSI48 = 48MHz
*/
/* switch SYSCLK to HSI48 */
STM32_RCC_CFGR = 0x00000003;
/* wait until the HSI48 is the clock source */
while ((STM32_RCC_CFGR & 0xc) != 0xc)
;
#elif (CPU_CLOCK == PLL_CLOCK)
/*
* HSI48 = 48MHz, no prescalar, no MCO, with PLL *4/5 => 38.4MHz SYSCLK
* therefore PCLK = FCLK = SYSCLK = 38.4MHz
* USB uses HSI48 = 48MHz
*/
/* If PLL is the clock source, PLL has already been set up. */
if ((STM32_RCC_CFGR & 0xc) == 0x8)
return;
/*
* Specify HSI48 clock as input clock to PLL and set PLL multiplier
* and divider.
*/
STM32_RCC_CFGR = 0x00098000;
STM32_RCC_CFGR2 = 0x4;
/* Enable the PLL. */
STM32_RCC_CR |= 0x01000000;
/* Wait until PLL is ready. */
while (!(STM32_RCC_CR & 0x02000000))
;
/* Switch SYSCLK to PLL. */
STM32_RCC_CFGR |= 0x2;
/* wait until the PLL is the clock source */
while ((STM32_RCC_CFGR & 0xc) != 0x8)
;
#else
#error "CPU_CLOCK must be either 48MHz or 38.4MHz"
#endif
#endif
}
#ifdef CONFIG_HIBERNATE
void __enter_hibernate(uint32_t seconds, uint32_t microseconds)
{
uint32_t rtc, rtcss;
if (seconds || microseconds)
set_rtc_alarm(seconds, microseconds, &rtc, &rtcss);
/* interrupts off now */
asm volatile("cpsid i");
#ifdef CONFIG_HIBERNATE_WAKEUP_PINS
/* enable the wake up pins */
STM32_PWR_CSR |= CONFIG_HIBERNATE_WAKEUP_PINS;
#endif
STM32_PWR_CR |= 0xe;
CPU_SCB_SYSCTRL |= 0x4;
/* go to Standby mode */
asm("wfi");
/* we should never reach that point */
while (1)
;
}
#endif
#ifdef CONFIG_LOW_POWER_IDLE
void clock_refresh_console_in_use(void)
{
}
#ifdef CONFIG_FORCE_CONSOLE_RESUME
#define UARTN_BASE STM32_USART_BASE(CONFIG_UART_CONSOLE)
static void enable_serial_wakeup(int enable)
{
if (enable) {
/*
* Allow UART wake up from STOP mode. Note, UART clock must
* be HSI(8MHz) for wakeup to work.
*/
STM32_USART_CR1(UARTN_BASE) |= STM32_USART_CR1_UESM;
STM32_USART_CR3(UARTN_BASE) |= STM32_USART_CR3_WUFIE;
} else {
/* Disable wake up from STOP mode. */
STM32_USART_CR1(UARTN_BASE) &= ~STM32_USART_CR1_UESM;
}
}
#else
static void enable_serial_wakeup(int enable)
{
}
#endif
void __idle(void)
{
timestamp_t t0;
int next_delay, margin_us, rtc_diff;
uint32_t rtc0, rtc0ss, rtc1, rtc1ss;
while (1) {
asm volatile("cpsid i");
t0 = get_time();
next_delay = __hw_clock_event_get() - t0.le.lo;
if (DEEP_SLEEP_ALLOWED &&
(next_delay > (STOP_MODE_LATENCY + SET_RTC_MATCH_DELAY))) {
/* deep-sleep in STOP mode */
idle_dsleep_cnt++;
enable_serial_wakeup(1);
/* set deep sleep bit */
CPU_SCB_SYSCTRL |= 0x4;
set_rtc_alarm(0, next_delay - STOP_MODE_LATENCY,
&rtc0, &rtc0ss);
asm("wfi");
CPU_SCB_SYSCTRL &= ~0x4;
enable_serial_wakeup(0);
/*
* By default only HSI 8MHz is enabled here. Re-enable
* high-speed clock if in use.
*/
config_hispeed_clock();
/* fast forward timer according to RTC counter */
reset_rtc_alarm(&rtc1, &rtc1ss);
rtc_diff = get_rtc_diff(rtc0, rtc0ss, rtc1, rtc1ss);
t0.val = t0.val + rtc_diff;
force_time(t0);
/* Record time spent in deep sleep. */
idle_dsleep_time_us += rtc_diff;
/* Calculate how close we were to missing deadline */
margin_us = next_delay - rtc_diff;
if (margin_us < 0)
/* Use CPUTS to save stack space */
CPUTS("Idle overslept!\n");
/* 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");
}
asm volatile("cpsie i");
}
}
#endif /* CONFIG_LOW_POWER_IDLE */
int clock_get_freq(void)
{
return CPU_CLOCK;
}
void clock_wait_bus_cycles(enum bus_type bus, uint32_t cycles)
{
volatile uint32_t dummy __attribute__((unused));
if (bus == BUS_AHB) {
while (cycles--)
dummy = STM32_DMA1_REGS->isr;
} else { /* APB */
while (cycles--)
dummy = STM32_USART_BRR(STM32_USART1_BASE);
}
}
void clock_enable_module(enum module_id module, int enable)
{
}
void rtc_init(void)
{
rtc_unlock_regs();
/* Enter RTC initialize mode */
STM32_RTC_ISR |= STM32_RTC_ISR_INIT;
while (!(STM32_RTC_ISR & STM32_RTC_ISR_INITF))
;
/* Set clock prescalars */
STM32_RTC_PRER = (RTC_PREDIV_A << 16) | RTC_PREDIV_S;
/* Start RTC timer */
STM32_RTC_ISR &= ~STM32_RTC_ISR_INIT;
while (STM32_RTC_ISR & STM32_RTC_ISR_INITF)
;
/* Enable RTC alarm interrupt */
STM32_RTC_CR |= STM32_RTC_CR_ALRAIE | STM32_RTC_CR_BYPSHAD;
STM32_EXTI_RTSR |= EXTI_RTC_ALR_EVENT;
task_enable_irq(STM32_IRQ_RTC_ALARM);
rtc_lock_regs();
}
void clock_init(void)
{
/*
* The initial state :
* SYSCLK from HSI (=8MHz), no divider on AHB, APB1, APB2
* PLL unlocked, RTC enabled on LSE
*/
/*
* put 1 Wait-State for flash access to ensure proper reads at 48Mhz
* and enable prefetch buffer.
*/
STM32_FLASH_ACR = STM32_FLASH_ACR_LATENCY | STM32_FLASH_ACR_PRFTEN;
config_hispeed_clock();
rtc_init();
}
/*****************************************************************************/
/* Console commands */
#ifdef CONFIG_CMD_RTC_ALARM
static int command_rtc_alarm_test(int argc, char **argv)
{
int s = 1, us = 0;
uint32_t rtc, rtcss;
char *e;
ccprintf("Setting RTC alarm\n");
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;
}
set_rtc_alarm(s, us, &rtc, &rtcss);
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(rtc_alarm, command_rtc_alarm_test,
"[seconds [microseconds]]",
"Test alarm",
NULL);
#endif /* CONFIG_CMD_RTC_ALARM */
#if defined(CONFIG_LOW_POWER_IDLE) && defined(CONFIG_COMMON_RUNTIME)
#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("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_CMD_IDLE_STATS */
#endif