blob: 6f21e8e0163dc9153514754c9656f7593a863794 [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 "clock.h"
#include "clock_chip.h"
#include "common.h"
#include "console.h"
#include "cpu.h"
#include "gpio.h"
#include "hooks.h"
#include "hwtimer.h"
#include "hwtimer_chip.h"
#include "registers.h"
#include "system.h"
#include "task.h"
#include "timer.h"
#include "uart.h"
#include "util.h"
#include "watchdog.h"
/* Console output macros */
#define CPUTS(outstr) cputs(CC_CLOCK, outstr)
#define CPRINTS(format, args...) cprints(CC_CLOCK, format, ## args)
#define OSC_CLK 48000000 /* Default is 40MHz (target is 48MHz) */
#define WAKE_INTERVAL 61 /* Unit: 61 usec */
#define IDLE_PARAMS 0x7 /* Support deep idle, instant wake-up */
/*
* Frequency multiplier values definition according to the requested
* PLL_CLOCK Clock Frequency
*/
#define HFCGN 0x02
#if (OSC_CLK == 50000000)
#define HFCGMH 0x0B
#define HFCGML 0xEC
#elif (OSC_CLK == 48000000)
#define HFCGMH 0x0B
#define HFCGML 0x72
#elif (OSC_CLK == 40000000)
#define HFCGMH 0x09
#define HFCGML 0x89
#elif (OSC_CLK == 33000000)
#define HFCGMH 0x07
#define HFCGML 0xDE
#else
#error "Unsupported FMCLK Clock Frequency"
#endif
/* 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;
/*
* Fixed amount of time to keep the console in use flag true after boot in
* order to give a permanent window in which the low speed clock is not used.
*/
#define CONSOLE_IN_USE_ON_BOOT_TIME (15*SECOND)
static int console_in_use_timeout_sec = 15;
static timestamp_t console_expire_time;
#endif
static int freq;
/* Low power idle statistics */
/**
* Enable clock to peripheral by setting the CGC register pertaining
* to run, sleep, and/or deep sleep modes.
*
* @param offset Offset of the peripheral. See enum clock_gate_offsets.
* @param mask Bit mask of the bits within CGC reg to set.
* @param mode no used
*/
void clock_enable_peripheral(uint32_t offset, uint32_t mask, uint32_t mode)
{
/* Don't support for different mode */
uint8_t reg_mask = mask & 0xff;
/* Set PD bit to 0 */
NPCX_PWDWN_CTL(offset) &= ~reg_mask;
/* Wait for clock change to take affect. */
clock_wait_cycles(3);
}
/**
* Disable clock to peripheral by setting the CGC register pertaining
* to run, sleep, and/or deep sleep modes.
*
* @param offset Offset of the peripheral. See enum clock_gate_offsets.
* @param mask Bit mask of the bits within CGC reg to clear.
* @param mode no used
*/
void clock_disable_peripheral(uint32_t offset, uint32_t mask, uint32_t mode)
{
/* Don't support for different mode */
uint8_t reg_mask = mask & 0xff;
/* Set PD bit to 1 */
NPCX_PWDWN_CTL(offset) |= reg_mask;
}
/*****************************************************************************/
/* IC specific low-level driver */
/**
* Set the CPU clocks and PLLs.
*/
void clock_init(void)
{
/*
* Configure Frequency multiplier values according to the requested
* FMCLK Clock Frequency
*/
NPCX_HFCGN = HFCGN;
NPCX_HFCGML = HFCGML;
NPCX_HFCGMH = HFCGMH;
/* Load M and N values into the frequency multiplier */
SET_BIT(NPCX_HFCGCTRL, NPCX_HFCGCTRL_LOAD);
/* Wait for stable */
while (IS_BIT_SET(NPCX_HFCGCTRL, NPCX_HFCGCTRL_CLK_CHNG))
;
/* Keep Core CLK & FMCLK are the same */
NPCX_HFCGP = 0x00;
freq = OSC_CLK;
/* Notify modules of frequency change */
hook_notify(HOOK_FREQ_CHANGE);
}
/**
* Return the current clock frequency in Hz.
*/
int clock_get_freq(void)
{
return freq;
}
/**
* Return the current APB1 clock frequency in Hz.
*/
int clock_get_apb1_freq(void)
{
int apb1_div = (NPCX_HFCBCD & 0x03) + 1;
return freq/apb1_div;
}
/**
* Return the current APB2 clock frequency in Hz.
*/
int clock_get_apb2_freq(void)
{
int apb2_div = ((NPCX_HFCBCD>>2) & 0x03) + 1;
return freq/apb2_div;
}
/**
* Wait for a number of clock cycles.
*
* Simple busy waiting for use before clocks/timers are initialized.
*
* @param cycles Number of cycles to wait.
*/
void clock_wait_cycles(uint32_t cycles)
{
asm("1: subs %0, #1\n"
" bne 1b\n" :: "r"(cycles));
}
#ifdef CONFIG_LOW_POWER_IDLE
void clock_refresh_console_in_use(void)
{
/* Set console in use expire time. */
console_expire_time = get_time();
console_expire_time.val += console_in_use_timeout_sec * SECOND;
return;
}
void clock_uart2gpio(void)
{
/* Is pimux to UART? */
if (IS_BIT_SET(NPCX_DEVALT(0x0A), NPCX_DEVALTA_UART_SL)) {
/* Change pinmux to GPIO and disable UART IRQ */
task_disable_irq(NPCX_IRQ_UART);
CLEAR_BIT(NPCX_DEVALT(0x0A), NPCX_DEVALTA_UART_SL);
/*Enable MIWU for GPIO (UARTRX) */
SET_BIT(NPCX_WKEN(1, 1), 0);
/* Clear Pending bit of GPIO (UARTRX) */
if (IS_BIT_SET(NPCX_WKPND(1, 1), 0))
SET_BIT(NPCX_WKPCL(1, 1), 0);
/* Disable MIWU IRQ */
task_disable_irq(NPCX_IRQ_WKINTB_1);
}
}
void clock_gpio2uart(void)
{
/* Is Pending bit of GPIO (UARTRX) */
if (IS_BIT_SET(NPCX_WKPND(1, 1), 0)) {
/* Clear Pending bit of GPIO (UARTRX) */
SET_BIT(NPCX_WKPCL(1, 1), 0);
/* Refresh console in-use timer */
clock_refresh_console_in_use();
/* Disable MIWU & IRQ for GPIO (UARTRX) */
CLEAR_BIT(NPCX_WKEN(1, 1), 0);
/* Enable MIWU IRQ */
task_enable_irq(NPCX_IRQ_WKINTB_1);
/* Go back CR_SIN*/
SET_BIT(NPCX_DEVALT(0x0A), NPCX_DEVALTA_UART_SL);
/* Enable uart again */
task_enable_irq(NPCX_IRQ_UART);
}
}
/* Idle task. Executed when no tasks are ready to be scheduled. */
void __idle(void)
{
#ifdef SUPPORT_JTAG
while (1) {
/*
* TODO:(ML) JTAG bug: if debugger is connected,
* CPU can't enter wfi. Rev A3 will fix it.
*/
;
};
#else
timestamp_t t0, t1;
uint32_t next_evt_us;
/*
* Initialize console in use to true and specify the console expire
* time in order to give a fixed window on boot in which the low speed
* clock will not be used in idle.
*/
console_expire_time.val = get_time().val + CONSOLE_IN_USE_ON_BOOT_TIME;
while (1) {
/*
* Disable interrupts before going to deep sleep in order to
* calculate the appropriate time to wake up. Note: the wfi
* instruction waits until an interrupt is pending, so it
* will still wake up even with interrupts disabled.
*/
interrupt_disable();
/* Compute event delay */
t0 = get_time();
next_evt_us = __hw_clock_event_get() - t0.le.lo;
/* Do we have enough time before next event to deep sleep. */
if (DEEP_SLEEP_ALLOWED && (next_evt_us > WAKE_INTERVAL)
/* Make sure it's over console expired time */
&& (t0.val > console_expire_time.val)) {
#if DEBUG_CLK
/* Use GPIO to indicate SLEEP mode */
CLEAR_BIT(NPCX_PDOUT(0), 0);
#endif
idle_dsleep_cnt++;
/* Set instant wake up mode */
SET_BIT(NPCX_ENIDL_CTL, NPCX_ENIDL_CTL_LP_WK_CTL);
/* Set deep idle - instant wake-up mode */
NPCX_PMCSR = IDLE_PARAMS;
/* UART-rx(console) become to GPIO (NONE INT mode) */
clock_uart2gpio();
/* Enter deep idle */
asm("wfi");
/* GPIO back to UART-rx (console) */
clock_gpio2uart();
/* Get time delay cause of deep idle */
next_evt_us = __hw_clock_get_sleep_time();
/* Fast forward timer according to wake-up timer. */
t1.val = t0.val + next_evt_us;
/* Record time spent in deep sleep. */
idle_dsleep_time_us += next_evt_us;
force_time(t1);
} else {
#if DEBUG_CLK
/* Use GPIO to indicate NORMAL mode */
SET_BIT(NPCX_PDOUT(0), 0);
#endif
idle_sleep_cnt++;
/* normal idle : wait for interrupt */
asm("wfi");
}
/*
* Restore interrupt
* RTOS will leave idle task to handle ISR which wakes up EC
*/
interrupt_enable();
}
#endif
}
#endif /* CONFIG_LOW_POWER_IDLE */
#ifdef CONFIG_LOW_POWER_IDLE
/**
* 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);
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(idlestats, command_idle_stats,
"",
"Print last idle stats",
NULL);
/**
* 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 low speed clock or
* allow it to use the low speed clock.
*/
if (v)
disable_sleep(SLEEP_MASK_FORCE_NO_LOW_SPEED);
else
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);
ccprintf("PMCSR register: 0x%02x\n", NPCX_PMCSR);
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 use low speed clock.\nUse 'off' to "
"allow deep sleep to auto-select using the low speed "
"clock.\n"
"Give a timeout value for the console in use timeout.\n"
"See also 'sleepmask'.",
NULL);
#endif /* CONFIG_LOW_POWER_IDLE */