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/// Returns the `miicsrclk` value for the MDIO management clock divider.
10///
11/// On ESP32 the MDC CSR clock source is the APB clock (fixed at 80 MHz), so
12/// value 3 (35–60 MHz range → /26) has always been used and works in practice.
13///
14/// On ESP32-P4 the CSR clock source is the SYS (CPU) clock, which varies with
15/// the selected [`crate::clock::CpuClock`] preset (100–400 MHz). The divider
16/// is computed at runtime to keep MDC within the 1–2.5 MHz range required by
17/// IEEE 802.3 clause 22. The 4-bit `miicsrclk` field on P4 supports extended
18/// divider values (up to 6 = 300–500 MHz → /204).
19fn mdc_csr_clock_range() -> u8 {
20    #[cfg(esp32p4)]
21    {
22        // Matches emac_hal_set_csr_clock_range() in esp-idf (emac_hal.c).
23        // The P4 EMAC (DWC_gmac) only defines encoding values 0–5; value 6+
24        // is reserved and must not be used. The CSR clock source is the SYS
25        // (CPU) clock.
26        //
27        // emac_crs_div_table = {42, 62, 16, 26, 102, 124}
28        //   encoding 0 → /42  (60–100 MHz)
29        //   encoding 1 → /62  (100–150 MHz)
30        //   encoding 2 → /16  (20–35 MHz)
31        //   encoding 3 → /26  (35–60 MHz)
32        //   encoding 4 → /102 (150–250 MHz)
33        //   encoding 5 → /124 (≥ 250 MHz, slightly over 2.5 MHz spec at high SYS clocks)
34        match crate::clock::ll::sys_clk_frequency() {
35            hz if hz >= 250_000_000 => 5, // /124
36            hz if hz >= 150_000_000 => 4, // /102
37            hz if hz >= 100_000_000 => 1, // /62
38            hz if hz >= 60_000_000 => 0,  // /42
39            hz if hz >= 35_000_000 => 3,  // /26
40            _ => 2,                       // /16
41        }
42    }
43    #[cfg(not(esp32p4))]
44    {
45        3 // ESP32: 80 MHz APB → /26, works in practice
46    }
47}
48
49/// Link speed.
50#[derive(Clone, Copy, Debug, Eq, PartialEq)]
51#[cfg_attr(feature = "defmt", derive(defmt::Format))]
52#[non_exhaustive]
53pub enum Speed {
54    /// 10 Mbit/s
55    _10M,
56    /// 100 Mbit/s
57    _100M,
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        // Enhanced 32-byte descriptors, fixed burst, address-aligned beats, PBL=8.
103        EMAC_DMA::regs().dmabusmode().modify(|_, w| unsafe {
104            w.alt_desc_size().set_bit();
105            w.fixed_burst().set_bit();
106            w.dmaaddralibea().set_bit();
107            w.use_sep_pbl().set_bit();
108            w.prog_burst_len().bits(8);
109            w.rx_dma_pbl().bits(8);
110            w
111        });
112    }
113
114    // ── DMA operation ─────────────────────────────────────────────────────
115
116    /// Starts both DMA engines.
117    ///
118    /// P4's RX FIFO is too small (~256 B) to store full frames, so RSF must
119    /// not be set — it silently drops frames larger than the FIFO. Use
120    /// cut-through receive instead.
121    pub fn dma_start(&self) {
122        EMAC_DMA::regs().dmaoperation_mode().modify(|_, w| {
123            w.tx_str_fwd().set_bit();
124            cfg_select! {
125                esp32p4 => {
126                    w.fwd_under_gf().set_bit();
127                }
128                _ => {
129                    w.rx_store_forward().set_bit();
130                }
131            }
132            w.start_stop_rx().set_bit();
133            w.start_stop_transmission_command().set_bit()
134        });
135    }
136
137    /// Stops both DMA engines.
138    #[expect(dead_code)]
139    pub fn dma_stop(&self) {
140        EMAC_DMA::regs().dmaoperation_mode().modify(|_, w| {
141            w.start_stop_rx().clear_bit();
142            w.start_stop_transmission_command().clear_bit()
143        });
144    }
145
146    /// Programs the TX and RX descriptor list base addresses.
147    pub fn set_descriptor_lists(&self, tx_base: u32, rx_base: u32) {
148        unsafe {
149            EMAC_DMA::regs().dmatxbaseaddr().write(|w| w.bits(tx_base));
150            EMAC_DMA::regs().dmarxbaseaddr().write(|w| w.bits(rx_base));
151        }
152    }
153
154    /// Issues a TX poll demand to resume a suspended TX engine.
155    pub fn demand_tx_poll(&self) {
156        // Write any value to demand a TX poll; PAC doesn't expose Writable for
157        // this register so we use a direct raw write.
158        unsafe {
159            core::ptr::write_volatile(EMAC_DMA::regs().dmatxpolldemand().as_ptr(), 0);
160        }
161    }
162
163    /// Issues an RX poll demand to resume a suspended RX engine.
164    pub fn demand_rx_poll(&self) {
165        unsafe {
166            core::ptr::write_volatile(EMAC_DMA::regs().dmarxpolldemand().as_ptr(), 0);
167        }
168    }
169
170    // ── DMA interrupt control ─────────────────────────────────────────────
171
172    /// Enables Normal/Abnormal summary interrupts and the RX interrupt.
173    /// Optionally enables the TX interrupt (required in async mode).
174    pub fn dma_enable_interrupts(&self, enable_tx: bool) {
175        EMAC_DMA::regs().dmain_en().modify(|_, w| {
176            w.dmain_rie().set_bit();
177            w.dmain_aise().set_bit();
178            w.dmain_nise().set_bit();
179            w.dmain_tie().bit(enable_tx)
180        });
181    }
182
183    /// Disables all DMA interrupt sources.
184    pub fn dma_disable_interrupts(&self) {
185        unsafe {
186            EMAC_DMA::regs().dmain_en().write(|w| w.bits(0));
187        }
188    }
189
190    /// Reads and clears all pending DMA interrupt status bits.
191    #[expect(dead_code)]
192    pub fn dma_clear_interrupts(&self) -> u32 {
193        let status = EMAC_DMA::regs().dmastatus().read().bits();
194
195        EMAC_DMA::regs()
196            .dmastatus()
197            .write(|w| unsafe { w.bits(status) });
198
199        status
200    }
201
202    // ── MAC configuration ─────────────────────────────────────────────────
203
204    /// Configures the MAC for the given speed/duplex and enables TX/RX.
205    pub fn mac_init(&self, speed: Speed, duplex: Duplex) {
206        EMAC_MAC::regs().emacconfig().modify(|_, w| {
207            w.mii().set_bit();
208            w.fespeed().bit(speed == Speed::_100M);
209            w.duplex().bit(duplex == Duplex::Full);
210            w.padcrcstrip().clear_bit();
211            w.rxipcoffload().set_bit();
212            w.retry().set_bit();
213            w.watchdog().set_bit();
214            w.rxown().set_bit();
215            w.loopback().clear_bit();
216            w.deferralcheck().clear_bit();
217            w.rx().set_bit();
218            w.tx().set_bit()
219        });
220
221        // Enable pass-all-multicast mode.
222        EMAC_MAC::regs().emacff().modify(|_, w| w.pam().set_bit());
223    }
224
225    /// Configures the MAC for the given speed.
226    pub fn set_speed(&self, speed: Speed) {
227        EMAC_MAC::regs()
228            .emacconfig()
229            .modify(|_, w| w.fespeed().bit(speed == Speed::_100M));
230    }
231
232    /// Configures the MAC for the given duplex mode.
233    pub fn set_duplex(&self, duplex: Duplex) {
234        EMAC_MAC::regs()
235            .emacconfig()
236            .modify(|_, w| w.duplex().bit(duplex == Duplex::Full));
237    }
238
239    // ── MAC address ───────────────────────────────────────────────────────
240
241    /// Programs the unicast MAC address (filter slot 0).
242    pub fn set_mac_address(&self, addr: &[u8; 6]) {
243        let hi = (addr[5] as u32) << 8 | (addr[4] as u32);
244        let lo = (addr[3] as u32) << 24
245            | (addr[2] as u32) << 16
246            | (addr[1] as u32) << 8
247            | (addr[0] as u32);
248
249        EMAC_MAC::regs().emacaddr0high().write(|w| unsafe {
250            w.address0_hi().bits(hi as u16);
251            w.address_enable0().set_bit()
252        });
253        EMAC_MAC::regs()
254            .emacaddr0low()
255            .write(|w| unsafe { w.bits(lo) });
256    }
257
258    // ── MDIO ─────────────────────────────────────────────────────────────
259
260    /// Reads one PHY register via the MDIO interface (Clause 22).
261    pub fn mdio_read(&self, phy_addr: u8, reg: u8) -> u16 {
262        EMAC_MAC::regs().emacgmiiaddr().write(|w| unsafe {
263            w.miidev().bits(phy_addr);
264            w.miireg().bits(reg);
265            w.miicsrclk().bits(mdc_csr_clock_range());
266            w.miiwrite().clear_bit();
267            w.miibusy().set_bit()
268        });
269
270        self.mdio_wait();
271        EMAC_MAC::regs().emacmiidata().read().mii_data().bits()
272    }
273
274    /// Writes one PHY register via the MDIO interface (Clause 22).
275    pub fn mdio_write(&self, phy_addr: u8, reg: u8, data: u16) {
276        EMAC_MAC::regs()
277            .emacmiidata()
278            .write(|w| unsafe { w.mii_data().bits(data) });
279        EMAC_MAC::regs().emacgmiiaddr().write(|w| unsafe {
280            w.miidev().bits(phy_addr);
281            w.miireg().bits(reg);
282            w.miicsrclk().bits(mdc_csr_clock_range());
283            w.miiwrite().set_bit();
284            w.miibusy().set_bit()
285        });
286
287        self.mdio_wait();
288    }
289
290    fn mdio_wait(&self) {
291        while EMAC_MAC::regs()
292            .emacgmiiaddr()
293            .read()
294            .miibusy()
295            .bit_is_set()
296        {}
297    }
298}