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}