use core::cell::{Cell, RefCell};
use core::sync::atomic::{AtomicPtr, AtomicU32, Ordering, compiler_fence};
use critical_section::CriticalSection;
use embassy_hal_internal::{Peri, PeripheralType};
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 pac::peripherals::lpit::LPIT;
use crate::pac;
const PERIOD_CH: usize = 0;
const ALARM_CH: usize = 1;
const HALF_PERIOD: u64 = 0x8000_0000;
const LPIT_MCR_M_CEN: u32 = 1 << 0;
const LPIT_TCTRL_T_EN: u32 = 1 << 0;
const LPIT_TCTRL_MODE_PERIODIC: u32 = 0 << 2;
const LPIT_TCTRL_TSOI: u32 = 1 << 17;
struct AlarmState {
timestamp: Cell<u64>,
}
unsafe impl Send for AlarmState {}
impl AlarmState {
const fn new() -> Self {
Self {
timestamp: Cell::new(u64::MAX),
}
}
}
pub struct LpitTimeDriver {
base: AtomicPtr<()>,
period: AtomicU32,
alarm: Mutex<CriticalSectionRawMutex, AlarmState>,
queue: Mutex<CriticalSectionRawMutex, RefCell<Queue>>,
}
embassy_time_driver::time_driver_impl!(static DRIVER: LpitTimeDriver = LpitTimeDriver {
base: AtomicPtr::new(core::ptr::null_mut()),
period: AtomicU32::new(0),
alarm: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()),
queue: Mutex::new(RefCell::new(Queue::new())),
});
fn lpit() -> Option<LPIT> {
let ptr = DRIVER.base.load(Ordering::Acquire);
if ptr.is_null() {
None
} else {
Some(unsafe { LPIT::from_ptr(ptr) })
}
}
const PERIOD_TVAL: u32 = (HALF_PERIOD - 1) as u32;
const fn calc_now(period: u32, elapsed: u32) -> u64 {
((period as u64) * HALF_PERIOD) + elapsed as u64
}
fn elapsed_in_period(r: LPIT) -> u32 {
let remaining = r.CHANNEL_CVAL(PERIOD_CH).read().VAL();
PERIOD_TVAL.saturating_sub(remaining)
}
const fn lpit_alarm_ticks(now: u64, timestamp: u64) -> Option<u32> {
if timestamp <= now {
None
} else {
let diff = timestamp - now;
if diff > u32::MAX as u64 {
Some(u32::MAX)
} else {
Some(diff as u32)
}
}
}
impl LpitTimeDriver {
unsafe fn init(&'static self, base: *mut (), clock_hz: u32) {
assert!(clock_hz as u64 == TICK_HZ);
self.base.store(base, Ordering::Release);
self.period.store(0, Ordering::Release);
let r = unsafe { LPIT::from_ptr(base) };
r.MCR().write_value(LPIT_MCR_M_CEN);
r.CLRTEN().write_value((1 << PERIOD_CH) | (1 << ALARM_CH));
r.MIER().write(|w| {
w.set_TIE0(true);
w.set_TIE1(false);
});
r.MSR().write(|w| {
w.set_TIF0(true);
w.set_TIF1(true);
});
r.CHANNEL_TVAL(PERIOD_CH).write_value(PERIOD_TVAL);
r.CHANNEL_TCTRL(PERIOD_CH)
.write_value(LPIT_TCTRL_T_EN | LPIT_TCTRL_MODE_PERIODIC);
r.SETTEN().write_value(1 << PERIOD_CH);
}
fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
let Some(r) = lpit() else {
return false;
};
self.alarm.borrow(cs).timestamp.set(timestamp);
let now = self.now();
if timestamp <= now {
r.MIER().modify(|w| w.set_TIE1(false));
r.CLRTEN().write_value(1 << ALARM_CH);
self.alarm.borrow(cs).timestamp.set(u64::MAX);
return false;
}
let ticks = lpit_alarm_ticks(now, timestamp).unwrap_or(0);
if ticks == u32::MAX {
r.MIER().modify(|w| w.set_TIE1(false));
r.CLRTEN().write_value(1 << ALARM_CH);
return true;
}
r.CLRTEN().write_value(1 << ALARM_CH);
r.MSR().write(|w| w.set_TIF1(true));
r.CHANNEL_TVAL(ALARM_CH).write_value(ticks);
r.CHANNEL_TCTRL(ALARM_CH)
.write_value(LPIT_TCTRL_T_EN | LPIT_TCTRL_MODE_PERIODIC | LPIT_TCTRL_TSOI);
r.MIER().modify(|w| w.set_TIE1(true));
r.SETTEN().write_value(1 << ALARM_CH);
true
}
fn trigger_alarm(&self, cs: CriticalSection) {
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 on_interrupt(&self) {
let Some(r) = lpit() else {
return;
};
critical_section::with(|cs| {
let sr = r.MSR().read();
let ier = r.MIER().read();
if sr.TIF0() {
self.period.fetch_add(1, Ordering::Relaxed);
r.MSR().write(|w| w.set_TIF0(true));
self.trigger_alarm(cs);
}
if sr.TIF1() && ier.TIE1() {
r.MIER().modify(|w| w.set_TIE1(false));
r.MSR().write(|w| w.set_TIF1(true));
self.trigger_alarm(cs);
}
});
}
}
impl Driver for LpitTimeDriver {
fn now(&self) -> u64 {
loop {
let period = self.period.load(Ordering::Relaxed);
compiler_fence(Ordering::Acquire);
let Some(r) = lpit() else {
return calc_now(period, 0);
};
let elapsed = elapsed_in_period(r);
compiler_fence(Ordering::Acquire);
let wrapped = r.MSR().read().TIF0();
compiler_fence(Ordering::Acquire);
if self.period.load(Ordering::Relaxed) != period {
continue;
}
return if wrapped {
calc_now(period + 1, elapsed_in_period(r))
} else {
calc_now(period, elapsed)
};
}
}
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) trait SealedInstance {
fn regs() -> LPIT;
}
#[allow(private_bounds)]
pub trait Instance: SealedInstance + PeripheralType {
const IRQ: Option<u16>;
}
macro_rules! impl_lpit {
($instance:ident, $base:expr, $irq:expr) => {
impl crate::timer_driver::SealedInstance for crate::peripherals::$instance {
#[inline]
fn regs() -> crate::pac::peripherals::lpit::LPIT {
unsafe { crate::pac::peripherals::lpit::LPIT::from_ptr($base as *mut ()) }
}
}
impl crate::timer_driver::Instance for crate::peripherals::$instance {
const IRQ: ::core::option::Option<u16> = $irq;
}
};
}
pub(crate) use impl_lpit;
pub fn init_timer<T: Instance>(peri: Peri<'static, T>, clock_hz: u32) {
let _ = peri;
unsafe { DRIVER.init(T::regs().as_ptr(), clock_hz) }
}
pub fn on_timer_interrupt() {
DRIVER.on_interrupt();
}
pub fn bringup(lpit1: Peri<'static, crate::peripherals::LPIT1>) {
const LPIT1_CLOCK_HZ: u32 = 24_000_000;
let irq =
<crate::peripherals::LPIT1 as Instance>::IRQ.expect("LPIT1 has no NVIC line on this core");
init_timer(lpit1, LPIT1_CLOCK_HZ);
unsafe { crate::hal::nvic::set_priority(irq, 0x80) };
unsafe { crate::hal::nvic::enable(irq) };
}
#[cfg(test)]
mod tests {
use super::{HALF_PERIOD, calc_now, lpit_alarm_ticks};
#[test]
fn calc_now_extends_half_periods() {
assert_eq!(calc_now(0, 7), 7);
assert_eq!(calc_now(1, 0), HALF_PERIOD);
assert_eq!(calc_now(2, 3), HALF_PERIOD * 2 + 3);
}
#[test]
fn alarm_ticks_reject_past_deadlines() {
assert_eq!(lpit_alarm_ticks(10, 10), None);
assert_eq!(lpit_alarm_ticks(10, 9), None);
}
#[test]
fn alarm_ticks_program_near_deadlines() {
assert_eq!(lpit_alarm_ticks(10, 11), Some(1));
assert_eq!(lpit_alarm_ticks(10, 42), Some(32));
}
#[test]
fn alarm_ticks_clamp_far_deadlines() {
assert_eq!(
lpit_alarm_ticks(10, 10 + u32::MAX as u64 + 1),
Some(u32::MAX)
);
}
}