This commit addresses #3256 by disabling dma NVIC interrupt enablement at startup. Instead, per-channel NVIC interrupt enablement is now done with the rest of the dma channel configuration. This ensures that each core will only handle the interrupts of the DMA channels that it uses.
670 lines
20 KiB
Rust
670 lines
20 KiB
Rust
#[cfg(any(stm32l0, stm32l1))]
|
|
pub use crate::pac::pwr::vals::Vos as VoltageScale;
|
|
use crate::pac::rcc::regs::Cfgr;
|
|
#[cfg(any(stm32wb, stm32wl))]
|
|
pub use crate::pac::rcc::vals::Hsepre as HsePrescaler;
|
|
pub use crate::pac::rcc::vals::{Hpre as AHBPrescaler, Msirange as MSIRange, Ppre as APBPrescaler, Sw as Sysclk};
|
|
use crate::pac::{FLASH, RCC};
|
|
use crate::time::Hertz;
|
|
|
|
/// HSI speed
|
|
pub const HSI_FREQ: Hertz = Hertz(16_000_000);
|
|
|
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
|
pub enum HseMode {
|
|
/// crystal/ceramic oscillator (HSEBYP=0)
|
|
Oscillator,
|
|
/// external analog clock (low swing) (HSEBYP=1)
|
|
Bypass,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
|
pub struct Hse {
|
|
/// HSE frequency.
|
|
pub freq: Hertz,
|
|
/// HSE mode.
|
|
pub mode: HseMode,
|
|
/// HSE prescaler
|
|
#[cfg(any(stm32wb, stm32wl))]
|
|
pub prescaler: HsePrescaler,
|
|
}
|
|
|
|
/// Clocks configuration
|
|
#[derive(Clone, Copy)]
|
|
pub struct Config {
|
|
// base clock sources
|
|
pub msi: Option<MSIRange>,
|
|
pub hsi: bool,
|
|
pub hse: Option<Hse>,
|
|
#[cfg(crs)]
|
|
pub hsi48: Option<super::Hsi48Config>,
|
|
|
|
// pll
|
|
pub pll: Option<Pll>,
|
|
#[cfg(any(stm32l4, stm32l5, stm32wb))]
|
|
pub pllsai1: Option<Pll>,
|
|
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
|
pub pllsai2: Option<Pll>,
|
|
|
|
// sysclk, buses.
|
|
pub sys: Sysclk,
|
|
pub ahb_pre: AHBPrescaler,
|
|
pub apb1_pre: APBPrescaler,
|
|
#[cfg(not(stm32u0))]
|
|
pub apb2_pre: APBPrescaler,
|
|
#[cfg(any(stm32wl5x, stm32wb))]
|
|
pub core2_ahb_pre: AHBPrescaler,
|
|
#[cfg(any(stm32wl, stm32wb))]
|
|
pub shared_ahb_pre: AHBPrescaler,
|
|
|
|
// low speed LSI/LSE/RTC
|
|
pub ls: super::LsConfig,
|
|
|
|
#[cfg(any(stm32l0, stm32l1))]
|
|
pub voltage_scale: VoltageScale,
|
|
|
|
/// Per-peripheral kernel clock selection muxes
|
|
pub mux: super::mux::ClockMux,
|
|
}
|
|
|
|
impl Default for Config {
|
|
#[inline]
|
|
fn default() -> Config {
|
|
Config {
|
|
hse: None,
|
|
hsi: false,
|
|
msi: Some(MSIRange::RANGE4M),
|
|
sys: Sysclk::MSI,
|
|
ahb_pre: AHBPrescaler::DIV1,
|
|
apb1_pre: APBPrescaler::DIV1,
|
|
#[cfg(not(stm32u0))]
|
|
apb2_pre: APBPrescaler::DIV1,
|
|
#[cfg(any(stm32wl5x, stm32wb))]
|
|
core2_ahb_pre: AHBPrescaler::DIV1,
|
|
#[cfg(any(stm32wl, stm32wb))]
|
|
shared_ahb_pre: AHBPrescaler::DIV1,
|
|
pll: None,
|
|
#[cfg(any(stm32l4, stm32l5, stm32wb))]
|
|
pllsai1: None,
|
|
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
|
pllsai2: None,
|
|
#[cfg(crs)]
|
|
hsi48: Some(Default::default()),
|
|
ls: Default::default(),
|
|
#[cfg(any(stm32l0, stm32l1))]
|
|
voltage_scale: VoltageScale::RANGE1,
|
|
mux: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(stm32wb)]
|
|
pub const WPAN_DEFAULT: Config = Config {
|
|
hse: Some(Hse {
|
|
freq: Hertz(32_000_000),
|
|
mode: HseMode::Oscillator,
|
|
prescaler: HsePrescaler::DIV1,
|
|
}),
|
|
sys: Sysclk::PLL1_R,
|
|
#[cfg(crs)]
|
|
hsi48: Some(super::Hsi48Config { sync_from_usb: false }),
|
|
msi: None,
|
|
hsi: false,
|
|
|
|
ls: super::LsConfig::default_lse(),
|
|
|
|
pll: Some(Pll {
|
|
source: PllSource::HSE,
|
|
prediv: PllPreDiv::DIV2,
|
|
mul: PllMul::MUL12,
|
|
divp: Some(PllPDiv::DIV3), // 32 / 2 * 12 / 3 = 64Mhz
|
|
divq: Some(PllQDiv::DIV4), // 32 / 2 * 12 / 4 = 48Mhz
|
|
divr: Some(PllRDiv::DIV3), // 32 / 2 * 12 / 3 = 64Mhz
|
|
}),
|
|
pllsai1: None,
|
|
|
|
ahb_pre: AHBPrescaler::DIV1,
|
|
core2_ahb_pre: AHBPrescaler::DIV2,
|
|
shared_ahb_pre: AHBPrescaler::DIV1,
|
|
apb1_pre: APBPrescaler::DIV1,
|
|
apb2_pre: APBPrescaler::DIV1,
|
|
|
|
mux: super::mux::ClockMux::default(),
|
|
};
|
|
|
|
fn msi_enable(range: MSIRange) {
|
|
#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl, stm32u0))]
|
|
RCC.cr().modify(|w| {
|
|
#[cfg(not(stm32wb))]
|
|
w.set_msirgsel(crate::pac::rcc::vals::Msirgsel::CR);
|
|
w.set_msirange(range);
|
|
w.set_msipllen(false);
|
|
});
|
|
#[cfg(any(stm32l0, stm32l1))]
|
|
RCC.icscr().modify(|w| w.set_msirange(range));
|
|
|
|
RCC.cr().modify(|w| w.set_msion(true));
|
|
while !RCC.cr().read().msirdy() {}
|
|
}
|
|
|
|
pub(crate) unsafe fn init(config: Config) {
|
|
// Switch to MSI to prevent problems with PLL configuration.
|
|
if !RCC.cr().read().msion() {
|
|
// Turn on MSI and configure it to 4MHz.
|
|
msi_enable(MSIRange::RANGE4M)
|
|
}
|
|
if RCC.cfgr().read().sws() != Sysclk::MSI {
|
|
// Set MSI as a clock source, reset prescalers.
|
|
RCC.cfgr().write_value(Cfgr::default());
|
|
// Wait for clock switch status bits to change.
|
|
while RCC.cfgr().read().sws() != Sysclk::MSI {}
|
|
}
|
|
|
|
#[cfg(stm32wl)]
|
|
{
|
|
// Set max latency
|
|
FLASH.acr().modify(|w| w.set_prften(true));
|
|
FLASH.acr().modify(|w| w.set_latency(2));
|
|
}
|
|
|
|
// Set voltage scale
|
|
#[cfg(any(stm32l0, stm32l1))]
|
|
{
|
|
while crate::pac::PWR.csr().read().vosf() {}
|
|
crate::pac::PWR.cr().write(|w| w.set_vos(config.voltage_scale));
|
|
while crate::pac::PWR.csr().read().vosf() {}
|
|
}
|
|
|
|
#[cfg(stm32l5)]
|
|
crate::pac::PWR.cr1().modify(|w| {
|
|
w.set_vos(crate::pac::pwr::vals::Vos::RANGE0);
|
|
});
|
|
|
|
let rtc = config.ls.init();
|
|
|
|
let msi = config.msi.map(|range| {
|
|
msi_enable(range);
|
|
msirange_to_hertz(range)
|
|
});
|
|
|
|
// If LSE is enabled and the right freq, enable calibration of MSI
|
|
#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))]
|
|
if config.ls.lse.map(|x| x.frequency) == Some(Hertz(32_768)) {
|
|
RCC.cr().modify(|w| w.set_msipllen(true));
|
|
}
|
|
|
|
let hsi = config.hsi.then(|| {
|
|
RCC.cr().modify(|w| w.set_hsion(true));
|
|
while !RCC.cr().read().hsirdy() {}
|
|
|
|
HSI_FREQ
|
|
});
|
|
|
|
let hse = config.hse.map(|hse| {
|
|
RCC.cr().modify(|w| {
|
|
#[cfg(stm32wl)]
|
|
w.set_hsebyppwr(hse.mode == HseMode::Bypass);
|
|
#[cfg(not(stm32wl))]
|
|
w.set_hsebyp(hse.mode == HseMode::Bypass);
|
|
w.set_hseon(true);
|
|
});
|
|
while !RCC.cr().read().hserdy() {}
|
|
|
|
hse.freq
|
|
});
|
|
|
|
#[cfg(crs)]
|
|
let hsi48 = config.hsi48.map(|config| super::init_hsi48(config));
|
|
#[cfg(not(crs))]
|
|
let hsi48: Option<Hertz> = None;
|
|
|
|
let _plls = [
|
|
&config.pll,
|
|
#[cfg(any(stm32l4, stm32l5, stm32wb))]
|
|
&config.pllsai1,
|
|
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
|
&config.pllsai2,
|
|
];
|
|
|
|
// L4 has shared PLLSRC, PLLM, check it's equal in all PLLs.
|
|
#[cfg(all(stm32l4, not(rcc_l4plus)))]
|
|
match super::util::get_equal(_plls.into_iter().flatten().map(|p| (p.source, p.prediv))) {
|
|
Err(()) => panic!("Source must be equal across all enabled PLLs."),
|
|
Ok(None) => {}
|
|
Ok(Some((source, prediv))) => RCC.pllcfgr().write(|w| {
|
|
w.set_pllm(prediv);
|
|
w.set_pllsrc(source);
|
|
}),
|
|
};
|
|
|
|
// L4+, WL has shared PLLSRC, check it's equal in all PLLs.
|
|
#[cfg(any(rcc_l4plus, stm32wl))]
|
|
match super::util::get_equal(_plls.into_iter().flatten().map(|p| p.source)) {
|
|
Err(()) => panic!("Source must be equal across all enabled PLLs."),
|
|
Ok(None) => {}
|
|
Ok(Some(source)) => RCC.pllcfgr().write(|w| {
|
|
w.set_pllsrc(source);
|
|
}),
|
|
};
|
|
|
|
let pll_input = PllInput {
|
|
hse,
|
|
hsi,
|
|
#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl, stm32u0))]
|
|
msi,
|
|
};
|
|
let pll = init_pll(PllInstance::Pll, config.pll, &pll_input);
|
|
#[cfg(any(stm32l4, stm32l5, stm32wb))]
|
|
let pllsai1 = init_pll(PllInstance::Pllsai1, config.pllsai1, &pll_input);
|
|
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
|
let pllsai2 = init_pll(PllInstance::Pllsai2, config.pllsai2, &pll_input);
|
|
|
|
let sys_clk = match config.sys {
|
|
Sysclk::HSE => hse.unwrap(),
|
|
Sysclk::HSI => hsi.unwrap(),
|
|
Sysclk::MSI => msi.unwrap(),
|
|
Sysclk::PLL1_R => pll.r.unwrap(),
|
|
#[cfg(stm32u0)]
|
|
Sysclk::LSI | Sysclk::LSE => todo!(),
|
|
#[cfg(stm32u0)]
|
|
Sysclk::_RESERVED_6 | Sysclk::_RESERVED_7 => unreachable!(),
|
|
};
|
|
|
|
#[cfg(rcc_l4plus)]
|
|
assert!(sys_clk.0 <= 120_000_000);
|
|
#[cfg(all(stm32l4, not(rcc_l4plus)))]
|
|
assert!(sys_clk.0 <= 80_000_000);
|
|
|
|
let hclk1 = sys_clk / config.ahb_pre;
|
|
let (pclk1, pclk1_tim) = super::util::calc_pclk(hclk1, config.apb1_pre);
|
|
#[cfg(not(stm32u0))]
|
|
let (pclk2, pclk2_tim) = super::util::calc_pclk(hclk1, config.apb2_pre);
|
|
#[cfg(any(stm32l4, stm32l5, stm32wlex))]
|
|
let hclk2 = hclk1;
|
|
#[cfg(any(stm32wl5x, stm32wb))]
|
|
let hclk2 = sys_clk / config.core2_ahb_pre;
|
|
#[cfg(any(stm32l4, stm32l5, stm32wlex))]
|
|
let hclk3 = hclk1;
|
|
#[cfg(any(stm32wl5x, stm32wb))]
|
|
let hclk3 = sys_clk / config.shared_ahb_pre;
|
|
|
|
// Set flash wait states
|
|
#[cfg(any(stm32l0, stm32l1))]
|
|
let latency = match (config.voltage_scale, sys_clk.0) {
|
|
(VoltageScale::RANGE1, ..=16_000_000) => false,
|
|
(VoltageScale::RANGE2, ..=8_000_000) => false,
|
|
(VoltageScale::RANGE3, ..=4_200_000) => false,
|
|
_ => true,
|
|
};
|
|
#[cfg(stm32l4)]
|
|
let latency = match hclk1.0 {
|
|
0..=16_000_000 => 0,
|
|
0..=32_000_000 => 1,
|
|
0..=48_000_000 => 2,
|
|
0..=64_000_000 => 3,
|
|
_ => 4,
|
|
};
|
|
#[cfg(stm32l5)]
|
|
let latency = match hclk1.0 {
|
|
// VCORE Range 0 (performance), others TODO
|
|
0..=20_000_000 => 0,
|
|
0..=40_000_000 => 1,
|
|
0..=60_000_000 => 2,
|
|
0..=80_000_000 => 3,
|
|
0..=100_000_000 => 4,
|
|
_ => 5,
|
|
};
|
|
#[cfg(stm32wl)]
|
|
let latency = match hclk3.0 {
|
|
// VOS RANGE1, others TODO.
|
|
..=18_000_000 => 0,
|
|
..=36_000_000 => 1,
|
|
_ => 2,
|
|
};
|
|
#[cfg(stm32wb)]
|
|
let latency = match hclk3.0 {
|
|
// VOS RANGE1, others TODO.
|
|
..=18_000_000 => 0,
|
|
..=36_000_000 => 1,
|
|
..=54_000_000 => 2,
|
|
..=64_000_000 => 3,
|
|
_ => 4,
|
|
};
|
|
#[cfg(stm32u0)]
|
|
let latency = match hclk1.0 {
|
|
// VOS RANGE1, others TODO.
|
|
..=24_000_000 => 0,
|
|
..=48_000_000 => 1,
|
|
_ => 2,
|
|
};
|
|
|
|
#[cfg(stm32l1)]
|
|
FLASH.acr().write(|w| w.set_acc64(true));
|
|
#[cfg(not(stm32l5))]
|
|
FLASH.acr().modify(|w| w.set_prften(true));
|
|
FLASH.acr().modify(|w| w.set_latency(latency));
|
|
while FLASH.acr().read().latency() != latency {}
|
|
|
|
RCC.cfgr().modify(|w| {
|
|
w.set_sw(config.sys);
|
|
w.set_hpre(config.ahb_pre);
|
|
#[cfg(stm32u0)]
|
|
w.set_ppre(config.apb1_pre);
|
|
#[cfg(not(stm32u0))]
|
|
w.set_ppre1(config.apb1_pre);
|
|
#[cfg(not(stm32u0))]
|
|
w.set_ppre2(config.apb2_pre);
|
|
});
|
|
while RCC.cfgr().read().sws() != config.sys {}
|
|
|
|
#[cfg(any(stm32wl, stm32wb))]
|
|
{
|
|
RCC.extcfgr().modify(|w| {
|
|
w.set_shdhpre(config.shared_ahb_pre);
|
|
#[cfg(any(stm32wl5x, stm32wb))]
|
|
w.set_c2hpre(config.core2_ahb_pre);
|
|
});
|
|
while !RCC.extcfgr().read().shdhpref() {}
|
|
#[cfg(any(stm32wl5x, stm32wb))]
|
|
while !RCC.extcfgr().read().c2hpref() {}
|
|
}
|
|
|
|
config.mux.init();
|
|
|
|
set_clocks!(
|
|
sys: Some(sys_clk),
|
|
hclk1: Some(hclk1),
|
|
#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))]
|
|
hclk2: Some(hclk2),
|
|
#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))]
|
|
hclk3: Some(hclk3),
|
|
pclk1: Some(pclk1),
|
|
#[cfg(not(stm32u0))]
|
|
pclk2: Some(pclk2),
|
|
pclk1_tim: Some(pclk1_tim),
|
|
#[cfg(not(stm32u0))]
|
|
pclk2_tim: Some(pclk2_tim),
|
|
#[cfg(stm32wl)]
|
|
pclk3: Some(hclk3),
|
|
hsi: hsi,
|
|
hse: hse,
|
|
msi: msi,
|
|
hsi48: hsi48,
|
|
|
|
#[cfg(any(stm32l0, stm32l1))]
|
|
pll1_vco_div_2: pll.vco.map(|c| c/2u32),
|
|
|
|
#[cfg(not(any(stm32l0, stm32l1)))]
|
|
pll1_p: pll.p,
|
|
#[cfg(not(any(stm32l0, stm32l1)))]
|
|
pll1_q: pll.q,
|
|
pll1_r: pll.r,
|
|
|
|
#[cfg(any(stm32l4, stm32l5, stm32wb))]
|
|
pllsai1_p: pllsai1.p,
|
|
#[cfg(any(stm32l4, stm32l5, stm32wb))]
|
|
pllsai1_q: pllsai1.q,
|
|
#[cfg(any(stm32l4, stm32l5, stm32wb))]
|
|
pllsai1_r: pllsai1.r,
|
|
|
|
#[cfg(not(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5)))]
|
|
pllsai2_p: None,
|
|
#[cfg(not(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5)))]
|
|
pllsai2_q: None,
|
|
#[cfg(not(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5)))]
|
|
pllsai2_r: None,
|
|
|
|
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
|
pllsai2_p: pllsai2.p,
|
|
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
|
pllsai2_q: pllsai2.q,
|
|
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
|
pllsai2_r: pllsai2.r,
|
|
|
|
#[cfg(dsihost)]
|
|
dsi_phy: None, // DSI PLL clock not supported, don't call `RccPeripheral::frequency()` in the drivers
|
|
|
|
rtc: rtc,
|
|
|
|
// TODO
|
|
sai1_extclk: None,
|
|
sai2_extclk: None,
|
|
lsi: None,
|
|
lse: None,
|
|
);
|
|
}
|
|
|
|
#[cfg(any(stm32l0, stm32l1))]
|
|
fn msirange_to_hertz(range: MSIRange) -> Hertz {
|
|
Hertz(32_768 * (1 << (range as u8 + 1)))
|
|
}
|
|
|
|
#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl, stm32u0))]
|
|
fn msirange_to_hertz(range: MSIRange) -> Hertz {
|
|
match range {
|
|
MSIRange::RANGE100K => Hertz(100_000),
|
|
MSIRange::RANGE200K => Hertz(200_000),
|
|
MSIRange::RANGE400K => Hertz(400_000),
|
|
MSIRange::RANGE800K => Hertz(800_000),
|
|
MSIRange::RANGE1M => Hertz(1_000_000),
|
|
MSIRange::RANGE2M => Hertz(2_000_000),
|
|
MSIRange::RANGE4M => Hertz(4_000_000),
|
|
MSIRange::RANGE8M => Hertz(8_000_000),
|
|
MSIRange::RANGE16M => Hertz(16_000_000),
|
|
MSIRange::RANGE24M => Hertz(24_000_000),
|
|
MSIRange::RANGE32M => Hertz(32_000_000),
|
|
MSIRange::RANGE48M => Hertz(48_000_000),
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
#[derive(PartialEq, Eq, Clone, Copy)]
|
|
enum PllInstance {
|
|
Pll,
|
|
#[cfg(any(stm32l4, stm32l5, stm32wb))]
|
|
Pllsai1,
|
|
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
|
Pllsai2,
|
|
}
|
|
|
|
fn pll_enable(instance: PllInstance, enabled: bool) {
|
|
match instance {
|
|
PllInstance::Pll => {
|
|
RCC.cr().modify(|w| w.set_pllon(enabled));
|
|
while RCC.cr().read().pllrdy() != enabled {}
|
|
}
|
|
#[cfg(any(stm32l4, stm32l5, stm32wb))]
|
|
PllInstance::Pllsai1 => {
|
|
RCC.cr().modify(|w| w.set_pllsai1on(enabled));
|
|
while RCC.cr().read().pllsai1rdy() != enabled {}
|
|
}
|
|
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
|
PllInstance::Pllsai2 => {
|
|
RCC.cr().modify(|w| w.set_pllsai2on(enabled));
|
|
while RCC.cr().read().pllsai2rdy() != enabled {}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub use pll::*;
|
|
|
|
#[cfg(any(stm32l0, stm32l1))]
|
|
mod pll {
|
|
use super::{pll_enable, PllInstance};
|
|
pub use crate::pac::rcc::vals::{Plldiv as PllDiv, Pllmul as PllMul, Pllsrc as PllSource};
|
|
use crate::pac::RCC;
|
|
use crate::time::Hertz;
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub struct Pll {
|
|
/// PLL source
|
|
pub source: PllSource,
|
|
|
|
/// PLL multiplication factor.
|
|
pub mul: PllMul,
|
|
|
|
/// PLL main output division factor.
|
|
pub div: PllDiv,
|
|
}
|
|
|
|
pub(super) struct PllInput {
|
|
pub hsi: Option<Hertz>,
|
|
pub hse: Option<Hertz>,
|
|
}
|
|
|
|
#[allow(unused)]
|
|
#[derive(Default)]
|
|
pub(super) struct PllOutput {
|
|
pub r: Option<Hertz>,
|
|
pub vco: Option<Hertz>,
|
|
}
|
|
|
|
pub(super) fn init_pll(instance: PllInstance, config: Option<Pll>, input: &PllInput) -> PllOutput {
|
|
// Disable PLL
|
|
pll_enable(instance, false);
|
|
|
|
let Some(pll) = config else { return PllOutput::default() };
|
|
|
|
let pll_src = match pll.source {
|
|
PllSource::HSE => unwrap!(input.hse),
|
|
PllSource::HSI => unwrap!(input.hsi),
|
|
};
|
|
|
|
let vco_freq = pll_src * pll.mul;
|
|
|
|
let r = vco_freq / pll.div;
|
|
|
|
assert!(r <= Hertz(32_000_000));
|
|
|
|
RCC.cfgr().write(move |w| {
|
|
w.set_pllmul(pll.mul);
|
|
w.set_plldiv(pll.div);
|
|
w.set_pllsrc(pll.source);
|
|
});
|
|
|
|
// Enable PLL
|
|
pll_enable(instance, true);
|
|
|
|
PllOutput {
|
|
r: Some(r),
|
|
vco: Some(vco_freq),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl, stm32u0))]
|
|
mod pll {
|
|
use super::{pll_enable, PllInstance};
|
|
pub use crate::pac::rcc::vals::{
|
|
Pllm as PllPreDiv, Plln as PllMul, Pllp as PllPDiv, Pllq as PllQDiv, Pllr as PllRDiv, Pllsrc as PllSource,
|
|
};
|
|
use crate::pac::RCC;
|
|
use crate::time::Hertz;
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub struct Pll {
|
|
/// PLL source
|
|
pub source: PllSource,
|
|
|
|
/// PLL pre-divider (DIVM).
|
|
pub prediv: PllPreDiv,
|
|
|
|
/// PLL multiplication factor.
|
|
pub mul: PllMul,
|
|
|
|
/// PLL P division factor. If None, PLL P output is disabled.
|
|
pub divp: Option<PllPDiv>,
|
|
/// PLL Q division factor. If None, PLL Q output is disabled.
|
|
pub divq: Option<PllQDiv>,
|
|
/// PLL R division factor. If None, PLL R output is disabled.
|
|
pub divr: Option<PllRDiv>,
|
|
}
|
|
|
|
pub(super) struct PllInput {
|
|
pub hsi: Option<Hertz>,
|
|
pub hse: Option<Hertz>,
|
|
pub msi: Option<Hertz>,
|
|
}
|
|
|
|
#[allow(unused)]
|
|
#[derive(Default)]
|
|
pub(super) struct PllOutput {
|
|
pub p: Option<Hertz>,
|
|
pub q: Option<Hertz>,
|
|
pub r: Option<Hertz>,
|
|
}
|
|
|
|
pub(super) fn init_pll(instance: PllInstance, config: Option<Pll>, input: &PllInput) -> PllOutput {
|
|
// Disable PLL
|
|
pll_enable(instance, false);
|
|
|
|
let Some(pll) = config else { return PllOutput::default() };
|
|
|
|
let pll_src = match pll.source {
|
|
PllSource::DISABLE => panic!("must not select PLL source as DISABLE"),
|
|
PllSource::HSE => unwrap!(input.hse),
|
|
PllSource::HSI => unwrap!(input.hsi),
|
|
PllSource::MSI => unwrap!(input.msi),
|
|
};
|
|
|
|
let vco_freq = pll_src / pll.prediv * pll.mul;
|
|
|
|
let p = pll.divp.map(|div| vco_freq / div);
|
|
let q = pll.divq.map(|div| vco_freq / div);
|
|
let r = pll.divr.map(|div| vco_freq / div);
|
|
|
|
#[cfg(stm32l5)]
|
|
if instance == PllInstance::Pllsai2 {
|
|
assert!(q.is_none(), "PLLSAI2_Q is not available on L5");
|
|
assert!(r.is_none(), "PLLSAI2_R is not available on L5");
|
|
}
|
|
|
|
macro_rules! write_fields {
|
|
($w:ident) => {
|
|
$w.set_plln(pll.mul);
|
|
if let Some(divp) = pll.divp {
|
|
$w.set_pllp(divp);
|
|
$w.set_pllpen(true);
|
|
}
|
|
if let Some(divq) = pll.divq {
|
|
$w.set_pllq(divq);
|
|
$w.set_pllqen(true);
|
|
}
|
|
if let Some(divr) = pll.divr {
|
|
$w.set_pllr(divr);
|
|
$w.set_pllren(true);
|
|
}
|
|
};
|
|
}
|
|
|
|
match instance {
|
|
PllInstance::Pll => RCC.pllcfgr().write(|w| {
|
|
w.set_pllm(pll.prediv);
|
|
w.set_pllsrc(pll.source);
|
|
write_fields!(w);
|
|
}),
|
|
#[cfg(any(stm32l4, stm32l5, stm32wb))]
|
|
PllInstance::Pllsai1 => RCC.pllsai1cfgr().write(|w| {
|
|
#[cfg(any(rcc_l4plus, stm32l5))]
|
|
w.set_pllm(pll.prediv);
|
|
#[cfg(stm32l5)]
|
|
w.set_pllsrc(pll.source);
|
|
write_fields!(w);
|
|
}),
|
|
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
|
PllInstance::Pllsai2 => RCC.pllsai2cfgr().write(|w| {
|
|
#[cfg(any(rcc_l4plus, stm32l5))]
|
|
w.set_pllm(pll.prediv);
|
|
#[cfg(stm32l5)]
|
|
w.set_pllsrc(pll.source);
|
|
write_fields!(w);
|
|
}),
|
|
}
|
|
|
|
// Enable PLL
|
|
pll_enable(instance, true);
|
|
|
|
PllOutput { p, q, r }
|
|
}
|
|
}
|