/* Copyright 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 "chipset.h" #include "clock.h" #include "common.h" #include "console.h" #include "cpu.h" #include "hooks.h" #include "registers.h" #include "util.h" #ifdef CONFIG_STM32L_FAKE_HIBERNATE #include "extpower.h" #include "keyboard_config.h" #include "lid_switch.h" #include "power.h" #include "power_button.h" #include "system.h" #include "task.h" static int fake_hibernate; #endif /* High-speed oscillator is 16 MHz */ #define HSI_CLOCK 16000000 /* * MSI is 2 MHz (default) 1 MHz, depending on ICSCR setting. We use 1 MHz * because it's the lowest clock rate we can still run 115200 baud serial * for the debug console. */ #define MSI_2MHZ_CLOCK BIT(21) #define MSI_1MHZ_CLOCK BIT(20) enum clock_osc { OSC_INIT = 0, /* Uninitialized */ OSC_HSI, /* High-speed oscillator */ OSC_MSI, /* Med-speed oscillator @ 1 MHz */ }; static int freq; static int current_osc; int clock_get_freq(void) { return freq; } int clock_get_timer_freq(void) { return clock_get_freq(); } void clock_wait_bus_cycles(enum bus_type bus, uint32_t cycles) { volatile uint32_t unused __attribute__((unused)); if (bus == BUS_AHB) { while (cycles--) unused = STM32_DMA1_REGS->isr; } else { /* APB */ while (cycles--) unused = STM32_USART_BRR(STM32_USART1_BASE); } } /** * Set which oscillator is used for the clock * * @param osc Oscillator to use */ static void clock_set_osc(enum clock_osc osc) { uint32_t tmp_acr; if (osc == current_osc) return; if (current_osc != OSC_INIT) hook_notify(HOOK_PRE_FREQ_CHANGE); switch (osc) { case OSC_HSI: /* Ensure that HSI is ON */ wait_for_ready(&STM32_RCC_CR, STM32_RCC_CR_HSION, STM32_RCC_CR_HSIRDY); /* Disable LPSDSR */ STM32_PWR_CR &= ~STM32_PWR_CR_LPSDSR; /* * Set the recommended flash settings for 16MHz clock. * * The 3 bits must be programmed strictly sequentially. * Also, follow the RM to check 64-bit access and latency bit * after writing those bits to the FLASH_ACR register. */ tmp_acr = STM32_FLASH_ACR; /* Enable 64-bit access */ tmp_acr |= STM32_FLASH_ACR_ACC64; STM32_FLASH_ACR = tmp_acr; /* Check ACC64 bit == 1 */ while (!(STM32_FLASH_ACR & STM32_FLASH_ACR_ACC64)) ; /* Enable Prefetch Buffer */ tmp_acr |= STM32_FLASH_ACR_PRFTEN; STM32_FLASH_ACR = tmp_acr; /* Flash 1 wait state */ tmp_acr |= STM32_FLASH_ACR_LATENCY; STM32_FLASH_ACR = tmp_acr; /* Check LATENCY bit == 1 */ while (!(STM32_FLASH_ACR & STM32_FLASH_ACR_LATENCY)) ; /* Switch to HSI */ STM32_RCC_CFGR = STM32_RCC_CFGR_SW_HSI; /* RM says to check SWS bits to make sure HSI is the sysclock */ while ((STM32_RCC_CFGR & STM32_RCC_CFGR_SWS_MASK) != STM32_RCC_CFGR_SWS_HSI) ; /* Disable MSI */ STM32_RCC_CR &= ~STM32_RCC_CR_MSION; freq = HSI_CLOCK; break; case OSC_MSI: /* Switch to MSI @ 1MHz */ STM32_RCC_ICSCR = (STM32_RCC_ICSCR & ~STM32_RCC_ICSCR_MSIRANGE_MASK) | STM32_RCC_ICSCR_MSIRANGE_1MHZ; /* Ensure that MSI is ON */ wait_for_ready(&STM32_RCC_CR, STM32_RCC_CR_MSION, STM32_RCC_CR_MSIRDY); /* Switch to MSI */ STM32_RCC_CFGR = STM32_RCC_CFGR_SW_MSI; /* RM says to check SWS bits to make sure MSI is the sysclock */ while ((STM32_RCC_CFGR & STM32_RCC_CFGR_SWS_MASK) != STM32_RCC_CFGR_SWS_MSI) ; /* * Set the recommended flash settings for <= 2MHz clock. * * The 3 bits must be programmed strictly sequentially. * Also, follow the RM to check 64-bit access and latency bit * after writing those bits to the FLASH_ACR register. */ tmp_acr = STM32_FLASH_ACR; /* Flash 0 wait state */ tmp_acr &= ~STM32_FLASH_ACR_LATENCY; STM32_FLASH_ACR = tmp_acr; /* Check LATENCY bit == 0 */ while (STM32_FLASH_ACR & STM32_FLASH_ACR_LATENCY) ; /* Disable prefetch Buffer */ tmp_acr &= ~STM32_FLASH_ACR_PRFTEN; STM32_FLASH_ACR = tmp_acr; /* Disable 64-bit access */ tmp_acr &= ~STM32_FLASH_ACR_ACC64; STM32_FLASH_ACR = tmp_acr; /* Check ACC64 bit == 0 */ while (STM32_FLASH_ACR & STM32_FLASH_ACR_ACC64) ; /* Disable HSI */ STM32_RCC_CR &= ~STM32_RCC_CR_HSION; /* Enable LPSDSR */ STM32_PWR_CR |= STM32_PWR_CR_LPSDSR; freq = MSI_1MHZ_CLOCK; break; default: break; } /* Notify modules of frequency change unless we're initializing */ if (current_osc != OSC_INIT) { current_osc = osc; hook_notify(HOOK_FREQ_CHANGE); } else { current_osc = osc; } } static uint64_t clock_mask; void clock_enable_module(enum module_id module, int enable) { uint64_t new_mask; if (enable) new_mask = clock_mask | BIT_ULL(module); else new_mask = clock_mask & ~BIT_ULL(module); /* Only change clock if needed */ if ((!!new_mask) != (!!clock_mask)) { /* Flush UART before switching clock speed */ cflush(); clock_set_osc(new_mask ? OSC_HSI : OSC_MSI); } if (module == MODULE_USB) { if (enable) STM32_RCC_APB1ENR |= STM32_RCC_PB1_USB; else STM32_RCC_APB1ENR &= ~STM32_RCC_PB1_USB; } clock_mask = new_mask; } int clock_is_module_enabled(enum module_id module) { return !!(clock_mask & BIT_ULL(module)); } #ifdef CONFIG_STM32L_FAKE_HIBERNATE /* * This is for NOT having enough hibernate (more precisely, the stand-by mode) * wake-up source pin. STM32L100 supports 3 wake-up source pins: * * WKUP1 (PA0) -- used for ACOK_PMU * WKUP2 (PC13) -- used for LID_OPEN * WKUP3 (PE6) -- cannot be used due to IC package. * * However, we need the power button as a wake-up source as well and there is * no available pin for us (we don't want to move the ACOK_PMU pin). * * Fortunately, the STM32L is low-power enough so that we don't need the * super-low-power mode. So, we fake this hibernate mode and accept the * following wake-up source. * * RTC alarm (faked as well). * Power button * Lid open * AC detected * * The original issue is here: crosbug.com/p/25435. */ void __enter_hibernate(uint32_t seconds, uint32_t microseconds) { int i; fake_hibernate = 1; #ifdef CONFIG_POWER_COMMON /* * A quick hack to stop annoying messages from charger task. * * When the battery is under 3%, the power task would call * power_off() to shutdown AP. However, the power_off() would * notify the HOOK_CHIPSET_SHUTDOWN, where the last hook is * charge_shutdown() and it hibernates the power task (infinite * loop -- not real CPU hibernate mode). Unfortunately, the * charger task is still running. It keeps generating annoying * log message. * * Thus, the hack is to set the power state machine (before we * enter infinite loop) so that the charger task thinks the AP * is off and stops generating messages. */ power_set_state(POWER_G3); #endif /* * Change keyboard outputs to high-Z to reduce power draw. * We don't need corresponding code to change them back, * because fake hibernate is always exited with a reboot. * * A little hacky to do this here. */ for (i = GPIO_KB_OUT00; i < GPIO_KB_OUT00 + KEYBOARD_COLS_MAX; i++) gpio_set_flags(i, GPIO_INPUT); ccprints("fake hibernate. waits for power button/lid/RTC/AC"); cflush(); if (seconds || microseconds) { if (seconds) sleep(seconds); if (microseconds) usleep(microseconds); } else { while (1) task_wait_event(-1); } ccprints("fake RTC alarm fires. resets EC"); cflush(); system_reset(SYSTEM_RESET_HARD); } static void fake_hibernate_power_button_hook(void) { if (fake_hibernate && lid_is_open() && !power_button_is_pressed()) { ccprints("%s() resets EC", __func__); cflush(); system_reset(SYSTEM_RESET_HARD); } } DECLARE_HOOK(HOOK_POWER_BUTTON_CHANGE, fake_hibernate_power_button_hook, HOOK_PRIO_DEFAULT); static void fake_hibernate_lid_hook(void) { if (fake_hibernate && lid_is_open()) { ccprints("%s() resets EC", __func__); cflush(); system_reset(SYSTEM_RESET_HARD); } } DECLARE_HOOK(HOOK_LID_CHANGE, fake_hibernate_lid_hook, HOOK_PRIO_DEFAULT); static void fake_hibernate_ac_hook(void) { if (fake_hibernate && extpower_is_present()) { ccprints("%s() resets EC", __func__); cflush(); system_reset(SYSTEM_RESET_HARD); } } DECLARE_HOOK(HOOK_AC_CHANGE, fake_hibernate_ac_hook, HOOK_PRIO_DEFAULT); #endif void clock_init(void) { /* * The initial state : * SYSCLK from MSI (=2MHz), no divider on AHB, APB1, APB2 * PLL unlocked, RTC enabled on LSE */ /* Switch to high-speed oscillator */ clock_set_osc(1); } static void clock_chipset_startup(void) { /* Return to full speed */ clock_enable_module(MODULE_CHIPSET, 1); } DECLARE_HOOK(HOOK_CHIPSET_STARTUP, clock_chipset_startup, HOOK_PRIO_DEFAULT); DECLARE_HOOK(HOOK_CHIPSET_RESUME, clock_chipset_startup, HOOK_PRIO_DEFAULT); static void clock_chipset_shutdown(void) { /* Drop to lower clock speed if no other module requires full speed */ clock_enable_module(MODULE_CHIPSET, 0); } DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, clock_chipset_shutdown, HOOK_PRIO_DEFAULT); DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, clock_chipset_shutdown, HOOK_PRIO_DEFAULT); static int command_clock(int argc, char **argv) { if (argc >= 2) { if (!strcasecmp(argv[1], "hsi")) clock_set_osc(OSC_HSI); else if (!strcasecmp(argv[1], "msi")) clock_set_osc(OSC_MSI); else return EC_ERROR_PARAM1; } ccprintf("Clock frequency is now %d Hz\n", freq); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(clock, command_clock, "hsi | msi", "Set clock frequency");