aboutsummaryrefslogtreecommitdiff
path: root/embassy-mspm0/src/time_driver.rs
diff options
context:
space:
mode:
authori509VCB <[email protected]>2025-03-13 22:10:45 -0500
committeri509VCB <[email protected]>2025-03-13 22:10:45 -0500
commite0cdc356ccd7f9e20c2b5355cc52b7eb7610147b (patch)
tree0c34424508b1ee8d5010dc186247b72fac7aca69 /embassy-mspm0/src/time_driver.rs
parent38f26137fc67beb874aa73c9a7ab2150d9f3d372 (diff)
Embassy for MSPM0
This adds an embassy hal for the Texas Instruments MSPM0 microcontroller series. So far the GPIO and time drivers have been implemented. I have tested these drivers on the following parts: - C1104 - L1306 - L2228 - G3507 - G3519 The PAC is generated at https://github.com/mspm0-rs
Diffstat (limited to 'embassy-mspm0/src/time_driver.rs')
-rw-r--r--embassy-mspm0/src/time_driver.rs437
1 files changed, 437 insertions, 0 deletions
diff --git a/embassy-mspm0/src/time_driver.rs b/embassy-mspm0/src/time_driver.rs
new file mode 100644
index 000000000..3af7a5edb
--- /dev/null
+++ b/embassy-mspm0/src/time_driver.rs
@@ -0,0 +1,437 @@
1use core::{
2 cell::{Cell, RefCell},
3 sync::atomic::{compiler_fence, AtomicU32, Ordering},
4 task::Waker,
5};
6
7use critical_section::{CriticalSection, Mutex};
8use embassy_time_driver::Driver;
9use embassy_time_queue_utils::Queue;
10use mspm0_metapac::{
11 interrupt,
12 tim::{
13 vals::{Cm, Cvae, CxC, EvtCfg, PwrenKey, Ratio, Repeat, ResetKey},
14 Counterregs16, Tim,
15 },
16};
17
18use crate::peripherals;
19use crate::timer::SealedTimer;
20
21// Currently TIMG12 and TIMG13 are excluded because those are 32-bit timers.
22#[cfg(time_driver_timg0)]
23type T = peripherals::TIMG0;
24#[cfg(time_driver_timg1)]
25type T = peripherals::TIMG1;
26#[cfg(time_driver_timg2)]
27type T = peripherals::TIMG2;
28#[cfg(time_driver_timg3)]
29type T = peripherals::TIMG3;
30#[cfg(time_driver_timg4)]
31type T = peripherals::TIMG4;
32#[cfg(time_driver_timg5)]
33type T = peripherals::TIMG5;
34#[cfg(time_driver_timg6)]
35type T = peripherals::TIMG6;
36#[cfg(time_driver_timg7)]
37type T = peripherals::TIMG7;
38#[cfg(time_driver_timg8)]
39type T = peripherals::TIMG8;
40#[cfg(time_driver_timg9)]
41type T = peripherals::TIMG9;
42#[cfg(time_driver_timg10)]
43type T = peripherals::TIMG10;
44#[cfg(time_driver_timg11)]
45type T = peripherals::TIMG11;
46#[cfg(time_driver_timg14)]
47type T = peripherals::TIMG14;
48#[cfg(time_driver_tima0)]
49type T = peripherals::TIMA0;
50#[cfg(time_driver_tima1)]
51type T = peripherals::TIMA1;
52
53// TODO: RTC
54
55fn regs() -> Tim {
56 unsafe { Tim::from_ptr(T::regs()) }
57}
58
59fn regs_counter(tim: Tim) -> Counterregs16 {
60 unsafe { Counterregs16::from_ptr(tim.counterregs(0).as_ptr()) }
61}
62
63/// Clock timekeeping works with something we call "periods", which are time intervals
64/// of 2^15 ticks. The Clock counter value is 16 bits, so one "overflow cycle" is 2 periods.
65fn calc_now(period: u32, counter: u16) -> u64 {
66 ((period as u64) << 15) + ((counter as u32 ^ ((period & 1) << 15)) as u64)
67}
68
69/// The TIMx driver uses one of the `TIMG` or `TIMA` timer instances to implement a timer with a 32.768 kHz
70/// tick rate. (TODO: Allow setting the tick rate)
71///
72/// This driver defines a period to be 2^15 ticks. 16-bit timers of course count to 2^16 ticks.
73///
74/// To generate a period every 2^15 ticks, the CC0 value is set to 2^15 and the load value set to 2^16.
75/// Incrementing the period on a CCU0 and load results in the a period of 2^15 ticks.
76///
77/// For a specific timestamp, load the lower 16 bits into the CC1 value. When the period where the timestamp
78/// should be enabled is reached, then the CCU1 (CC1 up) interrupt runs to actually wake the timer.
79///
80/// TODO: Compensate for per part variance. This can supposedly be done with the FCC system.
81/// TODO: Allow using 32-bit timers (TIMG12 and TIMG13).
82struct TimxDriver {
83 /// Number of 2^15 periods elapsed since boot.
84 period: AtomicU32,
85 /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
86 alarm: Mutex<Cell<u64>>,
87 queue: Mutex<RefCell<Queue>>,
88}
89
90impl TimxDriver {
91 #[inline(never)]
92 fn init(&'static self, _cs: CriticalSection) {
93 // Clock config
94 // TODO: Configurable tick rate up to 4 MHz (32 kHz for now)
95 let regs = regs();
96
97 // Reset timer
98 regs.gprcm(0).rstctl().write(|w| {
99 w.set_resetassert(true);
100 w.set_key(ResetKey::KEY);
101 w.set_resetstkyclr(true);
102 });
103
104 // Power up timer
105 regs.gprcm(0).pwren().write(|w| {
106 w.set_enable(true);
107 w.set_key(PwrenKey::KEY);
108 });
109
110 // Following the instructions according to SLAU847D 23.2.1: TIMCLK Configuration
111
112 // 1. Select TIMCLK source
113 regs.clksel().modify(|w| {
114 // Use LFCLK for a 32.768kHz tick rate
115 w.set_lfclk_sel(true);
116 // TODO: Allow MFCLK for configurable tick rate up to 4 MHz
117 // w.set_mfclk_sel(ClkSel::ENABLE);
118 });
119
120 // 2. Divide by TIMCLK, we don't need to divide further for the 32kHz tick rate
121 regs.clkdiv().modify(|w| {
122 w.set_ratio(Ratio::DIV_BY_1);
123 });
124
125 // 3. To be generic across timer instances, we do not use the prescaler.
126 // TODO: mspm0-sdk always sets this, regardless of timer width?
127 regs.commonregs(0).cps().modify(|w| {
128 w.set_pcnt(0);
129 });
130
131 regs.pdbgctl().modify(|w| {
132 w.set_free(true);
133 });
134
135 // 4. Enable the TIMCLK.
136 regs.commonregs(0).cclkctl().modify(|w| {
137 w.set_clken(true);
138 });
139
140 regs.counterregs(0).ctrctl().modify(|w| {
141 // allow counting during debug
142 w.set_repeat(Repeat::REPEAT_3);
143 w.set_cvae(Cvae::ZEROVAL);
144 w.set_cm(Cm::UP);
145
146 // Must explicitly set CZC, CAC and CLC to 0 in order for all the timers to count.
147 //
148 // The reset value of these registers is 0x07, which is a reserved value.
149 //
150 // Looking at a bit representation of the reset value, this appears to be an AND
151 // of 2-input QEI mode and CCCTL_3 ACOND. Given that TIMG14 and TIMA0 have no QEI
152 // and 4 capture and compare channels, this works by accident for those timer units.
153 w.set_czc(CxC::CCTL0);
154 w.set_cac(CxC::CCTL0);
155 w.set_clc(CxC::CCTL0);
156 });
157
158 // Setup the period
159 let ctr = regs_counter(regs);
160
161 // Middle
162 ctr.cc(0).modify(|w| {
163 w.set_ccval(0x7FFF);
164 });
165
166 ctr.load().modify(|w| {
167 w.set_ld(u16::MAX);
168 });
169
170 // Enable the period interrupts
171 //
172 // This does not appear to ever be set for CPU_INT in the TI SDK and is not technically needed.
173 regs.evt_mode().modify(|w| {
174 w.set_evt_cfg(0, EvtCfg::SOFTWARE);
175 });
176
177 regs.int_event(0).imask().modify(|w| {
178 w.set_l(true);
179 w.set_ccu0(true);
180 });
181
182 unsafe { T::enable_interrupt() };
183
184 // Allow the counter to start counting.
185 regs.counterregs(0).ctrctl().modify(|w| {
186 w.set_en(true);
187 });
188 }
189
190 #[inline(never)]
191 fn next_period(&self) {
192 let r = regs();
193
194 // We only modify the period from the timer interrupt, so we know this can't race.
195 let period = self.period.load(Ordering::Relaxed) + 1;
196 self.period.store(period, Ordering::Relaxed);
197 let t = (period as u64) << 15;
198
199 critical_section::with(move |cs| {
200 r.int_event(0).imask().modify(move |w| {
201 let alarm = self.alarm.borrow(cs);
202 let at = alarm.get();
203
204 if at < t + 0xC000 {
205 // just enable it. `set_alarm` has already set the correct CC1 val.
206 w.set_ccu1(true);
207 }
208 })
209 });
210 }
211
212 #[inline(never)]
213 fn on_interrupt(&self) {
214 let r = regs();
215
216 critical_section::with(|cs| {
217 let mis = r.int_event(0).mis().read();
218
219 // Advance to next period if overflowed
220 if mis.l() {
221 self.next_period();
222
223 r.int_event(0).iclr().write(|w| {
224 w.set_l(true);
225 });
226 }
227
228 if mis.ccu0() {
229 self.next_period();
230
231 r.int_event(0).iclr().write(|w| {
232 w.set_ccu0(true);
233 });
234 }
235
236 if mis.ccu1() {
237 r.int_event(0).iclr().write(|w| {
238 w.set_ccu1(true);
239 });
240
241 self.trigger_alarm(cs);
242 }
243 });
244 }
245
246 fn trigger_alarm(&self, cs: CriticalSection) {
247 let mut next = self
248 .queue
249 .borrow(cs)
250 .borrow_mut()
251 .next_expiration(self.now());
252
253 while !self.set_alarm(cs, next) {
254 next = self
255 .queue
256 .borrow(cs)
257 .borrow_mut()
258 .next_expiration(self.now());
259 }
260 }
261
262 fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
263 let r = regs();
264 let ctr = regs_counter(r);
265
266 self.alarm.borrow(cs).set(timestamp);
267
268 let t = self.now();
269
270 if timestamp <= t {
271 // If alarm timestamp has passed the alarm will not fire.
272 // Disarm the alarm and return `false` to indicate that.
273 r.int_event(0).imask().modify(|w| w.set_ccu1(false));
274
275 self.alarm.borrow(cs).set(u64::MAX);
276
277 return false;
278 }
279
280 // Write the CC1 value regardless of whether we're going to enable it now or not.
281 // This way, when we enable it later, the right value is already set.
282 ctr.cc(1).write(|w| {
283 w.set_ccval(timestamp as u16);
284 });
285
286 // Enable it if it'll happen soon. Otherwise, `next_period` will enable it.
287 let diff = timestamp - t;
288 r.int_event(0).imask().modify(|w| w.set_ccu1(diff < 0xC000));
289
290 // Reevaluate if the alarm timestamp is still in the future
291 let t = self.now();
292 if timestamp <= t {
293 // If alarm timestamp has passed since we set it, we have a race condition and
294 // the alarm may or may not have fired.
295 // Disarm the alarm and return `false` to indicate that.
296 // It is the caller's responsibility to handle this ambiguity.
297 r.int_event(0).imask().modify(|w| w.set_ccu1(false));
298
299 self.alarm.borrow(cs).set(u64::MAX);
300
301 return false;
302 }
303
304 // We're confident the alarm will ring in the future.
305 true
306 }
307}
308
309impl Driver for TimxDriver {
310 fn now(&self) -> u64 {
311 let regs = regs();
312
313 let period = self.period.load(Ordering::Relaxed);
314 // Ensure the compiler does not read the counter before the period.
315 compiler_fence(Ordering::Acquire);
316
317 let counter = regs_counter(regs).ctr().read().cctr() as u16;
318
319 calc_now(period, counter)
320 }
321
322 fn schedule_wake(&self, at: u64, waker: &Waker) {
323 critical_section::with(|cs| {
324 let mut queue = self.queue.borrow(cs).borrow_mut();
325
326 if queue.schedule_wake(at, waker) {
327 let mut next = queue.next_expiration(self.now());
328
329 while !self.set_alarm(cs, next) {
330 next = queue.next_expiration(self.now());
331 }
332 }
333 });
334 }
335}
336
337embassy_time_driver::time_driver_impl!(static DRIVER: TimxDriver = TimxDriver {
338 period: AtomicU32::new(0),
339 alarm: Mutex::new(Cell::new(u64::MAX)),
340 queue: Mutex::new(RefCell::new(Queue::new()))
341});
342
343pub(crate) fn init(cs: CriticalSection) {
344 DRIVER.init(cs);
345}
346
347#[cfg(time_driver_timg0)]
348#[interrupt]
349fn TIMG0() {
350 DRIVER.on_interrupt();
351}
352
353#[cfg(time_driver_timg1)]
354#[interrupt]
355fn TIMG1() {
356 DRIVER.on_interrupt();
357}
358
359#[cfg(time_driver_timg2)]
360#[interrupt]
361fn TIMG2() {
362 DRIVER.on_interrupt();
363}
364
365#[cfg(time_driver_timg3)]
366#[interrupt]
367fn TIMG3() {
368 DRIVER.on_interrupt();
369}
370
371#[cfg(time_driver_timg4)]
372#[interrupt]
373fn TIMG4() {
374 DRIVER.on_interrupt();
375}
376
377#[cfg(time_driver_timg5)]
378#[interrupt]
379fn TIMG5() {
380 DRIVER.on_interrupt();
381}
382
383#[cfg(time_driver_timg6)]
384#[interrupt]
385fn TIMG6() {
386 DRIVER.on_interrupt();
387}
388
389#[cfg(time_driver_timg7)]
390#[interrupt]
391fn TIMG7() {
392 DRIVER.on_interrupt();
393}
394
395#[cfg(time_driver_timg8)]
396#[interrupt]
397fn TIMG8() {
398 DRIVER.on_interrupt();
399}
400
401#[cfg(time_driver_timg9)]
402#[interrupt]
403fn TIMG9() {
404 DRIVER.on_interrupt();
405}
406
407#[cfg(time_driver_timg10)]
408#[interrupt]
409fn TIMG10() {
410 DRIVER.on_interrupt();
411}
412
413#[cfg(time_driver_timg11)]
414#[interrupt]
415fn TIMG11() {
416 DRIVER.on_interrupt();
417}
418
419// TODO: TIMG12 and TIMG13
420
421#[cfg(time_driver_timg14)]
422#[interrupt]
423fn TIMG14() {
424 DRIVER.on_interrupt();
425}
426
427#[cfg(time_driver_tima0)]
428#[interrupt]
429fn TIMA0() {
430 DRIVER.on_interrupt();
431}
432
433#[cfg(time_driver_tima1)]
434#[interrupt]
435fn TIMA1() {
436 DRIVER.on_interrupt();
437}