i509VCB 1ad5d5a771
nxp: Add MIMXRT1011 GPIO and time driver
PIT is used for the time driver
2025-07-22 11:07:10 -05:00

188 lines
6.2 KiB
Rust

//! Time driver using Periodic Interrupt Timer (PIT)
//!
//! This driver is used with the iMXRT1xxx parts.
//!
//! The PIT is run in lifetime mode. Timer 1 is chained to timer 0 to provide a free-running 64-bit timer.
//! The 64-bit timer is used to track how many ticks since boot.
//!
//! Timer 2 counts how many ticks there are within the current u32::MAX tick period. Timer 2 is restarted when
//! a new alarm is set (or every u32::MAX ticks). One caveat is that an alarm could be a few ticks late due to
//! restart. However the Cortex-M7 cores run at 500 MHz easily and the PIT will generally run at 1 MHz or lower.
//! Along with the fact that scheduling an alarm takes a critical section worst case an alarm may be a few
//! microseconds late.
//!
//! All PIT timers are clocked in lockstep, so the late start will not cause the now() count to drift.
use core::cell::{Cell, RefCell};
use core::task::Waker;
use critical_section::{CriticalSection, Mutex};
use embassy_hal_internal::interrupt::InterruptExt;
use embassy_time_driver::Driver as _;
use embassy_time_queue_utils::Queue;
use crate::pac::{self, interrupt};
struct Driver {
alarm: Mutex<Cell<u64>>,
queue: Mutex<RefCell<Queue>>,
}
impl embassy_time_driver::Driver for Driver {
fn now(&self) -> u64 {
loop {
// Even though reading LTMR64H will latch LTMR64L if another thread preempts between any of the
// three reads and calls now() then the value in LTMR64L will be wrong when execution returns to
// thread which was preempted.
let hi = pac::PIT.ltmr64h().read().lth();
let lo = pac::PIT.ltmr64l().read().ltl();
let hi2 = pac::PIT.ltmr64h().read().lth();
if hi == hi2 {
// PIT timers always count down.
return u64::MAX - ((hi as u64) << 32 | (lo as u64));
}
}
}
fn schedule_wake(&self, at: u64, waker: &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());
}
}
})
}
}
impl Driver {
fn init(&'static self) {
// Disable PIT clock during mux configuration.
pac::CCM.ccgr1().modify(|r| r.set_cg6(0b00));
// TODO: This forces the PIT to be driven by the oscillator. However that isn't the only option as you
// could divide the clock root by up to 64.
pac::CCM.cscmr1().modify(|r| {
// 1 MHz
r.set_perclk_podf(pac::ccm::vals::PerclkPodf::DIVIDE_24);
r.set_perclk_clk_sel(pac::ccm::vals::PerclkClkSel::PERCLK_CLK_SEL_1);
});
pac::CCM.ccgr1().modify(|r| r.set_cg6(0b11));
// Disable clock during init.
//
// It is important that the PIT clock is prepared to not exceed limit (50 MHz on RT1011), or else
// you will need to recover the device with boot mode switches when using any PIT registers.
pac::PIT.mcr().modify(|w| {
w.set_mdis(true);
});
pac::PIT.timer(0).ldval().write_value(u32::MAX);
pac::PIT.timer(1).ldval().write_value(u32::MAX);
pac::PIT.timer(2).ldval().write_value(0);
pac::PIT.timer(3).ldval().write_value(0);
pac::PIT.timer(1).tctrl().write(|w| {
// In lifetime mode, timer 1 is chained to timer 0 to form a 64-bit timer.
w.set_chn(true);
w.set_ten(true);
w.set_tie(false);
});
pac::PIT.timer(0).tctrl().write(|w| {
w.set_chn(false);
w.set_ten(true);
w.set_tie(false);
});
pac::PIT.timer(2).tctrl().write(|w| {
w.set_tie(true);
});
unsafe { interrupt::PIT.enable() };
pac::PIT.mcr().write(|w| {
w.set_mdis(false);
});
}
fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
let alarm = self.alarm.borrow(cs);
alarm.set(timestamp);
let timer = pac::PIT.timer(2);
let now = self.now();
if timestamp <= now {
alarm.set(u64::MAX);
return false;
}
timer.tctrl().modify(|x| x.set_ten(false));
timer.tflg().modify(|x| x.set_tif(true));
// If the next alarm happens in more than u32::MAX cycles then the alarm will be restarted later.
timer.ldval().write_value((timestamp - now) as u32);
timer.tctrl().modify(|x| x.set_ten(true));
true
}
fn trigger_alarm(&self, cs: CriticalSection) {
let mut next = self.queue.borrow_ref_mut(cs).next_expiration(self.now());
while !self.set_alarm(cs, next) {
next = self.queue.borrow_ref_mut(cs).next_expiration(self.now());
}
}
fn on_interrupt(&self) {
critical_section::with(|cs| {
let timer = pac::PIT.timer(2);
let alarm = self.alarm.borrow(cs);
let interrupted = timer.tflg().read().tif();
timer.tflg().write(|r| r.set_tif(true));
if interrupted {
// A new load value will not apply until the next timer expiration.
//
// The expiry may be up to u32::MAX cycles away, so the timer must be restarted.
timer.tctrl().modify(|r| r.set_ten(false));
let now = self.now();
let timestamp = alarm.get();
if timestamp <= now {
self.trigger_alarm(cs);
} else {
// The alarm is not ready. Wait for u32::MAX cycles and check again or set the next alarm.
timer.ldval().write_value((timestamp - now) as u32);
timer.tctrl().modify(|r| r.set_ten(true));
}
}
});
}
}
embassy_time_driver::time_driver_impl!(static DRIVER: Driver = Driver {
alarm: Mutex::new(Cell::new(0)),
queue: Mutex::new(RefCell::new(Queue::new()))
});
pub(crate) fn init() {
DRIVER.init();
}
#[cfg(feature = "rt")]
#[interrupt]
fn PIT() {
DRIVER.on_interrupt();
}