esp_hal/ethernet/mac.rs
1//! MAC register abstraction for the EMAC driver.
2//!
3//! Provides higher-level helpers for MAC/DMA initialization and runtime
4//! control, all accessing the three EMAC register blocks via their
5//! `::regs()` static accessors.
6
7use crate::peripherals::{EMAC_DMA, EMAC_MAC};
8
9/// Programmable DMA burst length, matching the esp-idf per-target defaults.
10const DMA_BURST_LEN: u8 = property!("ethernet.dma_burst_len");
11const TX_CHECKSUM_OFFLOAD: bool = property!("ethernet.tx_checksum_offload");
12
13/// Returns the `miicsrclk` value for the MDIO management clock divider.
14///
15/// On ESP32 the MDC CSR clock source is the APB clock (fixed at 80 MHz), so
16/// value 3 (35–60 MHz range → /26) has always been used and works in practice.
17///
18/// On ESP32-P4 and ESP32-S31 the CSR clock source is the SYS clock. Both vary with the selected
19/// [`crate::clock::CpuClock`] preset. The divider is computed at runtime to keep MDC within the
20/// 1–2.5 MHz range required by IEEE 802.3 clause 22.
21fn mdc_csr_clock_range() -> u8 {
22 // Matches emac_hal_set_csr_clock_range() in esp-idf (emac_hal.c).
23 // The DWC_gmac only defines encoding values 0–5; value 6+
24 // is reserved and must not be used.
25 //
26 // emac_crs_div_table = {42, 62, 16, 26, 102, 124}
27 // encoding 0 → /42 (60–100 MHz)
28 // encoding 1 → /62 (100–150 MHz)
29 // encoding 2 → /16 (20–35 MHz)
30 // encoding 3 → /26 (35–60 MHz)
31 // encoding 4 → /102 (150–250 MHz)
32 // encoding 5 → /124 (≥ 250 MHz, slightly over 2.5 MHz spec at high SYS clocks)
33 let hz = cfg_select! {
34 soc_has_clock_node_sys_clk => crate::clock::ll::sys_clk_frequency(),
35 _ => 40_000_000,
36 };
37 match hz {
38 hz if hz >= 250_000_000 => 5, // /124
39 hz if hz >= 150_000_000 => 4, // /102
40 hz if hz >= 100_000_000 => 1, // /62
41 hz if hz >= 60_000_000 => 0, // /42
42 hz if hz >= 35_000_000 => 3, // /26
43 _ => 2, // /16
44 }
45}
46
47/// Link speed.
48#[derive(Clone, Copy, Debug, Eq, PartialEq)]
49#[cfg_attr(feature = "defmt", derive(defmt::Format))]
50#[non_exhaustive]
51pub enum Speed {
52 /// 10 Mbit/s
53 _10M,
54 /// 100 Mbit/s
55 _100M,
56 /// 1000 Mbit/s (RGMII)
57 _1000M,
58}
59
60/// Link duplex mode.
61#[derive(Clone, Copy, Debug, Eq, PartialEq)]
62#[cfg_attr(feature = "defmt", derive(defmt::Format))]
63#[non_exhaustive]
64pub enum Duplex {
65 /// Half duplex
66 Half,
67 /// Full duplex
68 Full,
69}
70
71/// Link state reported by the PHY.
72#[derive(Clone, Copy, Debug, Eq, PartialEq)]
73#[cfg_attr(feature = "defmt", derive(defmt::Format))]
74pub struct LinkState {
75 /// Whether the link is established.
76 pub up: bool,
77 /// Link speed (valid if `up` is true).
78 pub speed: Speed,
79 /// Link duplex mode (valid if `up` is true).
80 pub duplex: Duplex,
81}
82
83/// Zero-sized handle that provides register-level operations on the three EMAC
84/// blocks.
85///
86/// All methods use `EMAC_MAC::regs()` / `EMAC_DMA::regs()` static accessors; the singleton
87/// ownership is tracked by the `Ethernet` struct.
88#[derive(Clone, Copy)]
89pub(super) struct EmacRegs;
90
91impl EmacRegs {
92 // ── DMA soft-reset ────────────────────────────────────────────────────
93
94 /// Issues a DMA soft-reset and spins until the hardware clears the bit.
95 pub fn dma_soft_reset(&self) {
96 EMAC_DMA::regs()
97 .dmabusmode()
98 .modify(|_, w| w.sw_rst().set_bit());
99
100 while EMAC_DMA::regs().dmabusmode().read().sw_rst().bit_is_set() {}
101
102 // Bus mode per esp-idf `emac_hal_init_dma_default`: enhanced 32-byte
103 // descriptors, mixed burst, address-aligned beats, one PBL for both
104 // directions, round-robin arbitration with a 1:1 RX/TX ratio.
105 EMAC_DMA::regs().dmabusmode().modify(|_, w| unsafe {
106 w.alt_desc_size().set_bit();
107 w.dmamixedburst().set_bit();
108 w.dmaaddralibea().set_bit();
109 w.fixed_burst().clear_bit();
110 w.use_sep_pbl().clear_bit();
111 w.prog_burst_len().bits(DMA_BURST_LEN);
112 w.desc_skip_len().bits(0);
113 w.dma_arb_sch().clear_bit();
114 w.pri_ratio().bits(0);
115 w
116 });
117 }
118
119 /// Configures the DMA operation mode, matching esp-idf
120 /// `emac_hal_init_dma_default`.
121 ///
122 /// RX store-and-forward follows `ethernet.rx_store_and_forward`. TX
123 /// store-and-forward is enabled only with TX checksum offload, which
124 /// needs the whole frame in the FIFO. Thresholds stay at 64 bytes for
125 /// cut-through.
126 pub fn dma_init_op_mode(&self) {
127 // The TX FIFO flush must complete before any other write to this
128 // register, so it is issued on its own first.
129 EMAC_DMA::regs()
130 .dmaoperation_mode()
131 .modify(|_, w| w.flush_tx_fifo().set_bit());
132 for _ in 0..1000 {
133 if !EMAC_DMA::regs()
134 .dmaoperation_mode()
135 .read()
136 .flush_tx_fifo()
137 .bit_is_set()
138 {
139 break;
140 }
141 }
142
143 EMAC_DMA::regs().dmaoperation_mode().modify(|_, w| unsafe {
144 // With RSF, drop FCS/length (and TCP/IP, if RX COE is on) error frames.
145 w.dis_drop_tcpip_err_fram()
146 .bit(!cfg!(ethernet_rx_store_and_forward));
147 // Flush received frames when descriptors or buffers run out.
148 w.dis_flush_recv_frames().clear_bit();
149 w.rx_store_forward()
150 .bit(cfg!(ethernet_rx_store_and_forward));
151 // Payload checksum insertion (TDES0.CIC) requires TX store-and-forward.
152 w.tx_str_fwd().bit(TX_CHECKSUM_OFFLOAD);
153 w.tx_thresh_ctrl().bits(0); // 64 bytes
154 w.rx_thresh_ctrl().bits(0); // 64 bytes
155 w.fwd_err_frame().clear_bit();
156 w.fwd_under_gf().clear_bit();
157 // Start on a second frame before the first one's status is read.
158 w.opt_second_frame().set_bit();
159 w
160 });
161 }
162
163 // ── DMA operation ─────────────────────────────────────────────────────
164
165 /// Starts the MAC and DMA transmit and receive paths.
166 ///
167 /// The order follows esp-idf `emac_hal_start`. The EMAC Databook allows
168 /// starting the DMA before enabling the MAC transmitter, but doing so has
169 /// been observed to hang the transmitter.
170 pub fn dma_start(&self) {
171 EMAC_MAC::regs().config().modify(|_, w| w.tx().set_bit());
172 EMAC_DMA::regs()
173 .dmaoperation_mode()
174 .modify(|_, w| w.start_stop_transmission_command().set_bit());
175
176 EMAC_DMA::regs()
177 .dmaoperation_mode()
178 .modify(|_, w| w.start_stop_rx().set_bit());
179 EMAC_MAC::regs().config().modify(|_, w| w.rx().set_bit());
180 }
181
182 /// Stops both DMA engines.
183 #[expect(dead_code)]
184 pub fn dma_stop(&self) {
185 EMAC_DMA::regs().dmaoperation_mode().modify(|_, w| {
186 w.start_stop_rx().clear_bit();
187 w.start_stop_transmission_command().clear_bit()
188 });
189 }
190
191 /// Programs the TX and RX descriptor list base addresses.
192 pub fn set_descriptor_lists(&self, tx_base: u32, rx_base: u32) {
193 unsafe {
194 EMAC_DMA::regs().dmatxbaseaddr().write(|w| w.bits(tx_base));
195 EMAC_DMA::regs().dmarxbaseaddr().write(|w| w.bits(rx_base));
196 }
197 }
198
199 /// Issues a TX poll demand to resume a suspended TX engine.
200 pub fn demand_tx_poll(&self) {
201 // Write any value to demand a TX poll; PAC doesn't expose Writable for
202 // this register so we use a direct raw write.
203 unsafe {
204 core::ptr::write_volatile(EMAC_DMA::regs().dmatxpolldemand().as_ptr(), 0);
205 }
206 }
207
208 /// Issues an RX poll demand to resume a suspended RX engine.
209 pub fn demand_rx_poll(&self) {
210 unsafe {
211 core::ptr::write_volatile(EMAC_DMA::regs().dmarxpolldemand().as_ptr(), 0);
212 }
213 }
214
215 // ── DMA interrupt control ─────────────────────────────────────────────
216
217 /// Enables the normal summary and RX interrupts, optionally TX as well
218 /// (required in async mode).
219 ///
220 /// The abnormal summary stays masked: the ISR does not handle FBE/RBU, so
221 /// a sticky error would re-enter it forever.
222 pub fn dma_enable_interrupts(&self, enable_tx: bool) {
223 EMAC_DMA::regs().dmain_en().modify(|_, w| {
224 w.dmain_rie().set_bit();
225 w.dmain_nise().set_bit();
226 w.dmain_tie().bit(enable_tx)
227 });
228 }
229
230 /// Masks every MAC-level interrupt source (PMT, LPI, timestamp, and
231 /// RGMII/PCS on chips that have them).
232 ///
233 /// These share the peripheral interrupt line with the DMA but are not
234 /// gated by `dmain_en`, and the ISR only acknowledges `dmastatus`. Leaving
235 /// them unmasked lets a single PMT/LPI event re-enter the ISR forever.
236 pub fn mask_mac_interrupts(&self) {
237 unsafe {
238 EMAC_MAC::regs().intmask().write(|w| w.bits(u32::MAX));
239 }
240 }
241
242 /// Disables all DMA interrupt sources.
243 pub fn dma_disable_interrupts(&self) {
244 unsafe {
245 EMAC_DMA::regs().dmain_en().write(|w| w.bits(0));
246 }
247 }
248
249 /// Reads and clears all pending DMA interrupt status bits.
250 #[expect(dead_code)]
251 pub fn dma_clear_interrupts(&self) -> u32 {
252 let status = EMAC_DMA::regs().dmastatus().read().bits();
253
254 EMAC_DMA::regs()
255 .dmastatus()
256 .write(|w| unsafe { w.bits(status) });
257
258 status
259 }
260
261 // ── MAC configuration ─────────────────────────────────────────────────
262
263 /// Configures the MAC for the given speed/duplex.
264 ///
265 /// TX/RX are left disabled; [`Self::dma_start`] enables them in the order
266 /// the hardware requires.
267 pub fn mac_init(&self, speed: Speed, duplex: Duplex) {
268 EMAC_MAC::regs().config().modify(|_, w| {
269 match speed {
270 Speed::_10M => {
271 w.mii().set_bit();
272 w.fespeed().clear_bit();
273 }
274 Speed::_100M => {
275 w.mii().set_bit();
276 w.fespeed().set_bit();
277 }
278 Speed::_1000M => {
279 w.mii().clear_bit();
280 }
281 }
282 w.duplex().bit(duplex == Duplex::Full);
283 w.padcrcstrip().clear_bit();
284 w.rxipcoffload().bit(cfg!(ethernet_rx_checksum_offload));
285 w.retry().set_bit();
286 w.watchdog().set_bit();
287 w.rxown().set_bit();
288 w.loopback().clear_bit();
289 w.deferralcheck().clear_bit();
290 w.rx().clear_bit();
291 w.tx().clear_bit()
292 });
293
294 // Enable pass-all-multicast mode.
295 EMAC_MAC::regs().ff().modify(|_, w| w.pam().set_bit());
296 }
297
298 /// Configures the MAC for the given speed.
299 ///
300 /// 10/100 Mbps sets `mii` (port select) and `fespeed`. 1000 Mbps clears `mii`.
301 pub fn set_speed(&self, speed: Speed) {
302 EMAC_MAC::regs().config().modify(|_, w| {
303 match speed {
304 Speed::_10M => {
305 w.mii().set_bit();
306 w.fespeed().clear_bit();
307 }
308 Speed::_100M => {
309 w.mii().set_bit();
310 w.fespeed().set_bit();
311 }
312 Speed::_1000M => {
313 w.mii().clear_bit();
314 }
315 }
316 w
317 });
318 }
319
320 /// Configures the MAC for the given duplex mode.
321 pub fn set_duplex(&self, duplex: Duplex) {
322 EMAC_MAC::regs()
323 .config()
324 .modify(|_, w| w.duplex().bit(duplex == Duplex::Full));
325 }
326
327 // ── MAC address ───────────────────────────────────────────────────────
328
329 /// Programs the unicast MAC address (filter slot 0).
330 pub fn set_mac_address(&self, addr: &[u8; 6]) {
331 let hi = (addr[5] as u32) << 8 | (addr[4] as u32);
332 let lo = (addr[3] as u32) << 24
333 | (addr[2] as u32) << 16
334 | (addr[1] as u32) << 8
335 | (addr[0] as u32);
336
337 EMAC_MAC::regs().addr0high().write(|w| unsafe {
338 w.address0_hi().bits(hi as u16);
339 w.address_enable0().set_bit()
340 });
341 EMAC_MAC::regs().addr0low().write(|w| unsafe { w.bits(lo) });
342 }
343
344 // ── MDIO ─────────────────────────────────────────────────────────────
345
346 /// Reads one PHY register via the MDIO interface (Clause 22).
347 pub fn mdio_read(&self, phy_addr: u8, reg: u8) -> u16 {
348 EMAC_MAC::regs().gmiiaddr().write(|w| unsafe {
349 w.miidev().bits(phy_addr);
350 w.miireg().bits(reg);
351 w.miicsrclk().bits(mdc_csr_clock_range());
352 w.miiwrite().clear_bit();
353 w.miibusy().set_bit()
354 });
355
356 self.mdio_wait();
357 EMAC_MAC::regs().miidata().read().mii_data().bits()
358 }
359
360 /// Writes one PHY register via the MDIO interface (Clause 22).
361 pub fn mdio_write(&self, phy_addr: u8, reg: u8, data: u16) {
362 EMAC_MAC::regs()
363 .miidata()
364 .write(|w| unsafe { w.mii_data().bits(data) });
365 EMAC_MAC::regs().gmiiaddr().write(|w| unsafe {
366 w.miidev().bits(phy_addr);
367 w.miireg().bits(reg);
368 w.miicsrclk().bits(mdc_csr_clock_range());
369 w.miiwrite().set_bit();
370 w.miibusy().set_bit()
371 });
372
373 self.mdio_wait();
374 }
375
376 fn mdio_wait(&self) {
377 while EMAC_MAC::regs().gmiiaddr().read().miibusy().bit_is_set() {}
378 }
379}