A while back, macros had to be put in place to avoid letting the compiler directly use the bitfields. This was necessary because the compiler used strb instruction which only write bytes. On the AHB bus, byte writes are transformed into word writes by repeating the byte, which caused mayhem in the registers. After a lot of research, turns out the packed attribute stops the compiler from does optimal (word) writes and isn't needed anyway. Removing them fixes the issue
184 lines
4.4 KiB
C
184 lines
4.4 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_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)
|
|
{
|
|
switch (current_preset)
|
|
{
|
|
case RCC_PRESET_DEFAULT:
|
|
clocks->ahb_freq = 8000000;
|
|
clocks->apb1_freq = 8000000;
|
|
clocks->apb2_freq = 8000000;
|
|
break;
|
|
case RCC_PRESET_SPEED:
|
|
clocks->ahb_freq = 72000000;
|
|
clocks->apb1_freq = 36000000;
|
|
clocks->apb2_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;
|
|
}
|
|
|