197 lines
4.6 KiB
C
197 lines
4.6 KiB
C
/** @file rcc.c
|
|
* Module handling Reset and Clocks Control (RCC).
|
|
*
|
|
* The module provides functions to configure clocks according to presets as
|
|
* well as to enable/disable/reset peripherals.
|
|
*/
|
|
|
|
//--includes--------------------------------------------------------------------
|
|
|
|
#include "rcc.h"
|
|
#include "rcc_regs.h"
|
|
#include "flash.h"
|
|
|
|
|
|
//--local definitions-----------------------------------------------------------
|
|
|
|
#define AHB_MASK 0x00000557
|
|
#define APB1_MASK 0x3afec9ff
|
|
#define APB2_MASK 0x0038fffd
|
|
|
|
static void apply_default_preset(void);
|
|
static void apply_speed_preset(void);
|
|
|
|
|
|
//--local variables-------------------------------------------------------------
|
|
|
|
static volatile struct RCC* regs = (struct RCC*)RCC_BASE_ADDRESS;
|
|
static enum RccPreset current_preset = RCC_PRESET_DEFAULT;
|
|
|
|
|
|
//--public functions------------------------------------------------------------
|
|
|
|
/**
|
|
* Configures the clock sources, PLL, prescalers, etc. All peripherals are
|
|
* disabled prior to any modifications to avoid unwanted side effects.
|
|
*/
|
|
void rcc_configure(enum RccPreset preset)
|
|
{
|
|
//disable all peripherals during clock configuration
|
|
union RCC_APB1ENR apb1_enr = regs->APB1ENR;
|
|
regs->APB1ENR.word = 0x0;
|
|
union RCC_APB2ENR apb2_enr = regs->APB2ENR;
|
|
regs->APB2ENR.word = 0x0;
|
|
|
|
switch (preset) {
|
|
case RCC_PRESET_DEFAULT:
|
|
apply_default_preset();
|
|
current_preset = RCC_PRESET_DEFAULT;
|
|
break;
|
|
case RCC_PRESET_SPEED:
|
|
apply_speed_preset();
|
|
current_preset = RCC_PRESET_SPEED;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
//re-enable peripherals
|
|
regs->APB1ENR = apb1_enr;
|
|
regs->APB2ENR = apb2_enr;
|
|
}
|
|
|
|
void rcc_configure_lsi(bool enable)
|
|
{
|
|
regs->CSR.LSION = enable;
|
|
|
|
//ensure LSI is enabled
|
|
if (enable) {
|
|
while (regs->CSR.LSIRDY != 0x1) {}
|
|
}
|
|
}
|
|
|
|
void rcc_enable(enum RccAhb ahb_mask, enum RccApb1 apb1_mask,
|
|
enum RccApb2 apb2_mask)
|
|
{
|
|
regs->AHBENR.word |= ahb_mask & AHB_MASK;
|
|
regs->APB1ENR.word |= apb1_mask & APB1_MASK;
|
|
regs->APB2ENR.word |= apb2_mask & APB2_MASK;
|
|
}
|
|
|
|
void rcc_disable(enum RccAhb ahb_mask, enum RccApb1 apb1_mask,
|
|
enum RccApb2 apb2_mask)
|
|
{
|
|
regs->AHBENR.word &= !(ahb_mask & AHB_MASK);
|
|
regs->APB1ENR.word &= !(apb1_mask & APB1_MASK);
|
|
regs->APB2ENR.word &= !(apb2_mask & APB2_MASK);
|
|
}
|
|
|
|
void rcc_reset(enum RccApb1 apb1_mask, enum RccApb2 apb2_mask)
|
|
{
|
|
regs->APB1RSTR.word &= !(apb1_mask & APB1_MASK);
|
|
regs->APB2RSTR.word &= !(apb2_mask & APB2_MASK);
|
|
}
|
|
|
|
void rcc_get_clocks(struct RccClocks* clocks)
|
|
{
|
|
clocks->ahb_freq = 0;
|
|
switch (current_preset)
|
|
{
|
|
case RCC_PRESET_DEFAULT:
|
|
clocks->ahb_freq = 8000000;
|
|
clocks->apb1_freq = 8000000;
|
|
clocks->apb2_freq = 8000000;
|
|
clocks->tim_freq = 8000000;
|
|
break;
|
|
case RCC_PRESET_SPEED:
|
|
clocks->ahb_freq = 72000000;
|
|
clocks->apb1_freq = 36000000;
|
|
clocks->apb2_freq = 72000000;
|
|
clocks->tim_freq = 72000000;
|
|
break;
|
|
default:
|
|
//TODO hardfault
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
//--local functions-------------------------------------------------------------
|
|
|
|
/**
|
|
* Apply the default clock preset, using HSI whithout PLL or prescalers. This is
|
|
* similar to the reset configuration. Most buses run at 8Mhz, except ADCs wich
|
|
* run at 4Mhz, and Systick wich only reaches 1Mhz.
|
|
*/
|
|
static void apply_default_preset(void)
|
|
{
|
|
//ensures HSI is enabled
|
|
regs->CR.HSION = 1;
|
|
while (regs->CR.HSIRDY != 0x1);
|
|
|
|
//set HSI as main clock source and disable prescalers
|
|
regs->CFGR.word &= ~0x077fff3;
|
|
|
|
//disable all options
|
|
regs->CR.HSITRIM = 0;
|
|
regs->CR.HSITRIM = 0x10;
|
|
regs->CR.HSEON = 0;
|
|
regs->CR.HSEBYP = 0;
|
|
regs->CR.CCSON = 0;
|
|
regs->CR.PLLON = 0;
|
|
|
|
//disable all interrupts
|
|
regs->CIR.LSIRDYIE = 0;
|
|
regs->CIR.LSERDYIE = 0;
|
|
regs->CIR.HSIRDYIE = 0;
|
|
regs->CIR.HSERDYIE = 0;
|
|
regs->CIR.PLLRDYIE = 0;
|
|
|
|
//reconfigure flash
|
|
flash_configure(FLASH_PRESET_LOW_CLOCK_SPEED);
|
|
}
|
|
|
|
/**
|
|
* Apply the speed preset, using HSE with PLL to achieve 72MHz on AHB, APB2 and
|
|
* timer buses. APB1 is limited to its maximum clock of 36Mhz and the ADCs run
|
|
* at 12MHz. Systick runs at 9Mhz.
|
|
*/
|
|
static void apply_speed_preset(void)
|
|
{
|
|
//restore sane values
|
|
apply_default_preset();
|
|
|
|
//try enabling HSE, fallback to HSI if HSE fails
|
|
regs->CR.HSEON = 1;
|
|
for (uint32_t i=0; i<1000; ++i) {
|
|
__asm__("nop");
|
|
}
|
|
if (regs->CR.HSERDY == 0x1) {
|
|
regs->CFGR.PLLSCR = 1;
|
|
} else {
|
|
regs->CR.HSEON = 0;
|
|
}
|
|
|
|
//configure PLL, fallback to HSI if PLL fails
|
|
regs->CFGR.PLLMUL = 0x7; //PLL x9
|
|
regs->CR.PLLON = 1;
|
|
for (uint32_t i=0; i<1000; ++i) {
|
|
__asm__("nop");
|
|
}
|
|
if (regs->CR.PLLRDY != 0x1) {
|
|
regs->CR.PLLON = 0;
|
|
return; //clock low enough, no need for prescalers
|
|
}
|
|
|
|
//configure prescalers
|
|
regs->CFGR.PPRE1 = 0x4; // /2
|
|
regs->CFGR.ADCPRE = 0x2; // /6
|
|
|
|
//reconfigure flash
|
|
flash_configure(FLASH_PRESET_HIGH_CLOCK_SPEED);
|
|
|
|
//switch to PLL output
|
|
regs->CFGR.SW = 0x2;
|
|
}
|
|
|