#![allow(non_snake_case)]
#[cfg(feature = "low-power")]
use core::cell::Cell;
use core::cell::RefCell;
#[cfg(feature = "low-power")]
use core::sync::atomic::AtomicBool;
use core::sync::atomic::{AtomicU32, Ordering, compiler_fence};
use critical_section::CriticalSection;
use embassy_sync::blocking_mutex::Mutex;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_time_driver::{Driver, TICK_HZ};
use embassy_time_queue_utils::Queue;
use stm32_metapac::lptim::{Lptim, regs};
use super::AlarmState;
use crate::interrupt::typelevel::Interrupt;
use crate::lptim::SealedInstance;
use crate::pac::lptim::vals;
use crate::rcc::SealedRccPeripheral;
use crate::{peripherals, rcc};
#[cfg(time_driver_lptim1)]
type T = peripherals::LPTIM1;
#[cfg(time_driver_lptim2)]
type T = peripherals::LPTIM2;
#[cfg(time_driver_lptim3)]
type T = peripherals::LPTIM3;
fn regs_lptim() -> Lptim {
T::regs()
}
pub(crate) struct RtcDriver {
period: AtomicU32,
alarm: Mutex<CriticalSectionRawMutex, AlarmState>,
#[cfg(feature = "low-power")]
is_stopped: AtomicBool,
#[cfg(feature = "low-power")]
min_stop_pause: Mutex<CriticalSectionRawMutex, Cell<embassy_time::Duration>>,
queue: Mutex<CriticalSectionRawMutex, RefCell<Queue>>,
}
embassy_time_driver::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver {
period: AtomicU32::new(0),
alarm: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()),
#[cfg(feature = "low-power")]
is_stopped: AtomicBool::new(false),
#[cfg(feature = "low-power")]
min_stop_pause: Mutex::const_new(CriticalSectionRawMutex::new(), Cell::new(embassy_time::Duration::from_millis(0))),
queue: Mutex::new(RefCell::new(Queue::new())),
});
impl RtcDriver {
pub(crate) fn init_timer(&'static self, _cs: critical_section::CriticalSection) {
let r = regs_lptim();
rcc::enable_and_reset_without_stop::<T>();
let timer_freq = T::frequency();
r.cnt().write(|w| w.set_cnt(0));
let psc = timer_freq.0 / TICK_HZ as u32;
let psc = match psc {
128 => vals::Presc::DIV128,
64 => vals::Presc::DIV64,
32 => vals::Presc::DIV32,
16 => vals::Presc::DIV16,
8 => vals::Presc::DIV8,
4 => vals::Presc::DIV4,
2 => vals::Presc::DIV2,
1 => vals::Presc::DIV1,
_ => panic!("Invalid prescaler: {} for timer frequency: {}Hz", psc, timer_freq.0),
};
trace!(
"init: setting presc: {} timer_freq: {}Hz TICK_HZ: {}",
psc, timer_freq, TICK_HZ
);
r.cfgr().modify(|w| w.set_presc(psc));
r.cr().modify(|w| w.set_enable(true));
trace!("init: arr: {:?}", r.arr().read());
r.arr().write(|w| w.set_arr(u16::MAX));
T::regs().ier().modify(|w| w.set_ueie(true));
<T as crate::lptim::SealedBasicInstance>::GlobalInterrupt::unpend();
unsafe {
<T as crate::lptim::SealedBasicInstance>::GlobalInterrupt::enable();
}
#[cfg(feature = "low-power")]
{
#[cfg(feature = "_core-cm4")]
const CPU: usize = 0;
#[cfg(feature = "_core-cm0p")]
const CPU: usize = 1;
#[cfg(all(any(stm32wlex, stm32wl5x), time_driver_lptim1))]
const EXTI_WAKEUP_LINE: usize = 29;
#[cfg(all(any(stm32wlex, stm32wl5x), time_driver_lptim2))]
const EXTI_WAKEUP_LINE: usize = 30;
#[cfg(all(any(stm32wlex, stm32wl5x), time_driver_lptim3))]
const EXTI_WAKEUP_LINE: usize = 31;
#[cfg(any(stm32wb, stm32wl5x))]
{
crate::pac::EXTI
.cpu(CPU)
.imr(0)
.modify(|w| w.set_line(EXTI_WAKEUP_LINE, true));
crate::pac::EXTI
.cpu(CPU)
.emr(0)
.modify(|w| w.set_line(EXTI_WAKEUP_LINE, true));
}
#[cfg(not(any(stm32wb, stm32wl5x)))]
{
crate::pac::EXTI.imr(0).modify(|w| w.set_line(EXTI_WAKEUP_LINE, true));
}
}
}
fn init(&'static self, cs: CriticalSection) {
self.init_timer(cs);
regs_lptim().cr().modify(|w| w.set_cntstrt(true));
}
pub(crate) fn on_interrupt(&self) {
let r = regs_lptim();
critical_section::with(|cs| {
let sr = r.isr().read();
let ier = r.ier().read();
trace!("on_interrupt: sr: {:?}, ier: {:?}", sr, ier);
r.icr().write_value(regs::Icr(ier.0));
r.icr().write_value(regs::Icr(sr.0));
if sr.ue() {
self.next_period();
}
if sr.ccif(0) && ier.ccie(0) {
self.trigger_alarm(cs);
}
})
}
fn next_period(&self) {
let r = regs_lptim();
let period = self.period.load(Ordering::Relaxed) + 1;
self.period.store(period, Ordering::Relaxed);
let t = (period as u64) << 16;
critical_section::with(move |cs| {
r.ier().modify(move |w| {
let alarm = self.alarm.borrow(cs);
let at = alarm.timestamp.get();
if at < t + 0xc000 {
w.set_ccie(0, true);
}
})
})
}
fn trigger_alarm(&self, cs: CriticalSection) {
trace!("trigger_alarm");
let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now());
while !self.set_alarm(cs, next) {
next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now());
}
}
fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
trace!("set_alarm: timestamp: {}", timestamp);
let r = regs_lptim();
self.alarm.borrow(cs).timestamp.set(timestamp);
let t = self.now();
if timestamp <= t {
trace!("set_alarm: timestamp <= t");
r.ier().modify(|w| w.set_ccie(0, false));
self.alarm.borrow(cs).timestamp.set(u64::MAX);
return false;
}
r.cmp().write(|w| w.set_cmp(timestamp as u16));
while !r.isr().read().cmpok(0) {}
let diff = timestamp - t;
r.ier().modify(|w| w.set_ccie(0, diff < 0xc000));
let t = self.now();
if timestamp <= t {
trace!("set_alarm: timestamp <= t (after set)");
r.ier().modify(|w| w.set_ccie(0, false));
self.alarm.borrow(cs).timestamp.set(u64::MAX);
return false;
}
trace!("set_alarm: true");
true
}
}
#[cfg(feature = "low-power")]
impl super::LPTimeDriver for RtcDriver {
fn time_until_next_alarm(&self, cs: CriticalSection) -> embassy_time::Duration {
let now = self.now() + 32;
embassy_time::Duration::from_ticks(self.alarm.borrow(cs).timestamp.get().saturating_sub(now))
}
fn pause_time(&self, cs: CriticalSection) -> Result<(), ()> {
trace!("pause_time");
let time_until_next_alarm = self.time_until_next_alarm(cs);
if time_until_next_alarm < self.min_stop_pause.borrow(cs).get() {
trace!(
"time_until_next_alarm < self.min_stop_pause ({})",
time_until_next_alarm
);
Err(())
} else {
self.is_stopped.store(true, Ordering::Relaxed);
Ok(())
}
}
fn resume_time(&self, _cs: CriticalSection) {
trace!("resume_time");
self.is_stopped.store(false, Ordering::Relaxed);
}
fn is_stopped(&self) -> bool {
self.is_stopped.load(Ordering::Relaxed)
}
}
impl Driver for RtcDriver {
fn now(&self) -> u64 {
let r = regs_lptim();
loop {
let period = self.period.load(Ordering::Relaxed);
compiler_fence(Ordering::Acquire);
let counter = r.cnt().read().cnt();
let now = ((period as u64) << 16) + (counter as u64);
if self.period.load(Ordering::Relaxed) == period {
break now;
}
}
}
fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
critical_section::with(|cs| {
let mut queue = self.queue.borrow(cs).borrow_mut();
if queue.schedule_wake(at, waker) {
let mut next = queue.next_expiration(self.now());
while !self.set_alarm(cs, next) {
next = queue.next_expiration(self.now());
}
}
})
}
}
pub(crate) const fn get_driver() -> &'static RtcDriver {
&DRIVER
}
pub(crate) fn init(cs: CriticalSection) {
DRIVER.init(cs)
}