Skip to main content

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}