esp_emac/config.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//! EMAC configuration types.
5
6/// EMAC configuration.
7#[derive(Debug, Clone)]
8pub struct EmacConfig {
9 /// RMII clock source.
10 pub clock: RmiiClockConfig,
11 /// RMII management pins (MDC/MDIO).
12 pub pins: RmiiPins,
13}
14
15/// RMII reference clock configuration.
16///
17/// The 50 MHz RMII clock can be generated internally (ESP32 APLL) or
18/// supplied externally from a PHY-driven oscillator. Mode selection is
19/// hardware-specific and `Emac::init` rejects mismatched GPIO choices
20/// with [`crate::EmacError::InvalidConfig`] — see each variant's docs
21/// for which pads are valid.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23#[cfg_attr(feature = "defmt", derive(defmt::Format))]
24pub enum RmiiClockConfig {
25 /// ESP32 APLL generates 50 MHz and drives it out of the chip.
26 ///
27 /// Valid GPIO choices: [`ClkGpio::Gpio16`] (`EMAC_CLK_OUT`, 0°) or
28 /// [`ClkGpio::Gpio17`] (`EMAC_CLK_OUT_180`, 180° — the LAN8720A
29 /// reference design preference). [`ClkGpio::Gpio0`] is **invalid**
30 /// for this mode: GPIO0 function 5 is `EMAC_TX_CLK`, an input pad.
31 ///
32 /// `xtal` selects the SDM coefficients for APLL programming and
33 /// MUST match the actual on-board crystal — there is no detection
34 /// at runtime, getting it wrong silently produces a wrong-frequency
35 /// REF_CLK and the link will not come up.
36 ///
37 /// Coexistence note: ESP32 errata CLK-3.22 — the APLL clock signal
38 /// emitted on the GPIO pad is corrupted by on-chip RF noise during
39 /// WiFi/BT transmission. This mode is unsafe with active radio;
40 /// boards needing Ethernet + WiFi should use [`Self::External`].
41 InternalApll {
42 /// GPIO for clock output. Must be `Gpio16` or `Gpio17`.
43 gpio: ClkGpio,
44 /// On-board crystal frequency. APLL SDM coefficients are
45 /// chosen accordingly to land on a 50 MHz RMII reference clock.
46 xtal: XtalFreq,
47 },
48 /// External 50 MHz clock fed in from a PHY crystal or oscillator.
49 ///
50 /// Valid GPIO choice: [`ClkGpio::Gpio0`] only — that is the only
51 /// pad whose function 5 (`EMAC_TX_CLK`) is an input. `Gpio16` /
52 /// `Gpio17` are **invalid** here.
53 ///
54 /// Required for Ethernet + WiFi coexistence (immune to the
55 /// CLK-3.22 errata since the clock never leaves the PHY domain).
56 External {
57 /// GPIO for clock input. Must be `Gpio0`.
58 gpio: ClkGpio,
59 },
60}
61
62/// On-board crystal frequency in MHz, used to pick APLL SDM coefficients
63/// for a 50 MHz RMII reference-clock output.
64///
65/// ESP32 SoC accepts crystals at 26, 32, or 40 MHz. The vast majority
66/// of modules ship with 40 MHz (`ESP32-WROOM-32`, `ESP32-WROVER`,
67/// `ESP32-MINI-1` and most reference designs). 26 MHz appears in older
68/// QFN-only designs; 32 MHz is rare custom-design territory. The crate
69/// only provides coefficients for these three values — for any other
70/// crystal you will need to extend [`crate::clock::ApllCoefficients`]
71/// and submit a patch.
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73#[cfg_attr(feature = "defmt", derive(defmt::Format))]
74pub enum XtalFreq {
75 /// 26 MHz — legacy ESP32 modules, certain QFN bare-chip designs.
76 Mhz26,
77 /// 32 MHz — rare custom designs.
78 Mhz32,
79 /// 40 MHz — ESP32-WROOM, ESP32-WROVER, ESP32-MINI, most modern boards.
80 Mhz40,
81}
82
83impl XtalFreq {
84 /// Frequency in MHz as a `u32`. Useful for logging / diagnostics.
85 pub const fn mhz(self) -> u32 {
86 match self {
87 Self::Mhz26 => 26,
88 Self::Mhz32 => 32,
89 Self::Mhz40 => 40,
90 }
91 }
92}
93
94/// GPIO pins that can carry the EMAC RMII reference clock on ESP32.
95///
96/// Direction is fixed by the IO_MUX function 5 wiring:
97///
98/// - [`Self::Gpio0`] — `EMAC_TX_CLK`, input only — pair with
99/// [`RmiiClockConfig::External`].
100/// - [`Self::Gpio16`] — `EMAC_CLK_OUT` (0°), output only — pair with
101/// [`RmiiClockConfig::InternalApll`].
102/// - [`Self::Gpio17`] — `EMAC_CLK_OUT_180` (180°), output only — pair
103/// with [`RmiiClockConfig::InternalApll`]. Most common on LAN8720A.
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105#[cfg_attr(feature = "defmt", derive(defmt::Format))]
106pub enum ClkGpio {
107 /// GPIO0 — `EMAC_TX_CLK` input. Also a boot-strapping pin, take
108 /// care that the external oscillator does not violate boot timing.
109 Gpio0,
110 /// GPIO16 — `EMAC_CLK_OUT` (0° phase).
111 Gpio16,
112 /// GPIO17 — `EMAC_CLK_OUT_180` (180° phase, most common for LAN8720A).
113 Gpio17,
114}
115
116impl ClkGpio {
117 /// Get the GPIO number.
118 pub const fn gpio_num(self) -> u8 {
119 match self {
120 ClkGpio::Gpio0 => 0,
121 ClkGpio::Gpio16 => 16,
122 ClkGpio::Gpio17 => 17,
123 }
124 }
125}
126
127/// RMII management pin configuration.
128///
129/// Data pins (TXD0/1, RXD0/1, TX_EN, CRS_DV) are fixed on ESP32 via IO_MUX
130/// and cannot be remapped. Only MDC/MDIO are routed via GPIO Matrix.
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132#[cfg_attr(feature = "defmt", derive(defmt::Format))]
133pub struct RmiiPins {
134 /// MDC (Management Data Clock) — routed via GPIO Matrix, any GPIO.
135 pub mdc: u8,
136 /// MDIO (Management Data I/O) — routed via GPIO Matrix, any GPIO.
137 pub mdio: u8,
138}
139
140impl Default for RmiiPins {
141 /// Default: MDC=GPIO23, MDIO=GPIO18 (most common ESP32 Ethernet boards).
142 fn default() -> Self {
143 Self { mdc: 23, mdio: 18 }
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn clk_gpio_num() {
153 assert_eq!(ClkGpio::Gpio0.gpio_num(), 0);
154 assert_eq!(ClkGpio::Gpio16.gpio_num(), 16);
155 assert_eq!(ClkGpio::Gpio17.gpio_num(), 17);
156 }
157
158 #[test]
159 fn rmii_pins_default() {
160 let pins = RmiiPins::default();
161 assert_eq!(pins.mdc, 23);
162 assert_eq!(pins.mdio, 18);
163 }
164
165 #[test]
166 fn rmii_clock_config_equality() {
167 let a = RmiiClockConfig::InternalApll {
168 gpio: ClkGpio::Gpio17,
169 xtal: XtalFreq::Mhz40,
170 };
171 let b = RmiiClockConfig::InternalApll {
172 gpio: ClkGpio::Gpio17,
173 xtal: XtalFreq::Mhz40,
174 };
175 let c = RmiiClockConfig::External {
176 gpio: ClkGpio::Gpio0,
177 };
178 let d = RmiiClockConfig::InternalApll {
179 gpio: ClkGpio::Gpio17,
180 xtal: XtalFreq::Mhz26,
181 };
182 assert_eq!(a, b);
183 assert_ne!(a, c);
184 // Different XTAL — different config.
185 assert_ne!(a, d);
186 }
187
188 #[test]
189 fn xtal_freq_mhz_values() {
190 assert_eq!(XtalFreq::Mhz26.mhz(), 26);
191 assert_eq!(XtalFreq::Mhz32.mhz(), 32);
192 assert_eq!(XtalFreq::Mhz40.mhz(), 40);
193 }
194
195 #[test]
196 fn emac_config_clone() {
197 let config = EmacConfig {
198 clock: RmiiClockConfig::InternalApll {
199 gpio: ClkGpio::Gpio17,
200 xtal: XtalFreq::Mhz40,
201 },
202 pins: RmiiPins::default(),
203 };
204 let cloned = config.clone();
205 assert_eq!(cloned.pins.mdc, 23);
206 }
207}