| /* Copyright 2016 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" |
| |
| /* High-speed oscillator is 16 MHz */ |
| #define STM32_HSI_CLOCK 16000000 |
| /* Multi-speed oscillator is 4 MHz by default */ |
| #define STM32_MSI_CLOCK 4000000 |
| |
| enum clock_osc { |
| OSC_INIT = 0, /* Uninitialized */ |
| OSC_HSI, /* High-speed internal oscillator */ |
| OSC_MSI, /* Multi-speed internal oscillator */ |
| #ifdef STM32_HSE_CLOCK /* Allows us to catch absence of HSE at comiple time */ |
| OSC_HSE, /* High-speed external oscillator */ |
| #endif |
| OSC_PLL, /* PLL */ |
| }; |
| |
| static int freq = STM32_MSI_CLOCK; |
| static int current_osc; |
| |
| int clock_get_freq(void) |
| { |
| return freq; |
| } |
| |
| void clock_wait_bus_cycles(enum bus_type bus, uint32_t cycles) |
| { |
| volatile uint32_t dummy __attribute__((unused)); |
| |
| if (bus == BUS_AHB) { |
| while (cycles--) |
| dummy = STM32_DMA1_REGS->isr; |
| } else { /* APB */ |
| while (cycles--) |
| dummy = STM32_USART_BRR(STM32_USART1_BASE); |
| } |
| } |
| |
| static void clock_enable_osc(enum clock_osc osc) |
| { |
| uint32_t ready; |
| uint32_t on; |
| |
| switch (osc) { |
| case OSC_HSI: |
| ready = STM32_RCC_CR_HSIRDY; |
| on = STM32_RCC_CR_HSION; |
| break; |
| case OSC_MSI: |
| ready = STM32_RCC_CR_MSIRDY; |
| on = STM32_RCC_CR_MSION; |
| break; |
| #ifdef STM32_HSE_CLOCK |
| case OSC_HSE: |
| ready = STM32_RCC_CR_HSERDY; |
| on = STM32_RCC_CR_HSEON; |
| break; |
| #endif |
| case OSC_PLL: |
| ready = STM32_RCC_CR_PLLRDY; |
| on = STM32_RCC_CR_PLLON; |
| break; |
| default: |
| return; |
| } |
| |
| if (!(STM32_RCC_CR & ready)) { |
| /* Enable HSI */ |
| STM32_RCC_CR |= on; |
| /* Wait for HSI to be ready */ |
| while (!(STM32_RCC_CR & ready)) |
| ; |
| } |
| } |
| |
| /* Switch system clock oscillator */ |
| static void clock_switch_osc(enum clock_osc osc) |
| { |
| uint32_t sw; |
| uint32_t sws; |
| |
| switch (osc) { |
| case OSC_HSI: |
| sw = STM32_RCC_CFGR_SW_HSI; |
| sws = STM32_RCC_CFGR_SWS_HSI; |
| break; |
| case OSC_MSI: |
| sw = STM32_RCC_CFGR_SW_MSI; |
| sws = STM32_RCC_CFGR_SWS_MSI; |
| break; |
| #ifdef STM32_HSE_CLOCK |
| case OSC_HSE: |
| sw = STM32_RCC_CFGR_SW_HSE; |
| sws = STM32_RCC_CFGR_SWS_HSE; |
| break; |
| #endif |
| case OSC_PLL: |
| sw = STM32_RCC_CFGR_SW_PLL; |
| sws = STM32_RCC_CFGR_SWS_PLL; |
| break; |
| default: |
| return; |
| } |
| |
| STM32_RCC_CFGR = sw; |
| while ((STM32_RCC_CFGR & STM32_RCC_CFGR_SWS_MASK) != sws) |
| ; |
| } |
| |
| /* |
| * Configure PLL for HSE |
| * |
| * 1. Disable the PLL by setting PLLON to 0 in RCC_CR. |
| * 2. Wait until PLLRDY is cleared. The PLL is now fully stopped. |
| * 3. Change the desired parameter. |
| * 4. Enable the PLL again by setting PLLON to 1. |
| * 5. Enable the desired PLL outputs by configuring PLLPEN, PLLQEN, PLLREN |
| * in RCC_PLLCFGR. |
| */ |
| static int stm32_configure_pll(enum clock_osc osc, |
| uint8_t m, uint8_t n, uint8_t r) |
| { |
| uint32_t val; |
| int f; |
| |
| /* 1 */ |
| STM32_RCC_CR &= ~STM32_RCC_CR_PLLON; |
| |
| /* 2 */ |
| while (STM32_RCC_CR & STM32_RCC_CR_PLLRDY) |
| ; |
| |
| /* 3 */ |
| val = STM32_RCC_PLLCFGR; |
| |
| val &= ~STM32_RCC_PLLCFGR_PLLSRC_MASK; |
| switch (osc) { |
| case OSC_HSI: |
| val |= STM32_RCC_PLLCFGR_PLLSRC_HSI; |
| f = STM32_HSI_CLOCK; |
| break; |
| case OSC_MSI: |
| val |= STM32_RCC_PLLCFGR_PLLSRC_MSI; |
| f = STM32_MSI_CLOCK; |
| break; |
| #ifdef STM32_HSE_CLOCK |
| case OSC_HSE: |
| val |= STM32_RCC_PLLCFGR_PLLSRC_HSE; |
| f = STM32_HSE_CLOCK; |
| break; |
| #endif |
| default: |
| return -1; |
| } |
| |
| ASSERT(m > 0 && m < 9); |
| val &= ~STM32_RCC_PLLCFGR_PLLM_MASK; |
| val |= (m - 1) << STM32_RCC_PLLCFGR_PLLM_SHIFT; |
| |
| /* Max and min values are from TRM */ |
| ASSERT(n > 7 && n < 87); |
| val &= ~STM32_RCC_PLLCFGR_PLLN_MASK; |
| val |= n << STM32_RCC_PLLCFGR_PLLN_SHIFT; |
| |
| val &= ~STM32_RCC_PLLCFGR_PLLR_MASK; |
| switch (r) { |
| case 2: |
| val |= 0 << STM32_RCC_PLLCFGR_PLLR_SHIFT; |
| break; |
| case 4: |
| val |= 1 << STM32_RCC_PLLCFGR_PLLR_SHIFT; |
| break; |
| case 6: |
| val |= 2 << STM32_RCC_PLLCFGR_PLLR_SHIFT; |
| break; |
| case 8: |
| val |= 3 << STM32_RCC_PLLCFGR_PLLR_SHIFT; |
| break; |
| default: |
| return -1; |
| } |
| |
| STM32_RCC_PLLCFGR = val; |
| |
| /* 4 */ |
| clock_enable_osc(OSC_PLL); |
| |
| /* 5 */ |
| val = STM32_RCC_PLLCFGR; |
| val |= 1 << STM32_RCC_PLLCFGR_PLLREN_SHIFT; |
| STM32_RCC_PLLCFGR = val; |
| |
| /* (f * n) shouldn't overflow based on their max values */ |
| return (f * n / m / r); |
| } |
| |
| /** |
| * Set system clock oscillator |
| * |
| * @param osc Oscillator to use |
| * @param pll_osc Source oscillator for PLL. Ignored if osc is not PLL. |
| */ |
| static void clock_set_osc(enum clock_osc osc, enum clock_osc pll_osc) |
| { |
| uint32_t val; |
| |
| 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 */ |
| clock_enable_osc(osc); |
| |
| /* Disable LPSDSR */ |
| STM32_PWR_CR &= ~STM32_PWR_CR_LPSDSR; |
| |
| /* Switch to HSI */ |
| clock_switch_osc(osc); |
| |
| /* Disable MSI */ |
| STM32_RCC_CR &= ~STM32_RCC_CR_MSION; |
| |
| freq = STM32_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 */ |
| clock_enable_osc(osc); |
| |
| /* Switch to MSI */ |
| clock_switch_osc(osc); |
| |
| /* Disable HSI */ |
| STM32_RCC_CR &= ~STM32_RCC_CR_HSION; |
| |
| /* Enable LPSDSR */ |
| STM32_PWR_CR |= STM32_PWR_CR_LPSDSR; |
| |
| freq = STM32_MSI_CLOCK; |
| break; |
| |
| #ifdef STM32_HSE_CLOCK |
| case OSC_HSE: |
| /* Ensure that HSE is stable */ |
| clock_enable_osc(osc); |
| |
| /* Switch to HSE */ |
| clock_switch_osc(osc); |
| |
| /* Disable other clock sources */ |
| STM32_RCC_CR &= ~(STM32_RCC_CR_MSION | STM32_RCC_CR_HSION | |
| STM32_RCC_CR_PLLON); |
| |
| freq = STM32_HSE_CLOCK; |
| |
| break; |
| #endif |
| case OSC_PLL: |
| /* Ensure that source clock is stable */ |
| clock_enable_osc(pll_osc); |
| |
| /* Configure PLLCFGR */ |
| freq = stm32_configure_pll(pll_osc, STM32_PLLM, |
| STM32_PLLN, STM32_PLLR); |
| ASSERT(freq > 0); |
| |
| /* Adjust flash latency as instructed in TRM */ |
| val = STM32_FLASH_ACR; |
| val &= ~STM32_FLASH_ACR_LATENCY_MASK; |
| /* Flash 4 wait state. TODO: Should depend on freq. */ |
| val |= 4 << STM32_FLASH_ACR_LATENCY_SHIFT; |
| STM32_FLASH_ACR = val; |
| while (STM32_FLASH_ACR != val) |
| ; |
| |
| /* Switch to PLL */ |
| clock_switch_osc(osc); |
| |
| /* TODO: Disable other sources */ |
| 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; |
| } |
| } |
| |
| void clock_enable_module(enum module_id module, int enable) |
| { |
| static uint32_t clock_mask; |
| int new_mask; |
| |
| if (enable) |
| new_mask = clock_mask | (1 << module); |
| else |
| new_mask = clock_mask & ~(1 << 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, OSC_INIT); |
| } |
| |
| clock_mask = new_mask; |
| } |
| |
| void clock_init(void) |
| { |
| #ifdef STM32_HSE_CLOCK |
| clock_set_osc(OSC_PLL, OSC_HSE); |
| #else |
| clock_set_osc(OSC_HSI, OSC_INIT); |
| #endif |
| } |
| |
| 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, OSC_INIT); |
| else if (!strcasecmp(argv[1], "msi")) |
| clock_set_osc(OSC_MSI, OSC_INIT); |
| #ifdef STM32_HSE_CLOCK |
| else if (!strcasecmp(argv[1], "hse")) |
| clock_set_osc(OSC_HSE, OSC_INIT); |
| else if (!strcasecmp(argv[1], "pll")) |
| clock_set_osc(OSC_PLL, OSC_HSE); |
| #else |
| else if (!strcasecmp(argv[1], "pll")) |
| clock_set_osc(OSC_PLL, OSC_HSI); |
| #endif |
| 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" |
| #ifdef STM32_HSE_CLOCK |
| " | hse | pll" |
| #endif |
| , |
| "Set clock frequency"); |