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}