1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
|
//! 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();
}
|