blob: 6fc2bd29f84b3f5a18d5ac5c332a304a429009f7 [file] [log] [blame]
/* Copyright (c) 2012 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 <stdint.h>
#include "atomic.h"
#include "board.h"
#include "clock.h"
#include "common.h"
#include "console.h"
#include "cpu.h"
#include "hwtimer.h"
#include "registers.h"
#include "system.h"
#include "task.h"
#include "timer.h"
#include "util.h"
/* Allow serial console to wake up the EC from STOP mode */
/* #define CONFIG_FORCE_CONSOLE_RESUME */
/**
* minimum delay to enter stop mode
* STOP mode wakeup time with regulator in low power mode is 5 us.
* PLL locking time is 200us.
*/
#define STOP_MODE_LATENCY 300 /* us */
/**
* RTC clock frequency (connected to LSI clock)
*
* TODO: crosbug.com/p/12281 calibrate LSI frequency
*/
#define RTC_FREQ 40000 /* Hz */
#define US_PER_RTC_TICK (1000000 / RTC_FREQ)
/* On-going actions preventing to go into deep-sleep mode */
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 */
while (!(STM32_RTC_CRL & (1 << 3)))
;
}
static void prepare_rtc_write(void)
{
/* wait for RTOFF */
while (!(STM32_RTC_CRL & (1 << 5)))
;
/* set CNF bit */
STM32_RTC_CRL |= (1 << 4);
}
static void finalize_rtc_write(void)
{
/* reset CNF bit */
STM32_RTC_CRL &= ~(1 << 4);
/* wait for RTOFF */
while (!(STM32_RTC_CRL & (1 << 5)))
;
}
uint32_t set_rtc_alarm(unsigned delay_us)
{
unsigned rtc_t0, rtc_t1;
rtc_t0 = ((uint32_t)STM32_RTC_CNTH << 16) | STM32_RTC_CNTL;
rtc_t1 = rtc_t0 + delay_us / US_PER_RTC_TICK;
prepare_rtc_write();
/* set RTC alarm timestamp (using the 40kHz counter ) */
STM32_RTC_ALRH = rtc_t1 >> 16;
STM32_RTC_ALRL = rtc_t1 & 0xffff;
/* clear RTC alarm */
STM32_RTC_CRL &= ~2;
/* enable RTC alarm interrupt */
STM32_RTC_CRL |= 2;
finalize_rtc_write();
/* remove synchro flag */
STM32_RTC_CRL &= ~(1 << 3);
return rtc_t0;
}
uint32_t reset_rtc_alarm(void)
{
uint32_t rtc_stamp;
wait_rtc_ready();
prepare_rtc_write();
/* clear RTC alarm */
STM32_RTC_CRL &= ~2;
finalize_rtc_write();
STM32_EXTI_PR = (1 << 17);
rtc_stamp = ((uint32_t)STM32_RTC_CNTH << 16) | STM32_RTC_CNTL;
return rtc_stamp;
}
static void __rtc_wakeup_irq(void)
{
reset_rtc_alarm();
}
DECLARE_IRQ(STM32_IRQ_RTC_WAKEUP, __rtc_wakeup_irq, 1);
static void __rtc_alarm_irq(void)
{
reset_rtc_alarm();
}
DECLARE_IRQ(STM32_IRQ_RTC_ALARM, __rtc_alarm_irq, 1);
static void config_hispeed_clock(void)
{
/* 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)))
;
}
/*
* stays on HSI (8MHz), no prescaler, PLLSRC = HSI/2, PLLMUL = x4
* no MCO => PLLCLK = 16 Mhz
*/
BUILD_ASSERT(CPU_CLOCK == 16000000);
STM32_RCC_CFGR = 0x00080000;
/* Enable the PLL */
STM32_RCC_CR |= 1 << 24;
/* Wait for the PLL to lock */
while (!(STM32_RCC_CR & (1 << 25)))
;
/* switch to SYSCLK to the PLL */
STM32_RCC_CFGR = 0x00080002;
/* wait until the PLL is the clock source */
while ((STM32_RCC_CFGR & 0xc) != 0x8)
;
}
#ifdef CONFIG_LOW_POWER_IDLE
#ifdef CONFIG_FORCE_CONSOLE_RESUME
static void enable_serial_wakeup(int enable)
{
static uint32_t save_exticr;
if (enable) {
/**
* allow to wake up from serial port (RX on pin PA10)
* by setting it as a GPIO with an external interrupt.
*/
save_exticr = STM32_AFIO_EXTICR(10 / 4);
STM32_AFIO_EXTICR(10 / 4) = (save_exticr & ~(0xf << 8));
} else {
/* serial port wake up : don't go back to sleep */
if (STM32_EXTI_PR & (1 << 10))
disable_sleep(SLEEP_MASK_FORCE);
/* restore keyboard external IT on PC10 */
STM32_AFIO_EXTICR(10 / 4) = save_exticr;
}
}
#else
static void enable_serial_wakeup(int enable)
{
}
#endif
/* Idle task. Executed when no tasks are ready to be scheduled. */
void __idle(void)
{
timestamp_t t0, t1;
uint32_t next_delay;
uint32_t rtc_t0, rtc_t1;
while (1) {
asm volatile("cpsid i");
t0 = get_time();
next_delay = __hw_clock_event_get() - t0.le.lo;
if (!sleep_mask && (next_delay > STOP_MODE_LATENCY)) {
/* deep-sleep in STOP mode */
enable_serial_wakeup(1);
/* set deep sleep bit */
CPU_SCB_SYSCTRL |= 0x4;
rtc_t0 = set_rtc_alarm(next_delay - STOP_MODE_LATENCY);
asm("wfi");
CPU_SCB_SYSCTRL &= ~0x4;
enable_serial_wakeup(0);
/* re-lock the PLL */
config_hispeed_clock();
/* fast forward timer according to RTC counter */
rtc_t1 = reset_rtc_alarm();
t1.val = t0.val + (rtc_t1 - rtc_t0) * US_PER_RTC_TICK;
force_time(t1);
} else {
/* normal idle : only CPU clock stopped */
asm("wfi");
}
asm volatile("cpsie i");
}
}
#endif /* CONFIG_LOW_POWER_IDLE */
int clock_init(void)
{
/*
* The initial state :
* SYSCLK from HSI (=8MHz), no divider on AHB, APB1, APB2
* PLL unlocked, RTC enabled on LSE
*/
config_hispeed_clock();
/* configure RTC clock */
wait_rtc_ready();
prepare_rtc_write();
/* set RTC divider to /1 */
STM32_RTC_PRLH = 0;
STM32_RTC_PRLL = 0;
finalize_rtc_write();
/* setup RTC EXTINT17 to wake up us from STOP mode */
STM32_EXTI_IMR |= (1 << 17);
STM32_EXTI_RTSR |= (1 << 17);
/*
* Our deep sleep mode is STOP mode.
* clear PDDS (stop mode) , set LDDS (regulator in low power mode)
*/
STM32_PWR_CR = (STM32_PWR_CR & ~2) | 1;
/* Enable RTC interrupts */
task_enable_irq(STM32_IRQ_RTC_WAKEUP);
task_enable_irq(STM32_IRQ_RTC_ALARM);
return EC_SUCCESS;
}
/*****************************************************************************/
/* 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);