rpi_hal/timer.rs
1use crate::pac::SYSTMR;
2
3/// Free-running microsecond counter (BCM System Timer) and blocking
4/// delays built on it.
5///
6/// The System Timer is a plain memory-mapped 64-bit counter (as CLO +
7/// CHI) running at a fixed 1MHz, needing no configuration — it's
8/// already counting from boot.
9///
10/// Of the four compare registers C0-C3, only Compare 1 is used: the GPU
11/// firmware reserves C0 and C2 for itself, and C3 is left free.
12///
13/// Two properties of that compare shape every deadline API below, and
14/// neither is a limitation of this driver:
15///
16/// - **It is 32 bits wide, against a 64-bit counter.** The compare only
17/// sees CLO, the low half, so a deadline can only be expressed within
18/// 2^32us (~71.6 minutes) of the present, and any value matches once
19/// per wrap whether or not anyone armed it.
20/// - **It is an equality test, not a threshold.** CS.M1 is set when C1
21/// equals CLO, so a deadline the counter has already walked past does
22/// not fire late — it fires a whole wrap later.
23pub struct Timer {
24 systmr: SYSTMR,
25}
26
27/// The furthest ahead [`Timer::set_compare1`] will arm, chosen as half
28/// the ~71.6-minute Compare 1 range so a clamped deadline is nowhere
29/// near the wrap it has to stay clear of.
30///
31/// At this size the clamp is unreachable in any practical test, since it
32/// takes a deadline over half an hour out. Shrinking it temporarily — to
33/// a few tens of milliseconds — puts every ordinary deadline through the
34/// clamp instead, turning a path that fires twice an hour into one that
35/// fires constantly, which is the only cheap way to exercise it.
36const MAX_COMPARE1_DELTA_US: u64 = 1 << 31;
37
38impl Timer {
39 /// Wraps the System Timer peripheral. Needs no configuration —
40 /// it's already free-running from boot.
41 pub fn new(systmr: SYSTMR) -> Self {
42 Self { systmr }
43 }
44
45 /// Microseconds elapsed since boot.
46 pub fn now_micros(&self) -> u64 {
47 // CLO/CHI are two separate 32-bit registers forming one 64-bit
48 // counter — reading them isn't atomic, so guard against CLO
49 // wrapping between the two reads by re-checking CHI.
50 loop {
51 let hi = self.systmr.chi().read().bits();
52 let lo = self.systmr.clo().read().bits();
53 if self.systmr.chi().read().bits() == hi {
54 return ((hi as u64) << 32) | lo as u64;
55 }
56 }
57 }
58
59 /// Busy-waits for approximately `us` microseconds.
60 pub fn delay_us(&self, us: u32) {
61 let target = self.now_micros() + us as u64;
62 while self.now_micros() < target {}
63 }
64
65 /// Busy-waits for approximately `ms` milliseconds.
66 pub fn delay_ms(&self, ms: u32) {
67 let target = self.now_micros() + (ms as u64) * 1000;
68 while self.now_micros() < target {}
69 }
70
71 /// Arms Compare 1 for the absolute [`now_micros`](Self::now_micros)
72 /// deadline `deadline_us`, raising an IRQ at that point (once routed
73 /// via `crate::lic::Lic::enable_timer1_irq` and unmasked via
74 /// [`crate::irq::enable_irq`]).
75 ///
76 /// Returns `false` if the deadline was already in the past by the
77 /// time it was armed — including the narrow case where the counter
78 /// passed it between computing the target and writing it. Because
79 /// the compare is an equality test (see the module doc comment),
80 /// that deadline would otherwise not fire for another ~71.6 minutes,
81 /// so a caller that still wants a timely wake-up must recompute
82 /// against a fresh `now_micros` and arm again rather than assume it
83 /// is pending.
84 ///
85 /// A deadline further out than ~35.8 minutes is clamped to that,
86 /// keeping the write inside the range the 32-bit compare can
87 /// express. The match then fires early, which callers must tolerate
88 /// regardless: they have to re-check their own deadline against
89 /// `now_micros` on every match and re-arm if it hasn't arrived yet.
90 ///
91 /// Does not clear a match already pending from a previous arm —
92 /// pair with [`clear_compare1_match`](Self::clear_compare1_match).
93 pub fn set_compare1(&self, deadline_us: u64) -> bool {
94 let now = self.now_micros();
95 if deadline_us <= now {
96 return false;
97 }
98
99 let target = deadline_us.min(now + MAX_COMPARE1_DELTA_US);
100 unsafe {
101 self.systmr.c1().write(|w| w.bits(target as u32));
102 }
103
104 // Re-read rather than trusting the pre-write `now`: if the
105 // counter reached the target while the write was in flight, the
106 // equality may have gone by unmatched. Clear whatever this arm
107 // did latch, so a caller acting on `false` doesn't then take a
108 // spurious IRQ for a deadline it has already given up on.
109 if self.now_micros() >= target {
110 self.clear_compare1_match();
111 return false;
112 }
113
114 true
115 }
116
117 /// Whether Compare 1 has matched (CS.M1) and not yet been cleared.
118 pub fn compare1_matched(&self) -> bool {
119 self.systmr.cs().read().m1().bit_is_set()
120 }
121
122 /// Acknowledges a fired Compare 1 match (write-1-to-clear CS.M1)
123 /// without arming another. Call this from the IRQ handler —
124 /// leaving the match flag set would just re-trigger the same IRQ
125 /// immediately after returning.
126 pub fn clear_compare1_match(&self) {
127 unsafe {
128 self.systmr
129 .cs()
130 .write_with_zero(|w| w.m1().clear_bit_by_one());
131 }
132 }
133
134 /// Arms Compare 1 to match `period_us` from now.
135 ///
136 /// A convenience wrapper over [`set_compare1`](Self::set_compare1)
137 /// for the fixed-period case, where the deadline is always close
138 /// enough to the present that neither the clamp nor the
139 /// already-passed case can be reached by a period of more than a
140 /// couple of microseconds.
141 pub fn arm_periodic_c1(&self, period_us: u32) {
142 self.set_compare1(self.now_micros() + period_us as u64);
143 }
144
145 /// Acknowledges a fired Compare 1 match and rearms the next period —
146 /// [`clear_compare1_match`](Self::clear_compare1_match) followed by
147 /// [`arm_periodic_c1`](Self::arm_periodic_c1).
148 pub fn ack_c1(&self, period_us: u32) {
149 self.clear_compare1_match();
150 self.arm_periodic_c1(period_us);
151 }
152}
153
154impl embedded_hal::delay::DelayNs for Timer {
155 /// Rounds up to the System Timer's 1us resolution, then delegates
156 /// to `delay_us` — a nonzero request always waits at least one
157 /// tick instead of returning immediately.
158 fn delay_ns(&mut self, ns: u32) {
159 self.delay_us(ns.div_ceil(1000));
160 }
161
162 /// Delegates to the inherent `Timer::delay_us`.
163 fn delay_us(&mut self, us: u32) {
164 Timer::delay_us(self, us);
165 }
166
167 /// Delegates to the inherent `Timer::delay_ms`.
168 fn delay_ms(&mut self, ms: u32) {
169 Timer::delay_ms(self, ms);
170 }
171}