Skip to main content

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}