aboutsummaryrefslogtreecommitdiff
path: root/embassy-nxp/src/time_driver
diff options
context:
space:
mode:
authori509VCB <[email protected]>2025-07-09 23:08:59 -0500
committeri509VCB <[email protected]>2025-07-22 11:07:10 -0500
commit1ad5d5a771d5109a763361454fb724b85ae25fdd (patch)
tree62beeabed93b1108b8b4888aaac259f0a3256d01 /embassy-nxp/src/time_driver
parentd656b174ed8976b9d77ba8098042d75dd76ef733 (diff)
nxp: Add MIMXRT1011 GPIO and time driver
PIT is used for the time driver
Diffstat (limited to 'embassy-nxp/src/time_driver')
-rw-r--r--embassy-nxp/src/time_driver/pit.rs187
1 files changed, 187 insertions, 0 deletions
diff --git a/embassy-nxp/src/time_driver/pit.rs b/embassy-nxp/src/time_driver/pit.rs
new file mode 100644
index 000000000..985e5e815
--- /dev/null
+++ b/embassy-nxp/src/time_driver/pit.rs
@@ -0,0 +1,187 @@
1//! Time driver using Periodic Interrupt Timer (PIT)
2//!
3//! This driver is used with the iMXRT1xxx parts.
4//!
5//! The PIT is run in lifetime mode. Timer 1 is chained to timer 0 to provide a free-running 64-bit timer.
6//! The 64-bit timer is used to track how many ticks since boot.
7//!
8//! Timer 2 counts how many ticks there are within the current u32::MAX tick period. Timer 2 is restarted when
9//! a new alarm is set (or every u32::MAX ticks). One caveat is that an alarm could be a few ticks late due to
10//! restart. However the Cortex-M7 cores run at 500 MHz easily and the PIT will generally run at 1 MHz or lower.
11//! Along with the fact that scheduling an alarm takes a critical section worst case an alarm may be a few
12//! microseconds late.
13//!
14//! All PIT timers are clocked in lockstep, so the late start will not cause the now() count to drift.
15
16use core::cell::{Cell, RefCell};
17use core::task::Waker;
18
19use critical_section::{CriticalSection, Mutex};
20use embassy_hal_internal::interrupt::InterruptExt;
21use embassy_time_driver::Driver as _;
22use embassy_time_queue_utils::Queue;
23
24use crate::pac::{self, interrupt};
25
26struct Driver {
27 alarm: Mutex<Cell<u64>>,
28 queue: Mutex<RefCell<Queue>>,
29}
30
31impl embassy_time_driver::Driver for Driver {
32 fn now(&self) -> u64 {
33 loop {
34 // Even though reading LTMR64H will latch LTMR64L if another thread preempts between any of the
35 // three reads and calls now() then the value in LTMR64L will be wrong when execution returns to
36 // thread which was preempted.
37 let hi = pac::PIT.ltmr64h().read().lth();
38 let lo = pac::PIT.ltmr64l().read().ltl();
39 let hi2 = pac::PIT.ltmr64h().read().lth();
40
41 if hi == hi2 {
42 // PIT timers always count down.
43 return u64::MAX - ((hi as u64) << 32 | (lo as u64));
44 }
45 }
46 }
47
48 fn schedule_wake(&self, at: u64, waker: &Waker) {
49 critical_section::with(|cs| {
50 let mut queue = self.queue.borrow(cs).borrow_mut();
51
52 if queue.schedule_wake(at, waker) {
53 let mut next = queue.next_expiration(self.now());
54
55 while !self.set_alarm(cs, next) {
56 next = queue.next_expiration(self.now());
57 }
58 }
59 })
60 }
61}
62
63impl Driver {
64 fn init(&'static self) {
65 // Disable PIT clock during mux configuration.
66 pac::CCM.ccgr1().modify(|r| r.set_cg6(0b00));
67
68 // TODO: This forces the PIT to be driven by the oscillator. However that isn't the only option as you
69 // could divide the clock root by up to 64.
70 pac::CCM.cscmr1().modify(|r| {
71 // 1 MHz
72 r.set_perclk_podf(pac::ccm::vals::PerclkPodf::DIVIDE_24);
73 r.set_perclk_clk_sel(pac::ccm::vals::PerclkClkSel::PERCLK_CLK_SEL_1);
74 });
75
76 pac::CCM.ccgr1().modify(|r| r.set_cg6(0b11));
77
78 // Disable clock during init.
79 //
80 // It is important that the PIT clock is prepared to not exceed limit (50 MHz on RT1011), or else
81 // you will need to recover the device with boot mode switches when using any PIT registers.
82 pac::PIT.mcr().modify(|w| {
83 w.set_mdis(true);
84 });
85
86 pac::PIT.timer(0).ldval().write_value(u32::MAX);
87 pac::PIT.timer(1).ldval().write_value(u32::MAX);
88 pac::PIT.timer(2).ldval().write_value(0);
89 pac::PIT.timer(3).ldval().write_value(0);
90
91 pac::PIT.timer(1).tctrl().write(|w| {
92 // In lifetime mode, timer 1 is chained to timer 0 to form a 64-bit timer.
93 w.set_chn(true);
94 w.set_ten(true);
95 w.set_tie(false);
96 });
97
98 pac::PIT.timer(0).tctrl().write(|w| {
99 w.set_chn(false);
100 w.set_ten(true);
101 w.set_tie(false);
102 });
103
104 pac::PIT.timer(2).tctrl().write(|w| {
105 w.set_tie(true);
106 });
107
108 unsafe { interrupt::PIT.enable() };
109
110 pac::PIT.mcr().write(|w| {
111 w.set_mdis(false);
112 });
113 }
114
115 fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
116 let alarm = self.alarm.borrow(cs);
117 alarm.set(timestamp);
118
119 let timer = pac::PIT.timer(2);
120 let now = self.now();
121
122 if timestamp <= now {
123 alarm.set(u64::MAX);
124
125 return false;
126 }
127
128 timer.tctrl().modify(|x| x.set_ten(false));
129 timer.tflg().modify(|x| x.set_tif(true));
130
131 // If the next alarm happens in more than u32::MAX cycles then the alarm will be restarted later.
132 timer.ldval().write_value((timestamp - now) as u32);
133 timer.tctrl().modify(|x| x.set_ten(true));
134
135 true
136 }
137
138 fn trigger_alarm(&self, cs: CriticalSection) {
139 let mut next = self.queue.borrow_ref_mut(cs).next_expiration(self.now());
140
141 while !self.set_alarm(cs, next) {
142 next = self.queue.borrow_ref_mut(cs).next_expiration(self.now());
143 }
144 }
145
146 fn on_interrupt(&self) {
147 critical_section::with(|cs| {
148 let timer = pac::PIT.timer(2);
149 let alarm = self.alarm.borrow(cs);
150 let interrupted = timer.tflg().read().tif();
151 timer.tflg().write(|r| r.set_tif(true));
152
153 if interrupted {
154 // A new load value will not apply until the next timer expiration.
155 //
156 // The expiry may be up to u32::MAX cycles away, so the timer must be restarted.
157 timer.tctrl().modify(|r| r.set_ten(false));
158
159 let now = self.now();
160 let timestamp = alarm.get();
161
162 if timestamp <= now {
163 self.trigger_alarm(cs);
164 } else {
165 // The alarm is not ready. Wait for u32::MAX cycles and check again or set the next alarm.
166 timer.ldval().write_value((timestamp - now) as u32);
167 timer.tctrl().modify(|r| r.set_ten(true));
168 }
169 }
170 });
171 }
172}
173
174embassy_time_driver::time_driver_impl!(static DRIVER: Driver = Driver {
175 alarm: Mutex::new(Cell::new(0)),
176 queue: Mutex::new(RefCell::new(Queue::new()))
177});
178
179pub(crate) fn init() {
180 DRIVER.init();
181}
182
183#[cfg(feature = "rt")]
184#[interrupt]
185fn PIT() {
186 DRIVER.on_interrupt();
187}