Skip to main content

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}