use cortex_m::{asm, peripheral::SCB};
use crate::{
pac,
rcc::{ClockSrc, Clocks, Enable, PLLSource, Rcc},
};
pub struct PWR(pac::PWR);
impl PWR {
pub fn new(pwr: pac::PWR, rcc: &mut Rcc) -> Self {
pac::PWR::enable(rcc);
pwr.cr.modify(|_, w| w.dbp().set_bit());
Self(pwr)
}
pub fn switch_vcore_range(&mut self, range: VcoreRange) {
while self.0.csr.read().vosf().bit_is_set() {}
self.0
.cr
.modify(|_, w| unsafe { w.vos().bits(range as u8) });
while self.0.csr.read().vosf().bit_is_set() {}
}
pub fn get_vcore_range(&mut self) -> VcoreRange {
let vos = self.0.cr.read().vos().bits();
VcoreRange::from_bits(vos)
}
pub fn enter_low_power_run_mode(&mut self, clocks: Clocks) {
assert!(clocks.sys_clk().0 <= 131_072);
self.switch_vcore_range(VcoreRange::Range2);
self.set_lpsdsr();
self.0.cr.modify(|_, w| w.lprun().set_bit());
}
pub fn exit_low_power_run_mode(&mut self) {
self.0.cr.modify(|_, w| w.lprun().clear_bit());
self.clear_lpsdsr();
}
pub fn sleep_mode<'r>(&'r mut self, scb: &'r mut SCB) -> SleepMode<'r> {
SleepMode { pwr: self, scb }
}
pub fn low_power_sleep_mode<'r>(
&'r mut self,
scb: &'r mut SCB,
rcc: &mut Rcc,
) -> LowPowerSleepMode<'r> {
assert!(rcc.clocks.sys_clk().0 <= 131_072);
LowPowerSleepMode { pwr: self, scb }
}
pub fn stop_mode<'r>(
&'r mut self,
scb: &'r mut SCB,
rcc: &'r mut Rcc,
config: StopModeConfig,
) -> StopMode<'r> {
StopMode {
pwr: self,
scb,
rcc,
config,
}
}
pub fn standby_mode<'r>(&'r mut self, scb: &'r mut SCB) -> StandbyMode<'r> {
StandbyMode { pwr: self, scb }
}
fn set_lpsdsr(&mut self) {
self.0.cr.modify(|_, w| w.lpsdsr().low_power_mode());
}
fn clear_lpsdsr(&mut self) {
self.0.cr.modify(|_, w| w.lpsdsr().main_mode());
}
}
#[repr(u8)]
pub enum VcoreRange {
Range1 = 0b01,
Range2 = 0b10,
Range3 = 0b11,
}
impl VcoreRange {
pub fn from_bits(bits: u8) -> Self {
match bits {
0b01 => VcoreRange::Range1,
0b10 => VcoreRange::Range2,
0b11 => VcoreRange::Range3,
bits => panic!("Bits don't represent valud Vcore range: {}", bits),
}
}
}
pub trait PowerMode {
fn enter(&mut self);
}
pub struct SleepMode<'r> {
pwr: &'r mut PWR,
scb: &'r mut SCB,
}
impl PowerMode for SleepMode<'_> {
fn enter(&mut self) {
self.pwr.clear_lpsdsr();
self.scb.clear_sleepdeep();
asm::dsb();
asm::wfi();
}
}
pub struct LowPowerSleepMode<'r> {
pwr: &'r mut PWR,
scb: &'r mut SCB,
}
impl PowerMode for LowPowerSleepMode<'_> {
fn enter(&mut self) {
let old_vcore = self.pwr.get_vcore_range();
self.pwr.switch_vcore_range(VcoreRange::Range2);
self.pwr.set_lpsdsr();
self.scb.clear_sleepdeep();
asm::dsb();
asm::wfi();
self.pwr.switch_vcore_range(old_vcore);
}
}
pub struct StopMode<'r> {
pwr: &'r mut PWR,
scb: &'r mut SCB,
rcc: &'r mut Rcc,
config: StopModeConfig,
}
impl PowerMode for StopMode<'_> {
fn enter(&mut self) {
self.scb.set_sleepdeep();
self.rcc
.rb
.cfgr
.modify(|_, w| match self.rcc.clocks.source() {
ClockSrc::MSI(_) => w.stopwuck().clear_bit(),
ClockSrc::HSI16(_) | ClockSrc::PLL(PLLSource::HSI16(_), _, _) => {
w.stopwuck().set_bit()
}
_ => panic!("External clock not supported for Stop mode"),
});
self.pwr.0.cr.modify(|_, w| {
w.ulp().bit(self.config.ultra_low_power);
w.cwuf().set_bit();
w.pdds().stop_mode();
w.lpds().set_bit()
});
while self.pwr.0.csr.read().wuf().bit_is_set() {}
asm::dsb();
asm::wfi();
}
}
pub struct StopModeConfig {
pub ultra_low_power: bool,
}
pub struct StandbyMode<'r> {
pwr: &'r mut PWR,
scb: &'r mut SCB,
}
impl PowerMode for StandbyMode<'_> {
fn enter(&mut self) {
self.scb.set_sleepdeep();
self.pwr.0.cr.modify(|_, w| {
w.cwuf().set_bit();
w.pdds().standby_mode()
});
while self.pwr.0.csr.read().wuf().bit_is_set() {}
asm::dsb();
asm::wfi();
}
}