Skip to main content

esp_emac/
reset.rs

1// SPDX-License-Identifier: GPL-2.0-or-later OR Apache-2.0
2// Copyright (c) Viacheslav Bocharov <v@baodeep.com> and JetHome (r)
3
4//! DMA software reset state machine.
5//!
6//! Wraps the `DMABUSMODE.SWR` bit-poll loop in a small struct that takes
7//! a [`DelayNs`] implementation. Note that
8//! [`embedded_hal::delay::DelayNs`] is a **blocking** delay trait — the
9//! poll loop will busy-wait the calling task until the controller
10//! self-clears or the timeout expires. Callers from async contexts
11//! should either accept that block (the reset finishes in a few
12//! microseconds on real hardware) or pass a `DelayNs` implementation
13//! whose `delay_us` yields to the executor.
14//!
15//! For a true async variant — using `embedded_hal_async::delay::DelayNs`
16//! so each poll-step `.await`s and yields control back to the executor —
17//! enable the `async` cargo feature and use
18//! `crate::reset::async_impl::AsyncResetController`, defined in the
19//! cfg-gated `async_impl` submodule.
20
21use embedded_hal::delay::DelayNs;
22
23use crate::regs::dma::{self, bus_mode, DMABUSMODE};
24
25/// Default soft-reset timeout (matches ESP-IDF / ph-esp32-mac).
26pub const SOFT_RESET_TIMEOUT_MS: u32 = 100;
27/// Polling interval while waiting for `DMABUSMODE.SWR` to self-clear.
28pub const RESET_POLL_INTERVAL_US: u32 = 100;
29
30/// Reset failure cause.
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32#[cfg_attr(feature = "defmt", derive(defmt::Format))]
33pub enum ResetError {
34    /// `DMABUSMODE.SWR` did not self-clear within the configured timeout.
35    Timeout,
36}
37
38/// Owns a [`DelayNs`] implementation and exposes the soft-reset routine.
39pub struct ResetController<D: DelayNs> {
40    delay: D,
41    timeout_ms: u32,
42}
43
44impl<D: DelayNs> ResetController<D> {
45    /// Build a controller with the [`SOFT_RESET_TIMEOUT_MS`] default.
46    pub fn new(delay: D) -> Self {
47        Self {
48            delay,
49            timeout_ms: SOFT_RESET_TIMEOUT_MS,
50        }
51    }
52
53    /// Build a controller with a caller-chosen timeout.
54    pub fn with_timeout(delay: D, timeout_ms: u32) -> Self {
55        Self { delay, timeout_ms }
56    }
57
58    /// Issue the DMA software reset and wait for `DMABUSMODE.SWR` to
59    /// self-clear. Returns [`ResetError::Timeout`] if it does not happen
60    /// within `timeout_ms`. The reset clears the entire DMA + MAC core to
61    /// its hardware-default state.
62    pub fn soft_reset(&mut self) -> Result<(), ResetError> {
63        // Setting the SWR bit triggers the reset; the bit auto-clears when
64        // the controller is back to a known state.
65        // SAFETY: DMABUSMODE is a known-valid 32-bit register.
66        unsafe { dma::set_bits(DMABUSMODE, bus_mode::SW_RESET) };
67
68        // Compute in u64 then clamp to u32 so very large `timeout_ms`
69        // values still produce a sane upper bound on the polling loop:
70        // `timeout_ms * 1000` would wrap a u32 once `timeout_ms`
71        // exceeds ~4.29 × 10⁶ ms (~71.6 minutes). Force at least one
72        // iteration so a `timeout_ms = 0` caller still gets a single
73        // read (the soft-reset bit usually clears within a few
74        // microseconds — much faster than any meaningful timeout).
75        let max_iters = (u64::from(self.timeout_ms) * 1000 / u64::from(RESET_POLL_INTERVAL_US))
76            .clamp(1, u64::from(u32::MAX)) as u32;
77        // Read-then-(maybe-)delay loop: after the last read we go
78        // straight to `Timeout` instead of sleeping a final poll
79        // interval we'd never check. This keeps the actual wait at
80        // ≤ `(max_iters - 1) * RESET_POLL_INTERVAL_US` (matching
81        // `timeout_ms`) and lets `timeout_ms = 0` actually mean
82        // "single read, no sleep" once the iteration clamp gives us
83        // exactly one pass.
84        let mut iter = 0u32;
85        loop {
86            // SAFETY: same address, read-only volatile.
87            let still_in_progress = unsafe { dma::read(DMABUSMODE) } & bus_mode::SW_RESET != 0;
88            if !still_in_progress {
89                return Ok(());
90            }
91            iter += 1;
92            if iter >= max_iters {
93                return Err(ResetError::Timeout);
94            }
95            self.delay.delay_us(RESET_POLL_INTERVAL_US);
96        }
97    }
98
99    /// Configured timeout in milliseconds.
100    pub fn timeout_ms(&self) -> u32 {
101        self.timeout_ms
102    }
103}
104
105#[cfg(feature = "async")]
106pub mod async_impl {
107    //! Async-flavoured variant of [`super::ResetController`].
108    //!
109    //! Identical bit-poll semantics, but each poll-step `.await`s the
110    //! delay so the calling task yields control to the executor instead
111    //! of busy-waiting. Enabled via the `async` cargo feature.
112
113    use embedded_hal_async::delay::DelayNs;
114
115    use super::{ResetError, RESET_POLL_INTERVAL_US, SOFT_RESET_TIMEOUT_MS};
116    use crate::regs::dma::{self, bus_mode, DMABUSMODE};
117
118    /// Async counterpart of [`super::ResetController`]. Owns an
119    /// [`embedded_hal_async::delay::DelayNs`] implementation and exposes
120    /// `soft_reset` as an `async fn`.
121    pub struct AsyncResetController<D: DelayNs> {
122        delay: D,
123        timeout_ms: u32,
124    }
125
126    impl<D: DelayNs> AsyncResetController<D> {
127        /// Build a controller with the [`SOFT_RESET_TIMEOUT_MS`] default.
128        pub fn new(delay: D) -> Self {
129            Self {
130                delay,
131                timeout_ms: SOFT_RESET_TIMEOUT_MS,
132            }
133        }
134
135        /// Build a controller with a caller-chosen timeout.
136        pub fn with_timeout(delay: D, timeout_ms: u32) -> Self {
137            Self { delay, timeout_ms }
138        }
139
140        /// Issue the DMA software reset and `.await` the polling loop
141        /// until `DMABUSMODE.SWR` self-clears. Returns
142        /// [`ResetError::Timeout`] if the bit does not clear within
143        /// `timeout_ms`.
144        pub async fn soft_reset(&mut self) -> Result<(), ResetError> {
145            // SAFETY: DMABUSMODE is a known-valid 32-bit register.
146            unsafe { dma::set_bits(DMABUSMODE, bus_mode::SW_RESET) };
147
148            // Floor `max_iters` to 1 so a `timeout_ms = 0` caller
149            // still gets a single check — the soft-reset bit usually
150            // clears within microseconds.
151            let max_iters = (u64::from(self.timeout_ms) * 1000 / u64::from(RESET_POLL_INTERVAL_US))
152                .clamp(1, u64::from(u32::MAX)) as u32;
153            // Read-then-(maybe-)await loop: after the last read we
154            // return `Timeout` directly instead of yielding for a
155            // final poll interval we'd never check, so the actual
156            // wait stays ≤ `(max_iters - 1) * RESET_POLL_INTERVAL_US`
157            // and `timeout_ms = 0` means "single read, no yield".
158            let mut iter = 0u32;
159            loop {
160                // SAFETY: same address, read-only volatile.
161                let still_in_progress = unsafe { dma::read(DMABUSMODE) } & bus_mode::SW_RESET != 0;
162                if !still_in_progress {
163                    return Ok(());
164                }
165                iter += 1;
166                if iter >= max_iters {
167                    return Err(ResetError::Timeout);
168                }
169                self.delay.delay_us(RESET_POLL_INTERVAL_US).await;
170            }
171        }
172
173        /// Configured timeout in milliseconds.
174        pub fn timeout_ms(&self) -> u32 {
175            self.timeout_ms
176        }
177    }
178
179    #[cfg(test)]
180    mod tests {
181        use super::*;
182
183        // Compile-only check: confirms the public API takes any
184        // `embedded_hal_async::delay::DelayNs` impl and returns an
185        // awaitable `Result`. Not a runtime test — the polling body
186        // would need an executor and a real (or mocked) MMIO surface.
187        #[allow(dead_code)]
188        async fn _compile_check<D: DelayNs>(d: D) -> Result<(), ResetError> {
189            let mut ctl = AsyncResetController::with_timeout(d, 100);
190            ctl.soft_reset().await
191        }
192    }
193}