Skip to main content

ws63_hal/
timer.rs

1//! Timer driver for WS63 (3 independent 32-bit timers).
2//!
3//! Each timer can operate in one-shot or periodic mode.
4//! The timer counts at the **TCXO crystal clock** ([`TIMER_CLOCK_HZ`] = 24 MHz on
5//! 24 MHz-crystal boards) — NOT the 240 MHz CPU/PLL clock. The vendor SDK programs
6//! the timer to the crystal via `timer_porting_clock_value_set(REQ_24M)`.
7//!
8//! # Usage
9//!
10//! ```ignore
11//! let timer = TimerDriver::new(peripherals.TIMER);
12//! let mut oneshot = timer.oneshot(0);
13//! oneshot.start(24_000); // 1ms at 24MHz
14//! while !oneshot.expired() {}
15//! ```
16
17use crate::peripherals::Timer;
18use crate::soc::ws63::TIMER_CLOCK_HZ;
19
20/// Timer operating mode.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum TimerMode {
23    /// One-shot: timer counts once and stops.
24    OneShot = 0,
25    /// Periodic: timer reloads and repeats.
26    Periodic = 1,
27}
28
29/// Timer driver managing 3 independent timer channels.
30pub struct TimerDriver<'d> {
31    _timer: Timer<'d>,
32}
33
34impl<'d> TimerDriver<'d> {
35    /// Create a new timer driver.
36    pub fn new(timer: Timer<'d>) -> Self {
37        Self { _timer: timer }
38    }
39
40    fn regs(&self) -> &'static ws63_pac::timer::RegisterBlock {
41        // SAFETY: PAC peripheral pointer is a static physical MMIO address, always valid
42        unsafe { &*Timer::ptr() }
43    }
44
45    /// Configure a timer channel.
46    pub fn configure(&self, n: usize, mode: TimerMode, load_value: u32) {
47        let r = self.regs();
48        match n {
49            0 => r.timer0_load_count(0).write(|w| unsafe { w.bits(load_value) }),
50            1 => r.timer0_load_count(1).write(|w| unsafe { w.bits(load_value) }),
51            2 => r.timer0_load_count(2).write(|w| unsafe { w.bits(load_value) }),
52            _ => unreachable!(),
53        };
54        let ctl = ((mode as u32) & 0x3) << 1;
55        match n {
56            0 => r.timer0_control(0).write(|w| unsafe { w.bits(ctl) }),
57            1 => r.timer0_control(1).write(|w| unsafe { w.bits(ctl) }),
58            2 => r.timer0_control(2).write(|w| unsafe { w.bits(ctl) }),
59            _ => unreachable!(),
60        };
61    }
62
63    /// Enable a timer channel.
64    pub fn enable(&self, n: usize) {
65        let r = self.regs();
66        let prev = match n {
67            0 => r.timer0_control(0).read().bits(),
68            1 => r.timer0_control(1).read().bits(),
69            2 => r.timer0_control(2).read().bits(),
70            _ => unreachable!(),
71        };
72        match n {
73            0 => r.timer0_control(0).write(|w| unsafe { w.bits(prev | 1) }),
74            1 => r.timer0_control(1).write(|w| unsafe { w.bits(prev | 1) }),
75            2 => r.timer0_control(2).write(|w| unsafe { w.bits(prev | 1) }),
76            _ => unreachable!(),
77        };
78    }
79
80    /// Disable a timer channel.
81    pub fn disable(&self, n: usize) {
82        let r = self.regs();
83        let prev = match n {
84            0 => r.timer0_control(0).read().bits(),
85            1 => r.timer0_control(1).read().bits(),
86            2 => r.timer0_control(2).read().bits(),
87            _ => unreachable!(),
88        };
89        match n {
90            0 => r.timer0_control(0).write(|w| unsafe { w.bits(prev & !1) }),
91            1 => r.timer0_control(1).write(|w| unsafe { w.bits(prev & !1) }),
92            2 => r.timer0_control(2).write(|w| unsafe { w.bits(prev & !1) }),
93            _ => unreachable!(),
94        };
95    }
96
97    /// Read the current counter value.
98    pub fn current_value(&self, n: usize) -> u32 {
99        let r = self.regs();
100        match n {
101            0 => r.timer0_current_value(0).read().bits(),
102            1 => r.timer0_current_value(1).read().bits(),
103            2 => r.timer0_current_value(2).read().bits(),
104            _ => unreachable!(),
105        }
106    }
107
108    /// Check if a timer interrupt is pending.
109    pub fn interrupt_pending(&self, n: usize) -> bool {
110        let r = self.regs();
111        match n {
112            0 => r.timer0_raw_intr(0).read().bits() & 1 != 0,
113            1 => r.timer0_raw_intr(1).read().bits() & 1 != 0,
114            2 => r.timer0_raw_intr(2).read().bits() & 1 != 0,
115            _ => unreachable!(),
116        }
117    }
118
119    /// Clear a timer interrupt (per-channel EOI).
120    pub fn clear_interrupt(&self, n: usize) {
121        let r = self.regs();
122        match n {
123            0 => {
124                let _ = r.timer0_eoi(0).read().bits();
125            }
126            1 => {
127                let _ = r.timer0_eoi(1).read().bits();
128            }
129            2 => {
130                let _ = r.timer0_eoi(2).read().bits();
131            }
132            _ => unreachable!(),
133        }
134    }
135
136    /// Create a one-shot timer wrapper for the given channel.
137    pub fn oneshot(&self, channel: usize) -> OneShotTimer<'_> {
138        OneShotTimer { driver: self, channel }
139    }
140
141    /// Create a periodic timer wrapper for the given channel.
142    pub fn periodic(&self, channel: usize) -> PeriodicTimer<'_> {
143        PeriodicTimer { driver: self, channel }
144    }
145}
146
147// ── One-shot timer ────────────────────────────────────────────────
148
149/// One-shot timer wrapper.
150///
151/// Counts down from a loaded value and stops when it reaches zero.
152pub struct OneShotTimer<'a> {
153    driver: &'a TimerDriver<'a>,
154    channel: usize,
155}
156
157impl OneShotTimer<'_> {
158    /// Start the one-shot timer with a count value.
159    ///
160    /// `count` is in timer clock ticks (TCXO = 24MHz → 1 tick ≈ 41.7ns).
161    pub fn start(&mut self, count: u32) {
162        self.driver.configure(self.channel, TimerMode::OneShot, count);
163        self.driver.enable(self.channel);
164    }
165
166    /// Start the timer for the given duration in microseconds.
167    ///
168    /// Max duration: ~178 seconds at 24MHz (u32 ticks limit).
169    /// For longer durations, use `start_millis()` or loop `start_micros()`.
170    pub fn start_micros(&mut self, us: u32) {
171        let ticks64 = TIMER_CLOCK_HZ as u64 * us as u64 / 1_000_000;
172        // Clamp to u32 max — delays longer than ~178s at 24MHz
173        let ticks = if ticks64 > u32::MAX as u64 { u32::MAX } else { ticks64 as u32 };
174        self.start(ticks);
175    }
176
177    /// Start the timer for the given duration in milliseconds.
178    ///
179    /// Max duration: ~178,956ms (~178s) at 24MHz.
180    /// For longer durations, repeat this call in a loop.
181    pub fn start_millis(&mut self, ms: u32) {
182        let ticks64 = TIMER_CLOCK_HZ as u64 * ms as u64 / 1_000;
183        let ticks = if ticks64 > u32::MAX as u64 { u32::MAX } else { ticks64 as u32 };
184        self.start(ticks);
185    }
186
187    /// Check if the timer has expired.
188    pub fn expired(&self) -> bool {
189        self.driver.interrupt_pending(self.channel)
190    }
191
192    /// Wait for the timer to expire (busy-loop).
193    pub fn wait(&self) {
194        while !self.expired() {}
195        self.driver.clear_interrupt(self.channel);
196    }
197
198    /// Get the current counter value.
199    pub fn current(&self) -> u32 {
200        self.driver.current_value(self.channel)
201    }
202
203    /// Stop the timer.
204    pub fn stop(&self) {
205        self.driver.disable(self.channel);
206    }
207
208    /// Clear the interrupt flag.
209    pub fn clear(&self) {
210        self.driver.clear_interrupt(self.channel);
211    }
212}
213
214impl embedded_hal::delay::DelayNs for OneShotTimer<'_> {
215    fn delay_ns(&mut self, ns: u32) {
216        let ticks64 = (TIMER_CLOCK_HZ as u64 * ns as u64) / 1_000_000_000;
217        let ticks = if ticks64 > u32::MAX as u64 { u32::MAX } else { ticks64 as u32 };
218        if ticks > 0 {
219            self.start(ticks);
220            self.wait();
221        }
222    }
223
224    fn delay_us(&mut self, us: u32) {
225        self.start_micros(us);
226        self.wait();
227    }
228
229    fn delay_ms(&mut self, ms: u32) {
230        self.start_millis(ms);
231        self.wait();
232    }
233}
234
235// ── Periodic timer ────────────────────────────────────────────────
236
237/// Periodic timer wrapper.
238///
239/// Counts down and automatically reloads, generating an interrupt each cycle.
240pub struct PeriodicTimer<'a> {
241    driver: &'a TimerDriver<'a>,
242    channel: usize,
243}
244
245impl PeriodicTimer<'_> {
246    /// Start the periodic timer with the given period in ticks.
247    pub fn start(&mut self, period: u32) {
248        self.driver.configure(self.channel, TimerMode::Periodic, period);
249        self.driver.enable(self.channel);
250    }
251
252    /// Start the periodic timer with the period in microseconds.
253    pub fn start_micros(&mut self, us: u32) {
254        let ticks64 = TIMER_CLOCK_HZ as u64 * us as u64 / 1_000_000;
255        let ticks = if ticks64 > u32::MAX as u64 { u32::MAX } else { ticks64 as u32 };
256        self.start(ticks);
257    }
258
259    /// Check if a period has elapsed (interrupt pending).
260    pub fn tick_elapsed(&self) -> bool {
261        self.driver.interrupt_pending(self.channel)
262    }
263
264    /// Wait for the next timer tick.
265    pub fn wait_tick(&self) {
266        while !self.tick_elapsed() {}
267        self.driver.clear_interrupt(self.channel);
268    }
269
270    /// Stop the timer.
271    pub fn stop(&self) {
272        self.driver.disable(self.channel);
273    }
274
275    /// Get the current counter value.
276    pub fn current(&self) -> u32 {
277        self.driver.current_value(self.channel)
278    }
279
280    /// Clear the tick interrupt flag.
281    pub fn clear_tick(&self) {
282        self.driver.clear_interrupt(self.channel);
283    }
284}
285
286// ── Tests ──────────────────────────────────────────────────────
287
288#[cfg(test)]
289mod tests {
290    use crate::soc::ws63::TIMER_CLOCK_HZ;
291
292    // The timer counts at the TCXO crystal clock (TIMER_CLOCK_HZ = 24 MHz), so
293    // there are TICKS_PER_US ticks per microsecond and the u32 one-shot caps at
294    // MAX_SAFE_US (≈178 s at 24 MHz) before the conversion saturates.
295    const TICKS_PER_US: u64 = TIMER_CLOCK_HZ as u64 / 1_000_000;
296    const MAX_SAFE_US: u64 = u32::MAX as u64 / TICKS_PER_US;
297
298    fn ticks_for_us(us: u64) -> u32 {
299        let t = TIMER_CLOCK_HZ as u64 * us / 1_000_000;
300        if t > u32::MAX as u64 { u32::MAX } else { t as u32 }
301    }
302
303    #[test]
304    fn oneshot_overflow_clamps() {
305        // µs beyond the safe range saturates to u32::MAX (no wrap, no panic).
306        assert_eq!(ticks_for_us(MAX_SAFE_US + 1_000_000), u32::MAX);
307    }
308
309    #[test]
310    fn small_value_does_not_clamp() {
311        // 100 µs → 100 * TICKS_PER_US ticks, well within u32.
312        assert_eq!(ticks_for_us(100), (100 * TICKS_PER_US) as u32);
313    }
314
315    #[test]
316    fn max_safe_value_not_clamped() {
317        let ticks64 = TIMER_CLOCK_HZ as u64 * MAX_SAFE_US / 1_000_000;
318        assert!(ticks64 <= u32::MAX as u64);
319        assert_eq!(ticks_for_us(MAX_SAFE_US), ticks64 as u32);
320    }
321}
322
323// ── Property-based fuzz tests ──────────────────────────────────
324
325#[cfg(test)]
326mod proptests {
327    use crate::soc::ws63::TIMER_CLOCK_HZ;
328    use proptest::prelude::*;
329
330    const MAX_SAFE_US: u64 = u32::MAX as u64 / (TIMER_CLOCK_HZ as u64 / 1_000_000);
331
332    fn ticks64(us: u64) -> u64 {
333        TIMER_CLOCK_HZ as u64 * us / 1_000_000
334    }
335
336    proptest! {
337        /// Fuzz: ticks calculation + clamp never panics for any u32 µs input.
338        #[test]
339        fn ticks_never_panics(us in any::<u32>()) {
340            let t = ticks64(us as u64);
341            let _ = if t > u32::MAX as u64 { u32::MAX } else { t as u32 };
342        }
343
344        /// Fuzz: µs within the safe range never overflow u32.
345        #[test]
346        fn safe_range_not_clamped(us in 0u64..=MAX_SAFE_US) {
347            prop_assert!(ticks64(us) <= u32::MAX as u64, "safe us={} -> ticks64={}", us, ticks64(us));
348        }
349
350        /// Fuzz: µs beyond the safe range always overflow u32 (and thus clamp).
351        #[test]
352        fn overflow_always_clamps(us in (MAX_SAFE_US + 1)..=u32::MAX as u64) {
353            prop_assert!(ticks64(us) > u32::MAX as u64);
354        }
355    }
356}
357
358// ── Async (embedded-hal-async) ──────────────────────────────────────────────
359#[cfg(feature = "async")]
360mod asynch_impl {
361    use super::{TIMER_CLOCK_HZ, Timer, TimerDriver, TimerMode};
362    use crate::asynch::IrqSignal;
363    use crate::interrupt::{self, Interrupt};
364    use core::future::Future;
365    use core::pin::Pin;
366    use core::task::{Context, Poll};
367
368    static TIMER_SIGNAL: [IrqSignal; 3] = [IrqSignal::new(), IrqSignal::new(), IrqSignal::new()];
369
370    fn ch_irq(ch: usize) -> Interrupt {
371        match ch {
372            0 => Interrupt::TIMER_INT0,
373            1 => Interrupt::TIMER_INT1,
374            _ => Interrupt::TIMER_INT2,
375        }
376    }
377
378    /// Timer trap-handler hook: stop channel `ch`'s one-shot, clear its
379    /// interrupt, and wake the awaiting [`AsyncDelay`] future. Call this from the
380    /// machine-interrupt trap when `mcause` is TIMER_INT0..2 (IRQ 26..28). The
381    /// EOI clears `mip`, so no `LOCIPCLR` is needed for these MIE-class lines.
382    pub fn on_interrupt(ch: usize) {
383        // SAFETY: RMW of the timer MMIO block. The AsyncDelay owns the peripheral
384        // handle, but the ISR uses raw register access (the standard ISR pattern).
385        let r = unsafe { &*Timer::ptr() };
386        match ch {
387            0 => {
388                let prev = r.timer0_control(0).read().bits();
389                r.timer0_control(0).write(|w| unsafe { w.bits(prev & !1) }); // stop (clear EN)
390                let _ = r.timer0_eoi(0).read().bits(); // EOI (read-clear)
391            }
392            1 => {
393                let prev = r.timer0_control(1).read().bits();
394                r.timer0_control(1).write(|w| unsafe { w.bits(prev & !1) });
395                let _ = r.timer0_eoi(1).read().bits();
396            }
397            _ => {
398                let prev = r.timer0_control(2).read().bits();
399                r.timer0_control(2).write(|w| unsafe { w.bits(prev & !1) });
400                let _ = r.timer0_eoi(2).read().bits();
401            }
402        }
403        TIMER_SIGNAL[ch].signal();
404    }
405
406    /// Async delay backed by one WS63 TIMER channel (one-shot + completion IRQ).
407    ///
408    /// Implements [`embedded_hal_async::delay::DelayNs`]: each `delay_*().await`
409    /// arms the channel one-shot, parks the task until the channel's IRQ fires,
410    /// then returns. The app must route the timer trap to [`on_interrupt`] and
411    /// have enabled global interrupts (see `ws63-examples/async_delay`).
412    pub struct AsyncDelay<'d> {
413        driver: TimerDriver<'d>,
414        channel: usize,
415    }
416
417    impl<'d> AsyncDelay<'d> {
418        /// Create an async delay on `channel` (0..=2).
419        pub fn new(timer: Timer<'d>, channel: usize) -> Self {
420            Self { driver: TimerDriver::new(timer), channel }
421        }
422
423        async fn delay_ticks(&mut self, ticks: u32) {
424            let ch = self.channel;
425            TIMER_SIGNAL[ch].reset();
426            self.driver.clear_interrupt(ch);
427            self.driver.configure(ch, TimerMode::OneShot, ticks.max(1));
428            // SAFETY: enabling a known, fixed WS63 timer IRQ line.
429            unsafe { interrupt::enable(ch_irq(ch)) };
430            self.driver.enable(ch);
431            DelayFuture { ch }.await;
432        }
433    }
434
435    struct DelayFuture {
436        ch: usize,
437    }
438
439    impl Future for DelayFuture {
440        type Output = ();
441        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
442            if TIMER_SIGNAL[self.ch].take_fired() {
443                Poll::Ready(())
444            } else {
445                TIMER_SIGNAL[self.ch].register(cx.waker());
446                Poll::Pending
447            }
448        }
449    }
450
451    impl embedded_hal_async::delay::DelayNs for AsyncDelay<'_> {
452        async fn delay_ns(&mut self, ns: u32) {
453            let ticks = (TIMER_CLOCK_HZ as u64 * ns as u64 / 1_000_000_000) as u32;
454            self.delay_ticks(ticks).await;
455        }
456    }
457}
458
459#[cfg(feature = "async")]
460pub use asynch_impl::{AsyncDelay, on_interrupt};