Files
OpenCellular/chip/it83xx/clock.c
Dino Li 5fd8fead54 it83xx: espi: workaround for changing PLL
We need to change PLL settings if host apply eSPI operating
frequency higher than 50MHz, because FND clock is required
to be higher than half of operating frequency.

BRANCH=none
BUG=b:70537592
TEST=Change PLL succeed with chip select is low.

Change-Id: Ieba62f33ed024aed7a8e7f4cc48b1398ed781170
Signed-off-by: Dino Li <Dino.Li@ite.com.tw>
Reviewed-on: https://chromium-review.googlesource.com/817717
Reviewed-by: Randall Spangler <rspangler@chromium.org>
2017-12-12 22:00:36 -08:00

541 lines
15 KiB
C

/* 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 "adc_chip.h"
#include "clock.h"
#include "common.h"
#include "console.h"
#include "hwtimer.h"
#include "hwtimer_chip.h"
#include "intc.h"
#include "irq_chip.h"
#include "registers.h"
#include "system.h"
#include "task.h"
#include "timer.h"
#include "uart.h"
#include "util.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
#define SLEEP_SET_HTIMER_DELAY_USEC 250
#define SLEEP_FTIMER_SKIP_USEC (HOOK_TICK_INTERVAL * 2)
#ifdef CONFIG_ADC
BUILD_ASSERT(ADC_TIMEOUT_US < SLEEP_SET_HTIMER_DELAY_USEC);
#endif
static timestamp_t sleep_mode_t0;
static timestamp_t sleep_mode_t1;
static int idle_doze_cnt;
static int idle_sleep_cnt;
static uint64_t total_idle_sleep_time_us;
static int allow_sleep;
static uint32_t ec_sleep;
/*
* 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 = 5;
static timestamp_t console_expire_time;
/* clock source is 32.768KHz */
#define TIMER_32P768K_CNT_TO_US(cnt) ((uint64_t)(cnt) * 1000000 / 32768)
#define TIMER_CNT_8M_32P768K(cnt) (((cnt) / (8000000 / 32768)) + 1)
#endif /*CONFIG_LOW_POWER_IDLE */
static int freq;
struct clock_gate_ctrl {
volatile uint8_t *reg;
uint8_t mask;
};
static void clock_module_disable(void)
{
/* bit0: FSPI interface tri-state */
IT83XX_SMFI_FLHCTRL3R |= (1 << 0);
/* bit7: USB pad power-on disable */
IT83XX_GCTRL_PMER2 &= ~(1 << 7);
/* bit7: USB debug disable */
IT83XX_GCTRL_MCCR &= ~(1 << 7);
clock_disable_peripheral((CGC_OFFSET_EGPC | CGC_OFFSET_CIR), 0, 0);
clock_disable_peripheral((CGC_OFFSET_SMBA | CGC_OFFSET_SMBB |
CGC_OFFSET_SMBC | CGC_OFFSET_SMBD | CGC_OFFSET_SMBE |
CGC_OFFSET_SMBF), 0, 0);
clock_disable_peripheral((CGC_OFFSET_SSPI | CGC_OFFSET_PECI |
CGC_OFFSET_USB), 0, 0);
}
enum pll_freq_idx {
PLL_24_MHZ = 1,
PLL_48_MHZ = 2,
PLL_96_MHZ = 4,
};
static const uint8_t pll_to_idx[8] = {
0,
0,
PLL_24_MHZ,
0,
PLL_48_MHZ,
0,
0,
PLL_96_MHZ
};
struct clock_pll_t {
int pll_freq;
uint8_t pll_setting;
uint8_t div_fnd;
uint8_t div_uart;
uint8_t div_usb;
uint8_t div_smb;
uint8_t div_sspi;
uint8_t div_ec;
uint8_t div_jtag;
uint8_t div_pwm;
uint8_t div_usbpd;
};
const struct clock_pll_t clock_pll_ctrl[] = {
/*
* UART: 24MHz
* SMB: 24MHz
* EC: 8MHz
* JTAG: 24MHz
* USBPD: 8MHz
* USB: 48MHz(no support if PLL=24MHz)
* SSPI: 48MHz(24MHz if PLL=24MHz)
*/
/* PLL:24MHz, MCU:24MHz, Fnd(e-flash):24MHz */
[PLL_24_MHZ] = {24000000, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0x2},
#ifdef CONFIG_IT83XX_FLASH_CLOCK_48MHZ
/* PLL:48MHz, MCU:48MHz, Fnd:48MHz */
[PLL_48_MHZ] = {48000000, 4, 0, 1, 0, 1, 0, 6, 1, 0, 0x5},
/* PLL:96MHz, MCU:96MHz, Fnd:48MHz */
[PLL_96_MHZ] = {96000000, 7, 1, 3, 1, 3, 1, 6, 3, 1, 0xb},
#else
/* PLL:48MHz, MCU:48MHz, Fnd:24MHz */
[PLL_48_MHZ] = {48000000, 4, 1, 1, 0, 1, 0, 2, 1, 0, 0x5},
/* PLL:96MHz, MCU:96MHz, Fnd:32MHz */
[PLL_96_MHZ] = {96000000, 7, 2, 3, 1, 3, 1, 4, 3, 1, 0xb},
#endif
};
static uint8_t pll_div_fnd;
static uint8_t pll_div_ec;
static uint8_t pll_div_jtag;
static uint8_t pll_setting;
void __ram_code clock_ec_pll_ctrl(enum ec_pll_ctrl mode)
{
IT83XX_ECPM_PLLCTRL = mode;
/* for deep doze / sleep mode */
IT83XX_ECPM_PLLCTRL = mode;
asm volatile ("dsb");
}
void __ram_code clock_pll_changed(void)
{
IT83XX_GCTRL_SSCR &= ~(1 << 0);
/*
* Update PLL settings.
* Writing data to this register doesn't change the
* PLL frequency immediately until the status is changed
* into wakeup from the sleep mode.
* The following code is intended to make the system
* enter sleep mode, and set up a HW timer to wakeup EC to
* complete PLL update.
*/
IT83XX_ECPM_PLLFREQR = pll_setting;
/* Pre-set FND clock frequency = PLL / 3 */
IT83XX_ECPM_SCDCR0 = (2 << 4);
/* JTAG and EC */
IT83XX_ECPM_SCDCR3 = (pll_div_jtag << 4) | pll_div_ec;
/* EC sleep after standby instruction */
clock_ec_pll_ctrl(EC_PLL_SLEEP);
/* Global interrupt enable */
asm volatile ("setgie.e");
/* EC sleep */
asm("standby wake_grant");
/* Global interrupt disable */
asm volatile ("setgie.d");
/* New FND clock frequency */
IT83XX_ECPM_SCDCR0 = (pll_div_fnd << 4);
/* EC doze after standby instruction */
clock_ec_pll_ctrl(EC_PLL_DOZE);
}
/* NOTE: Don't use this function in other place. */
static void clock_set_pll(enum pll_freq_idx idx)
{
int pll;
pll_div_fnd = clock_pll_ctrl[idx].div_fnd;
pll_div_ec = clock_pll_ctrl[idx].div_ec;
pll_div_jtag = clock_pll_ctrl[idx].div_jtag;
pll_setting = clock_pll_ctrl[idx].pll_setting;
/* Update PLL settings or not */
if (((IT83XX_ECPM_PLLFREQR & 0xf) != pll_setting) ||
((IT83XX_ECPM_SCDCR0 & 0xf0) != (pll_div_fnd << 4)) ||
((IT83XX_ECPM_SCDCR3 & 0xf) != pll_div_ec)) {
/* Enable hw timer to wakeup EC from the sleep mode */
ext_timer_ms(LOW_POWER_EXT_TIMER, EXT_PSR_32P768K_HZ,
1, 1, 5, 1, 0);
task_clear_pending_irq(et_ctrl_regs[LOW_POWER_EXT_TIMER].irq);
#ifdef CONFIG_ESPI
/*
* Workaround for (b:70537592):
* We have to set chip select pin as input mode in order to
* change PLL.
*/
IT83XX_GPIO_GPCRM5 = (IT83XX_GPIO_GPCRM5 & ~0xc0) | (1 << 7);
#endif
/* Update PLL settings. */
clock_pll_changed();
#ifdef CONFIG_ESPI
/* (b:70537592) Change back to ESPI CS# function. */
IT83XX_GPIO_GPCRM5 &= ~0xc0;
#endif
}
/* Get new/current setting of PLL frequency */
pll = pll_to_idx[IT83XX_ECPM_PLLFREQR & 0xf];
/* USB and UART */
IT83XX_ECPM_SCDCR1 = (clock_pll_ctrl[pll].div_usb << 4) |
clock_pll_ctrl[pll].div_uart;
/* SSPI and SMB */
IT83XX_ECPM_SCDCR2 = (clock_pll_ctrl[pll].div_sspi << 4) |
clock_pll_ctrl[pll].div_smb;
/* USBPD and PWM */
IT83XX_ECPM_SCDCR4 = (clock_pll_ctrl[pll].div_usbpd << 4) |
clock_pll_ctrl[pll].div_pwm;
/* Current PLL frequency */
freq = clock_pll_ctrl[pll].pll_freq;
}
void clock_init(void)
{
uint32_t image_type = (uint32_t)clock_init;
/* To change interrupt vector base if at RW image */
if (image_type > CONFIG_RW_MEM_OFF)
/* Interrupt Vector Table Base Address, in 64k Byte unit */
IT83XX_GCTRL_IVTBAR = (CONFIG_RW_MEM_OFF >> 16) & 0xFF;
#if (PLL_CLOCK == 24000000) || \
(PLL_CLOCK == 48000000) || \
(PLL_CLOCK == 96000000)
/* Set PLL frequency */
clock_set_pll(PLL_CLOCK / 24000000);
#else
#error "Support only for PLL clock speed of 24/48/96MHz."
#endif
/*
* The VCC power status is treated as power-on.
* The VCC supply of LPC and related functions (EC2I,
* KBC, SWUC, PMC, CIR, SSPI, UART, BRAM, and PECI).
* It means VCC (pin 11) should be logic high before using
* these functions, or firmware treats VCC logic high
* as following setting.
*/
IT83XX_GCTRL_RSTS = (IT83XX_GCTRL_RSTS & 0x3F) + 0x40;
/* Turn off auto clock gating. */
IT83XX_ECPM_AUTOCG = 0x00;
/* Default doze mode */
clock_ec_pll_ctrl(EC_PLL_DOZE);
clock_module_disable();
#ifdef CONFIG_LPC
IT83XX_WUC_WUESR4 = (1 << 2);
task_clear_pending_irq(IT83XX_IRQ_WKINTAD);
/* bit2, wake-up enable for LPC access */
IT83XX_WUC_WUENR4 |= (1 << 2);
#endif
}
int clock_get_freq(void)
{
return freq;
}
/**
* Enable clock to specified peripheral
*
* @param offset Should be element of clock_gate_offsets enum.
* Bits 8-15 specify the ECPM offset of the specific clock reg.
* Bits 0-7 specify the mask for the clock register.
* @param mask Unused
* @param mode Unused
*/
void clock_enable_peripheral(uint32_t offset, uint32_t mask, uint32_t mode)
{
volatile uint8_t *reg = (volatile uint8_t *)
(IT83XX_ECPM_BASE + (offset >> 8));
uint8_t reg_mask = offset & 0xff;
/*
* Note: CGCTRL3R, bit 6, must always write 1, but since there is no
* offset argument that addresses this bit, then we are guaranteed
* that this line will write a 1 to that bit.
*/
*reg &= ~reg_mask;
}
/**
* Disable clock to specified peripheral
*
* @param offset Should be element of clock_gate_offsets enum.
* Bits 8-15 specify the ECPM offset of the specific clock reg.
* Bits 0-7 specify the mask for the clock register.
* @param mask Unused
* @param mode Unused
*/
void clock_disable_peripheral(uint32_t offset, uint32_t mask, uint32_t mode)
{
volatile uint8_t *reg = (volatile uint8_t *)
(IT83XX_ECPM_BASE + (offset >> 8));
uint8_t reg_mask = offset & 0xff;
uint8_t tmp_mask = 0;
/* CGCTRL3R, bit 6, must always write a 1. */
tmp_mask |= ((offset >> 8) == IT83XX_ECPM_CGCTRL3R_OFF) ? 0x40 : 0x00;
*reg |= reg_mask | tmp_mask;
}
#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;
}
static void clock_event_timer_clock_change(enum ext_timer_clock_source clock,
uint32_t count)
{
IT83XX_ETWD_ETXCTRL(EVENT_EXT_TIMER) &= ~(1 << 0);
IT83XX_ETWD_ETXPSR(EVENT_EXT_TIMER) = clock;
IT83XX_ETWD_ETXCNTLR(EVENT_EXT_TIMER) = count;
IT83XX_ETWD_ETXCTRL(EVENT_EXT_TIMER) |= 0x3;
}
static void clock_htimer_enable(void)
{
uint32_t c;
/* change event timer clock source to 32.768 KHz */
#if 0
c = TIMER_CNT_8M_32P768K(IT83XX_ETWD_ETXCNTOR(EVENT_EXT_TIMER));
#else
/* TODO(crosbug.com/p/55044) */
c = TIMER_CNT_8M_32P768K(ext_observation_reg_read(EVENT_EXT_TIMER));
#endif
clock_event_timer_clock_change(EXT_PSR_32P768K_HZ, c);
}
static int clock_allow_low_power_idle(void)
{
if (!(IT83XX_ETWD_ETXCTRL(EVENT_EXT_TIMER) & (1 << 0)))
return 0;
if (*et_ctrl_regs[EVENT_EXT_TIMER].isr &
et_ctrl_regs[EVENT_EXT_TIMER].mask)
return 0;
#if 0
if (EVENT_TIMER_COUNT_TO_US(IT83XX_ETWD_ETXCNTOR(EVENT_EXT_TIMER)) <
#else
/* TODO(crosbug.com/p/55044) */
if (EVENT_TIMER_COUNT_TO_US(ext_observation_reg_read(EVENT_EXT_TIMER)) <
#endif
SLEEP_SET_HTIMER_DELAY_USEC)
return 0;
sleep_mode_t0 = get_time();
if ((sleep_mode_t0.le.lo > (0xffffffff - SLEEP_FTIMER_SKIP_USEC)) ||
(sleep_mode_t0.le.lo < SLEEP_FTIMER_SKIP_USEC))
return 0;
if (sleep_mode_t0.val < console_expire_time.val)
return 0;
return 1;
}
int clock_ec_wake_from_sleep(void)
{
return ec_sleep;
}
void __enter_hibernate(uint32_t seconds, uint32_t microseconds)
{
int i;
/* disable all interrupts */
interrupt_disable();
for (i = 0; i < IT83XX_IRQ_COUNT; i++) {
chip_disable_irq(i);
chip_clear_pending_irq(i);
}
/* bit5: watchdog is disabled. */
IT83XX_ETWD_ETWCTRL |= (1 << 5);
/* Setup GPIOs for hibernate */
if (board_hibernate_late)
board_hibernate_late();
if (seconds || microseconds) {
/* At least 1 ms for hibernate. */
uint64_t c = (seconds * 1000 + microseconds / 1000 + 1) * 1024;
uint64divmod(&c, 1000);
/* enable a 56-bit timer and clock source is 1.024 KHz */
ext_timer_stop(FREE_EXT_TIMER_L, 1);
ext_timer_stop(FREE_EXT_TIMER_H, 1);
IT83XX_ETWD_ETXPSR(FREE_EXT_TIMER_L) = EXT_PSR_1P024K_HZ;
IT83XX_ETWD_ETXPSR(FREE_EXT_TIMER_H) = EXT_PSR_1P024K_HZ;
IT83XX_ETWD_ETXCNTLR(FREE_EXT_TIMER_L) = c & 0xffffff;
IT83XX_ETWD_ETXCNTLR(FREE_EXT_TIMER_H) = (c >> 24) & 0xffffffff;
ext_timer_start(FREE_EXT_TIMER_H, 1);
ext_timer_start(FREE_EXT_TIMER_L, 0);
}
for (i = 0; i < hibernate_wake_pins_used; ++i)
gpio_enable_interrupt(hibernate_wake_pins[i]);
/* EC sleep */
ec_sleep = 1;
clock_ec_pll_ctrl(EC_PLL_SLEEP);
interrupt_enable();
/* standby instruction */
asm("standby wake_grant");
/* we should never reach that point */
while (1)
;
}
void clock_sleep_mode_wakeup_isr(void)
{
uint32_t st_us, c;
/* trigger a reboot if wake up EC from sleep mode (system hibernate) */
if (clock_ec_wake_from_sleep())
system_reset(SYSTEM_RESET_HARD);
if (IT83XX_ECPM_PLLCTRL == EC_PLL_DEEP_DOZE) {
clock_ec_pll_ctrl(EC_PLL_DOZE);
/* update free running timer */
c = 0xffffffff - IT83XX_ETWD_ETXCNTOR(LOW_POWER_EXT_TIMER);
st_us = TIMER_32P768K_CNT_TO_US(c);
sleep_mode_t1.val = sleep_mode_t0.val + st_us;
__hw_clock_source_set(sleep_mode_t1.le.lo);
/* reset event timer and clock source is 8 MHz */
clock_event_timer_clock_change(EXT_PSR_8M_HZ, 0xffffffff);
task_clear_pending_irq(et_ctrl_regs[EVENT_EXT_TIMER].irq);
process_timers(0);
#ifdef CONFIG_LPC
/* disable lpc access wui */
task_disable_irq(IT83XX_IRQ_WKINTAD);
IT83XX_WUC_WUESR4 = (1 << 2);
task_clear_pending_irq(IT83XX_IRQ_WKINTAD);
#endif
/* disable uart wui */
uart_exit_dsleep();
/* Record time spent in sleep. */
total_idle_sleep_time_us += st_us;
}
}
/**
* Low power idle task. Executed when no tasks are ready to be scheduled.
*/
void __idle(void)
{
console_expire_time.val = get_time().val + CONSOLE_IN_USE_ON_BOOT_TIME;
/* init hw timer and clock source is 32.768 KHz */
ext_timer_ms(LOW_POWER_EXT_TIMER, EXT_PSR_32P768K_HZ, 1, 0,
0xffffffff, 1, 1);
/*
* 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) {
allow_sleep = 0;
if (DEEP_SLEEP_ALLOWED)
allow_sleep = clock_allow_low_power_idle();
if (allow_sleep) {
interrupt_disable();
/* reset low power mode hw timer */
IT83XX_ETWD_ETXCTRL(LOW_POWER_EXT_TIMER) |= (1 << 1);
sleep_mode_t0 = get_time();
#ifdef CONFIG_LPC
/* enable lpc access wui */
task_enable_irq(IT83XX_IRQ_WKINTAD);
#endif
/* enable uart wui */
uart_enter_dsleep();
/* enable hw timer for deep doze / sleep mode wake-up */
clock_htimer_enable();
/* deep doze mode */
clock_ec_pll_ctrl(EC_PLL_DEEP_DOZE);
interrupt_enable();
/* standby instruction */
asm("standby wake_grant");
idle_sleep_cnt++;
} else {
/* doze mode */
clock_ec_pll_ctrl(EC_PLL_DOZE);
/* standby instruction */
asm("standby wake_grant");
idle_doze_cnt++;
}
}
}
#endif /* CONFIG_LOW_POWER_IDLE */
#ifdef CONFIG_LOW_POWER_IDLE
#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 doze: %d\n", idle_doze_cnt);
ccprintf("Num idle calls that sleep: %d\n", idle_sleep_cnt);
ccprintf("Total Time spent in sleep(sec): %.6ld(s)\n",
total_idle_sleep_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 /* CONFIG_CMD_IDLE_STATS */
#endif /* CONFIG_LOW_POWER_IDLE */