/* 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 "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 "it83xx_pd.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 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 |= BIT(0); /* bit7: USB pad power-on disable */ IT83XX_GCTRL_PMER2 &= ~BIT(7); /* bit7: USB debug disable */ IT83XX_GCTRL_MCCR &= ~BIT(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; /* * barrier: ensure low power mode setting is taken into control * register before standby instruction. */ data_serialization_barrier(); } void __ram_code clock_pll_changed(void) { IT83XX_GCTRL_SSCR &= ~BIT(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); if (IS_ENABLED(CHIP_CORE_NDS32)) { /* Global interrupt enable */ asm volatile ("setgie.e"); /* EC sleep */ asm("standby wake_grant"); /* Global interrupt disable */ asm volatile ("setgie.d"); } else if (IS_ENABLED(CHIP_CORE_RISCV)) { /* Global interrupt enable */ asm volatile ("csrsi mstatus, 0x8"); /* EC sleep */ asm("wfi"); /* Global interrupt disable */ asm volatile ("csrci mstatus, 0x8"); } /* 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; /* TODO(b/134542199): fix me... Changing PLL failed on it83202/ax */ if (IS_ENABLED(CHIP_VARIANT_IT83202AX)) return; 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_HOSTCMD_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) | BIT(7); #ifdef IT83XX_ESPI_INHIBIT_CS_BY_PAD_DISABLED /* * On DX version, we have to disable eSPI pad before changing * PLL sequence or sequence will fail if CS# pin is low. */ espi_enable_pad(0); #endif #endif /* Update PLL settings. */ clock_pll_changed(); #ifdef CONFIG_HOSTCMD_ESPI #ifdef IT83XX_ESPI_INHIBIT_CS_BY_PAD_DISABLED /* Enable eSPI pad after changing PLL sequence. */ espi_enable_pad(1); #endif /* (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; #if defined(IT83XX_ESPI_RESET_MODULE_BY_FW) && defined(CONFIG_HOSTCMD_ESPI) /* * Because we don't support eSPI HW reset function (b/111480168) on DX * version, so we have to reset eSPI configurations during init to * ensure Host and EC are synchronized (especially for the field of * I/O mode) */ if (!system_jumped_to_this_image()) espi_fw_reset_module(); #endif /* Turn off auto clock gating. */ IT83XX_ECPM_AUTOCG = 0x00; /* Default doze mode */ clock_ec_pll_ctrl(EC_PLL_DOZE); clock_module_disable(); #ifdef CONFIG_HOSTCMD_X86 IT83XX_WUC_WUESR4 = BIT(2); task_clear_pending_irq(IT83XX_IRQ_WKINTAD); /* bit2, wake-up enable for LPC access */ IT83XX_WUC_WUENR4 |= BIT(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) &= ~BIT(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 */ #ifdef IT83XX_EXT_OBSERVATION_REG_READ_TWO_TIMES c = TIMER_CNT_8M_32P768K(ext_observation_reg_read(EVENT_EXT_TIMER)); #else c = TIMER_CNT_8M_32P768K(IT83XX_ETWD_ETXCNTOR(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) & BIT(0))) return 0; if (*et_ctrl_regs[EVENT_EXT_TIMER].isr & et_ctrl_regs[EVENT_EXT_TIMER].mask) return 0; #ifdef IT83XX_EXT_OBSERVATION_REG_READ_TWO_TIMES if (EVENT_TIMER_COUNT_TO_US(ext_observation_reg_read(EVENT_EXT_TIMER)) < #else if (EVENT_TIMER_COUNT_TO_US(IT83XX_ETWD_ETXCNTOR(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 clock_cpu_standby(void) { /* standby instruction */ if (IS_ENABLED(CHIP_CORE_NDS32)) { asm("standby wake_grant"); } else if (IS_ENABLED(CHIP_CORE_RISCV)) { /* * An interrupt is not able to wake EC up from low power mode. * (AX bug) */ if (IS_ENABLED(CHIP_VARIANT_IT83202AX)) asm("nop"); else asm("wfi"); } } 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 |= BIT(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); } #ifdef CONFIG_USB_PD_TCPM_ITE83XX /* * Disable integrated pd modules in hibernate for * better power consumption. */ for (i = 0; i < USBPD_PORT_COUNT; i++) it83xx_disable_pd_module(i); #endif for (i = 0; i < hibernate_wake_pins_used; ++i) gpio_enable_interrupt(hibernate_wake_pins[i]); /* EC sleep */ ec_sleep = 1; #if defined(IT83XX_ESPI_INHIBIT_CS_BY_PAD_DISABLED) && \ defined(CONFIG_HOSTCMD_ESPI) /* Disable eSPI pad. */ espi_enable_pad(0); #endif clock_ec_pll_ctrl(EC_PLL_SLEEP); interrupt_enable(); /* standby instruction */ clock_cpu_standby(); /* we should never reach that point */ __builtin_unreachable(); } 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()) { #if defined(IT83XX_ESPI_INHIBIT_CS_BY_PAD_DISABLED) && \ defined(CONFIG_HOSTCMD_ESPI) /* * Enable eSPI pad. * We will not need to enable eSPI pad here if Dx is able to * enable watchdog hardware reset function. But the function is * failed (b:111264984), so the following system reset is * software reset (PLL setting is not reset). * We will not go into the change PLL sequence on reboot if PLL * setting is the same, so the operation of enabling eSPI pad we * added in clock_set_pll() will not be applied. */ espi_enable_pad(1); #endif 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_HOSTCMD_X86 /* disable lpc access wui */ task_disable_irq(IT83XX_IRQ_WKINTAD); IT83XX_WUC_WUESR4 = BIT(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) { /* Disable interrupts */ interrupt_disable(); /* Check if the EC can enter deep doze mode or not */ if (DEEP_SLEEP_ALLOWED && clock_allow_low_power_idle()) { /* reset low power mode hw timer */ IT83XX_ETWD_ETXCTRL(LOW_POWER_EXT_TIMER) |= BIT(1); sleep_mode_t0 = get_time(); #ifdef CONFIG_HOSTCMD_X86 /* 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); idle_sleep_cnt++; } else { /* doze mode */ clock_ec_pll_ctrl(EC_PLL_DOZE); idle_doze_cnt++; } clock_cpu_standby(); interrupt_enable(); } } #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 */