Skip to main content

esp_hal/ethernet/clock/
esp32p4.rs

1//! EMAC clock configuration for ESP32-P4.
2//!
3//! # RMII reference clock sources
4//!
5//! The ESP32-P4 EMAC needs a 50 MHz reference clock for RMII.
6//!
7//! - **[`ExternalRefClock`]** — the PHY drives the reference clock into one of the `EMAC_RMII_CLK`
8//!   input pads (GPIO32, GPIO44, or GPIO50). This is the recommended configuration when the PHY has
9//!   an integrated oscillator.
10//!
11//! - **[`InternalRefClock`]** — the ESP32-P4 MPLL generates a 50 MHz clock and drives it out on a
12//!   `REF_50M_CLK` pad (GPIO23 or GPIO39). This signal must be looped back externally into one of
13//!   the `EMAC_RMII_CLK` input pads.
14//!
15//! # MII clock source
16//!
17//! The PHY drives `TX_CLK` and `RX_CLK` separately at 25 MHz (100 Mbps) or
18//! 2.5 MHz (10 Mbps). No internal clock generation is required.
19
20use esp_rom_sys::rom::ets_delay_us;
21
22use crate::{
23    ethernet::{RmiiClkIn, RmiiClkOut, RmiiClockConfig},
24    peripherals::{HP_SYS, HP_SYS_CLKRST, LP_AON_CLKRST},
25    private::Sealed,
26};
27
28/// Number of spin-loop iterations to wait after enabling the EMAC clock tree.
29const CLOCK_STABILIZE_US: u32 = 300;
30
31// ── ExternalRefClock ─────────────────────────────────────────────────────────
32
33/// RMII reference clock provided externally by the PHY.
34///
35/// The PHY must drive a 50 MHz clock into one of the `EMAC_RMII_CLK` input
36/// pads: GPIO32, GPIO44, or GPIO50.
37pub struct ExternalRefClock<P>(P);
38
39impl<P> ExternalRefClock<P> {
40    /// Wraps the GPIO pin that receives the PHY reference clock.
41    pub fn new(pin: P) -> Self {
42        Self(pin)
43    }
44}
45
46impl<P> Sealed for ExternalRefClock<P> {}
47
48impl<P: RmiiClkIn> RmiiClockConfig for ExternalRefClock<P> {
49    fn configure(self) {
50        self.0.configure_iomux();
51        configure_rmii_input();
52    }
53}
54
55// ── InternalRefClock ─────────────────────────────────────────────────────────
56
57/// RMII reference clock derived from the ESP32-P4 MPLL at 50 MHz.
58///
59/// The MPLL output is driven out on a `REF_50M_CLK` output pad (GPIO23 or
60/// GPIO39) and must be looped back externally into one of the `EMAC_RMII_CLK`
61/// input pads (GPIO32, GPIO44, or GPIO50).
62pub struct InternalRefClock<POut, PIn> {
63    ref_clk_out: POut,
64    ref_clk_in: PIn,
65}
66
67impl<POut, PIn> InternalRefClock<POut, PIn> {
68    /// Creates an internal MPLL clock configuration.
69    ///
70    /// - `ref_clk_out` — the pad that will output the 50 MHz `REF_50M_CLK` signal (GPIO23 or
71    ///   GPIO39).
72    /// - `ref_clk_in` — the pad that receives the looped-back signal (GPIO32, GPIO44, or GPIO50).
73    pub fn new(ref_clk_out: POut, ref_clk_in: PIn) -> Self {
74        Self {
75            ref_clk_out,
76            ref_clk_in,
77        }
78    }
79}
80
81impl<POut, PIn> Sealed for InternalRefClock<POut, PIn> {}
82
83impl<POut: RmiiClkOut, PIn: RmiiClkIn> RmiiClockConfig for InternalRefClock<POut, PIn> {
84    fn configure(self) {
85        self.ref_clk_out.configure_iomux();
86        self.ref_clk_in.configure_iomux();
87        enable_mpll_50m_output();
88        configure_rmii_input();
89        // Override pad_emac_ref_clk_en to enable the clock output pad
90        // (configure_rmii_input clears it; the MPLL topology needs it set).
91        HP_SYS_CLKRST::regs()
92            .peri_clk_ctrl00()
93            .modify(|_, w| w.pad_emac_ref_clk_en().set_bit());
94    }
95}
96
97// ── MiiClock ─────────────────────────────────────────────────────────────────
98
99/// MII clock configuration.
100///
101/// In MII mode the PHY drives `TX_CLK` and `RX_CLK` separately
102/// (25 MHz at 100 Mbps, 2.5 MHz at 10 Mbps). No internal clock generation is
103/// required.
104pub(crate) struct MiiClock;
105
106impl MiiClock {
107    pub(super) fn configure(&self) {
108        configure_mii();
109    }
110}
111
112// ── Private helpers ───────────────────────────────────────────────────────────
113
114/// Configures the EMAC clock tree for RMII with an external clock input.
115///
116/// Matches `emac_ll_clock_enable_rmii_input()` in esp-idf.
117fn configure_rmii_input() {
118    HP_SYS::regs()
119        .gmac_ctrl0()
120        .modify(|_, w| unsafe { w.phy_intf_sel().bits(4) }); // 4 = RMII
121
122    HP_SYS_CLKRST::regs()
123        .peri_clk_ctrl00()
124        .modify(|_, w| unsafe {
125            // pad_emac_ref_clk_en stays clear for external-input topology;
126            // for MPLL output it is set afterwards by the caller.
127            w.pad_emac_ref_clk_en().clear_bit();
128            // Source: 0 = pad_emac_txrx_clk (the combined RMII ref pad).
129            w.emac_rmii_clk_src_sel().bits(0);
130            w.emac_rmii_clk_en().set_bit();
131            w.emac_rx_clk_src_sel().clear_bit(); // 0 = pad_emac_txrx_clk
132            w.emac_rx_clk_en().set_bit()
133        });
134
135    HP_SYS_CLKRST::regs()
136        .peri_clk_ctrl01()
137        .modify(|_, w| unsafe {
138            w.emac_tx_clk_src_sel().clear_bit(); // 0 = pad_emac_txrx_clk
139            w.emac_tx_clk_en().set_bit();
140            w.emac_rx_clk_div_num().bits(1); // div 1 = 100 Mbps default
141            w.emac_tx_clk_div_num().bits(1)
142        });
143
144    // LP pad gate: use the combined txrx clock pad, not the separate tx/rx pads.
145    LP_AON_CLKRST::regs()
146        .lp_aonclkrst_hp_clk_ctrl()
147        .modify(|_, w| {
148            w.lp_aonclkrst_hp_pad_emac_tx_clk_en().clear_bit();
149            w.lp_aonclkrst_hp_pad_emac_rx_clk_en().clear_bit();
150            w.lp_aonclkrst_hp_pad_emac_txrx_clk_en().set_bit()
151        });
152
153    deassert_reset();
154    clock_stabilize();
155}
156
157/// Enables the MPLL 500 MHz source and derives the 50 MHz `REF_50M_CLK` output.
158///
159/// Divider: 500 MHz / (9 + 1) = 50 MHz.
160///
161/// Must be called before `configure_rmii_input()` for the MPLL topology, since
162/// `configure_rmii_input()` clears `pad_emac_ref_clk_en` and the caller must
163/// restore it afterwards.
164fn enable_mpll_50m_output() {
165    LP_AON_CLKRST::regs()
166        .lp_aonclkrst_hp_clk_ctrl()
167        .modify(|_, w| w.lp_aonclkrst_hp_mpll_500m_clk_en().set_bit());
168
169    HP_SYS_CLKRST::regs()
170        .ref_clk_ctrl0()
171        .modify(|_, w| unsafe { w.ref_50m_clk_div_num().bits(9) });
172
173    HP_SYS_CLKRST::regs()
174        .ref_clk_ctrl1()
175        .modify(|_, w| w.ref_50m_clk_en().set_bit());
176}
177
178/// Configures the EMAC clock tree for MII mode.
179///
180/// Matches `emac_ll_clock_enable_mii()` in esp-idf. In MII mode the RX and TX
181/// clocks come from separate pads (not the combined RMII pad), so
182/// `emac_rx_clk_src_sel = 1` and `emac_tx_clk_src_sel = 1`.
183fn configure_mii() {
184    HP_SYS::regs()
185        .gmac_ctrl0()
186        .modify(|_, w| unsafe { w.phy_intf_sel().bits(0) }); // 0 = MII
187
188    HP_SYS_CLKRST::regs()
189        .peri_clk_ctrl00()
190        .modify(|_, w| unsafe {
191            w.pad_emac_ref_clk_en().clear_bit();
192            w.emac_rmii_clk_en().clear_bit();
193            w.emac_rmii_clk_src_sel().bits(0);
194            w.emac_rx_clk_src_sel().set_bit(); // 1 = pad_emac_rx_clk
195            w.emac_rx_clk_en().set_bit()
196        });
197
198    HP_SYS_CLKRST::regs()
199        .peri_clk_ctrl01()
200        .modify(|_, w| unsafe {
201            w.emac_tx_clk_src_sel().set_bit(); // 1 = pad_emac_tx_clk
202            w.emac_tx_clk_en().set_bit();
203            w.emac_rx_clk_div_num().bits(0); // div 0 = 25 MHz (no division)
204            w.emac_tx_clk_div_num().bits(0)
205        });
206
207    LP_AON_CLKRST::regs()
208        .lp_aonclkrst_hp_clk_ctrl()
209        .modify(|_, w| {
210            w.lp_aonclkrst_hp_pad_emac_tx_clk_en().set_bit();
211            w.lp_aonclkrst_hp_pad_emac_rx_clk_en().set_bit()
212        });
213
214    deassert_reset();
215    clock_stabilize();
216}
217
218/// Pulses the EMAC peripheral reset via `LP_AON_CLKRST` (assert then deassert).
219///
220/// Matches `emac_ll_reset_register()` in esp-idf. The `GenericPeripheralGuard`
221/// for EMAC on P4 does not perform a hardware reset (the reset handler is a
222/// no-op), so this must be called explicitly during clock configuration.
223fn deassert_reset() {
224    LP_AON_CLKRST::regs()
225        .lp_aonclkrst_hp_sdmmc_emac_rst_ctrl()
226        .modify(|_, w| {
227            w.lp_aonclkrst_rst_en_emac().set_bit();
228            w.lp_aonclkrst_force_norst_emac().clear_bit()
229        });
230    LP_AON_CLKRST::regs()
231        .lp_aonclkrst_hp_sdmmc_emac_rst_ctrl()
232        .modify(|_, w| w.lp_aonclkrst_rst_en_emac().clear_bit());
233}
234
235/// Spin-waits for the EMAC clock tree to stabilise after configuration.
236fn clock_stabilize() {
237    ets_delay_us(CLOCK_STABILIZE_US);
238}