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}