use core::cell::{Cell, RefCell};
use core::sync::atomic::{AtomicPtr, AtomicU8, 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::timer::{TIM_GP, TIM_SR};
use crate::pac;
const PERIOD_CCR: usize = 0;
const ALARM_CCR: usize = 1;
const fn half_period(bits: u8) -> u64 {
1 << (bits - 1)
}
const fn counter_max(bits: u8) -> u32 {
(((1u64) << bits) - 1) as u32
}
struct AlarmState {
timestamp: Cell<u64>,
}
unsafe impl Send for AlarmState {}
impl AlarmState {
const fn new() -> Self {
Self {
timestamp: Cell::new(u64::MAX),
}
}
}
pub struct GpTimerTimeDriver {
base: AtomicPtr<()>,
bits: AtomicU8,
period: AtomicU32,
alarm: Mutex<CriticalSectionRawMutex, AlarmState>,
queue: Mutex<CriticalSectionRawMutex, RefCell<Queue>>,
}
embassy_time_driver::time_driver_impl!(static DRIVER: GpTimerTimeDriver = GpTimerTimeDriver {
base: AtomicPtr::new(core::ptr::null_mut()),
bits: AtomicU8::new(32),
period: AtomicU32::new(0),
alarm: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()),
queue: Mutex::new(RefCell::new(Queue::new())),
});
fn tim() -> Option<TIM_GP> {
let ptr = DRIVER.base.load(Ordering::Acquire);
if ptr.is_null() {
None
} else {
Some(unsafe { TIM_GP::from_ptr(ptr) })
}
}
const fn calc_now(bits: u8, period: u32, counter: u32) -> u64 {
let top = bits as u32 - 1;
((period as u64) << top) + ((counter ^ ((period & 1) << top)) as u64)
}
const fn compute_psc(clock_hz: u32, tick_hz: u64) -> u32 {
assert!(tick_hz > 0 && (clock_hz as u64).is_multiple_of(tick_hz));
let div = clock_hz as u64 / tick_hz;
assert!(div > 0);
assert!(div - 1 <= u16::MAX as u64);
(div - 1) as u32
}
const fn tim_alarm_enabled(bits: u8, now: u64, timestamp: u64) -> Option<bool> {
let half = half_period(bits);
if timestamp <= now {
None
} else {
Some(timestamp - now < half + (half / 2))
}
}
impl GpTimerTimeDriver {
unsafe fn init(&'static self, base: *mut (), bits: u8, clock_hz: u32) {
assert!(
bits == 16 || bits == 32,
"timer counter must be 16 or 32 bits"
);
self.bits.store(bits, Ordering::Release);
self.base.store(base, Ordering::Release);
self.period.store(0, Ordering::Release);
let r = unsafe { TIM_GP::from_ptr(base) };
let psc = compute_psc(clock_hz, TICK_HZ);
r.CR1().modify(|w| w.set_CEN(false));
r.CNT().write(|w| w.set_CNT(0));
r.PSC().write(|w| w.set_PSC(psc));
r.ARR().write(|w| w.set_ARR(counter_max(bits)));
r.CCR(PERIOD_CCR)
.write(|w| w.set_CCR(half_period(bits) as u32));
r.EGR().write(|w| w.set_UG(true));
r.SR().write_value(Default::default());
r.DIER().write(|w| {
w.set_UIE(true);
w.set_CCIE(PERIOD_CCR, true);
w.set_CCIE(ALARM_CCR, false);
});
r.CR1().modify(|w| w.set_CEN(true));
}
fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
let Some(r) = tim() else {
return false;
};
self.alarm.borrow(cs).timestamp.set(timestamp);
let now = self.now();
if timestamp <= now {
r.DIER().modify(|w| w.set_CCIE(ALARM_CCR, false));
self.alarm.borrow(cs).timestamp.set(u64::MAX);
return false;
}
let bits = self.bits.load(Ordering::Relaxed);
r.CCR(ALARM_CCR)
.write(|w| w.set_CCR(timestamp as u32 & counter_max(bits)));
r.DIER().modify(|w| {
w.set_CCIE(
ALARM_CCR,
tim_alarm_enabled(bits, now, timestamp).unwrap_or(false),
)
});
let now = self.now();
if timestamp <= now {
r.DIER().modify(|w| w.set_CCIE(ALARM_CCR, false));
self.alarm.borrow(cs).timestamp.set(u64::MAX);
return false;
}
true
}
fn next_period(&self, cs: CriticalSection) {
let Some(r) = tim() else {
return;
};
let half = half_period(self.bits.load(Ordering::Relaxed));
let period = self.period.load(Ordering::Relaxed) + 1;
self.period.store(period, Ordering::Relaxed);
let now = (period as u64) * half;
let alarm = self.alarm.borrow(cs);
if alarm.timestamp.get() < now + half + (half / 2) {
r.DIER().modify(|w| w.set_CCIE(ALARM_CCR, 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) {
consortium_log::trace!("on_timer_interrupt");
let Some(r) = tim() else {
return;
};
critical_section::with(|cs| {
consortium_log::trace!("on_timer_interrupt: critical section");
let sr = r.SR().read();
let dier = r.DIER().read();
r.SR().write_value(TIM_SR(!sr.0));
if sr.UIF() {
self.next_period(cs);
}
if sr.CCIF(PERIOD_CCR) {
self.next_period(cs);
}
if sr.CCIF(ALARM_CCR) && dier.CCIE(ALARM_CCR) {
self.trigger_alarm(cs);
}
});
}
}
impl Driver for GpTimerTimeDriver {
fn now(&self) -> u64 {
let period = self.period.load(Ordering::Relaxed);
compiler_fence(Ordering::Acquire);
let counter = tim().map_or(0, |r| r.CNT().read().CNT());
calc_now(self.bits.load(Ordering::Relaxed), period, counter)
}
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 fn init_timer<T: Instance>(peri: Peri<'static, T>, clock_hz: u32) {
consortium_log::trace!("init_timer, clock hz={}", clock_hz);
let _ = peri;
unsafe { DRIVER.init(T::regs().as_ptr(), T::BITS, clock_hz) }
}
pub(crate) trait SealedInstance {
fn regs() -> TIM_GP;
}
#[allow(private_bounds)]
pub trait Instance: SealedInstance + PeripheralType {
const IRQ: Option<u16>;
const BITS: u8;
}
macro_rules! impl_tim_driver {
($instance:ident, $base:expr, Bits16, $irq:expr) => {
crate::timer_driver::impl_tim_driver!(@impl $instance, $base, 16, $irq);
};
($instance:ident, $base:expr, Bits32, $irq:expr) => {
crate::timer_driver::impl_tim_driver!(@impl $instance, $base, 32, $irq);
};
(@impl $instance:ident, $base:expr, $bits:literal, $irq:expr) => {
impl crate::timer_driver::SealedInstance for crate::peripherals::$instance {
#[inline]
fn regs() -> crate::pac::peripherals::timer::TIM_GP {
unsafe { crate::pac::peripherals::timer::TIM_GP::from_ptr($base as *mut ()) }
}
}
impl crate::timer_driver::Instance for crate::peripherals::$instance {
const IRQ: ::core::option::Option<u16> = $irq;
const BITS: u8 = $bits;
}
};
}
pub(crate) use impl_tim_driver;
pub fn on_timer_interrupt() {
consortium_log::trace!("on_timer_interrupt triggered");
DRIVER.on_interrupt();
}
pub fn bringup(tim2: Peri<'static, crate::peripherals::TIM2>) {
const RCC_BASE_NS: *mut () = 0x4420_0000 as *mut ();
const TIM2_CLOCK_HZ: u32 = 64_000_000;
let irq =
<crate::peripherals::TIM2 as Instance>::IRQ.expect("TIM2 has no NVIC line on this core");
let rcc = unsafe { crate::pac::peripherals::rcc::RCC::from_ptr(RCC_BASE_NS) };
rcc.TIM2CFGR().modify(|w| {
w.set_TIM2RST(false);
w.set_TIM2EN(true);
w.set_TIM2LPEN(true);
});
init_timer(tim2, TIM2_CLOCK_HZ);
unsafe { crate::hal::nvic::set_priority(crate::hal::nvic::Irq(irq), 0x80) };
unsafe { crate::hal::nvic::enable(crate::hal::nvic::Irq(irq)) };
}
#[cfg(test)]
mod tests {
use super::{calc_now, compute_psc, counter_max, half_period, tim_alarm_enabled};
const HALF_PERIOD: u64 = half_period(32);
#[test]
fn calc_now_tracks_period_parity() {
assert_eq!(calc_now(32, 0, 7), 7);
assert_eq!(calc_now(32, 1, 0x8000_0000), HALF_PERIOD);
assert_eq!(calc_now(32, 2, 3), HALF_PERIOD * 2 + 3);
assert_eq!(calc_now(16, 1, 0x8000), 0x8000);
assert_eq!(calc_now(16, 2, 3), 0x1_0003);
}
#[test]
fn calc_now_remains_monotonic_across_rollover_race() {
assert!(calc_now(32, 1, 0) > calc_now(32, 1, 0xffff_fffe));
assert_eq!(calc_now(32, 1, 0), HALF_PERIOD * 2);
assert!(calc_now(16, 1, 0) > calc_now(16, 1, 0xfffe));
assert_eq!(calc_now(16, 1, 0), 0x1_0000);
}
#[test]
fn counter_geometry_matches_width() {
assert_eq!(counter_max(16), 0xffff);
assert_eq!(counter_max(32), u32::MAX);
assert_eq!(half_period(16), 0x8000);
assert_eq!(half_period(32), 0x8000_0000);
}
#[test]
fn sixteen_bit_counter_keeps_time_across_wraps() {
let bits = 16;
let max = counter_max(bits);
let mut period = 0u32;
let mut counter = 0u32;
for elapsed in 0..(5 * (max as u64 + 1) + 123) {
assert_eq!(calc_now(bits, period, counter), elapsed);
counter = if counter == max { 0 } else { counter + 1 };
if counter == half_period(bits) as u32 || counter == 0 {
period += 1;
}
}
}
#[test]
fn sixteen_bit_counter_is_monotonic_with_late_interrupts() {
let bits = 16;
let max = counter_max(bits);
let mut period = 0u32;
let mut counter = 0u32;
let mut pending = None::<u32>;
let mut last = 0;
for _ in 0..(3 * (max as u64 + 1)) {
let now = calc_now(bits, period, counter);
assert!(now >= last, "time went backwards: {last} -> {now}");
last = now;
counter = if counter == max { 0 } else { counter + 1 };
if counter == half_period(bits) as u32 || counter == 0 {
pending = Some(7);
}
if let Some(delay) = pending.as_mut() {
if *delay == 0 {
period += 1;
pending = None;
} else {
*delay -= 1;
}
}
}
}
#[test]
fn alarm_enable_rejects_past_deadlines() {
assert_eq!(tim_alarm_enabled(32, 10, 10), None);
assert_eq!(tim_alarm_enabled(32, 10, 9), None);
}
#[test]
fn alarm_enable_handles_near_and_far_deadlines() {
assert_eq!(tim_alarm_enabled(32, 10, 42), Some(true));
assert_eq!(tim_alarm_enabled(32, 10, 10 + HALF_PERIOD * 2), Some(false));
assert_eq!(tim_alarm_enabled(16, 0, 0xbfff), Some(true));
assert_eq!(tim_alarm_enabled(16, 0, 0xc000), Some(false));
}
#[test]
fn psc_divides_clock_to_tick_rate() {
assert_eq!(compute_psc(1_000_000, 1_000_000), 0);
assert_eq!(compute_psc(200_000_000, 1_000_000), 199);
assert_eq!(compute_psc(32_768, 32_768), 0);
}
#[test]
#[should_panic]
fn psc_rejects_non_divisible_clock() {
compute_psc(3_000_001, 1_000_000);
}
#[test]
#[should_panic]
fn psc_rejects_clock_slower_than_tick() {
compute_psc(500_000, 1_000_000);
}
#[test]
#[should_panic]
fn psc_rejects_divider_overflow() {
compute_psc(200_000_000, 1_000);
}
}