Skip to main content

esp_p4_eth/
clock.rs

1//! EMAC clock-tree configuration for ESP32-P4.
2//!
3//! The P4 MAC is not usable until both the HP/LP clock gates and the RMII clock
4//! mux are configured.
5//!
6//! Two board topologies are supported:
7//!
8//! - `configure_clock_ext_in()`: the PHY or an external oscillator drives the
9//!   50 MHz `REF_CLK` into one of the RMII clock input pads.
10//! - `configure_clock_mpll_out()`: P4 derives a 50 MHz `REF_CLK` from MPLL,
11//!   drives it out on `GPIO23` or `GPIO39`, and expects that signal to be
12//!   looped back externally into one of the RMII clock input pads. This follows
13//!   the ESP-IDF guidance for `EMAC_CLK_OUT` on ESP32-P4.
14
15use core::hint::spin_loop;
16
17use crate::{regs, Speed};
18
19const DR_REG_HP_SYS_CLKRST_BASE: usize = 0x500E_6000;
20const HP_SYS_CLKRST_SOC_CLK_CTRL1_REG: usize = DR_REG_HP_SYS_CLKRST_BASE + 0x18;
21const HP_SYS_CLKRST_REF_CLK_CTRL0_REG: usize = DR_REG_HP_SYS_CLKRST_BASE + 0x24;
22const HP_SYS_CLKRST_REF_CLK_CTRL1_REG: usize = DR_REG_HP_SYS_CLKRST_BASE + 0x28;
23const HP_SYS_CLKRST_PERI_CLK_CTRL00_REG: usize = DR_REG_HP_SYS_CLKRST_BASE + 0x30;
24const HP_SYS_CLKRST_PERI_CLK_CTRL01_REG: usize = DR_REG_HP_SYS_CLKRST_BASE + 0x34;
25
26const DR_REG_LP_CLKRST_BASE: usize = 0x5011_1000;
27const LP_CLKRST_HP_CLK_CTRL_REG: usize = DR_REG_LP_CLKRST_BASE + 0x40;
28const LP_CLKRST_HP_SDMMC_EMAC_RST_CTRL_REG: usize = DR_REG_LP_CLKRST_BASE + 0x4C;
29
30const HP_SYS_CLKRST_REG_EMAC_SYS_CLK_EN: u32 = 1 << 13;
31const HP_SYS_CLKRST_REG_REF_50M_CLK_DIV_NUM_SHIFT: u32 = 0;
32const HP_SYS_CLKRST_REG_REF_50M_CLK_DIV_NUM_MASK: u32 =
33    0xff << HP_SYS_CLKRST_REG_REF_50M_CLK_DIV_NUM_SHIFT;
34const HP_SYS_CLKRST_REG_REF_50M_CLK_EN: u32 = 1 << 27;
35const HP_SYS_CLKRST_REG_PAD_EMAC_REF_CLK_EN: u32 = 1 << 24;
36const HP_SYS_CLKRST_REG_EMAC_RMII_CLK_SRC_SEL_SHIFT: u32 = 25;
37const HP_SYS_CLKRST_REG_EMAC_RMII_CLK_SRC_SEL_MASK: u32 =
38    0x3 << HP_SYS_CLKRST_REG_EMAC_RMII_CLK_SRC_SEL_SHIFT;
39const HP_SYS_CLKRST_REG_EMAC_RMII_CLK_EN: u32 = 1 << 27;
40const HP_SYS_CLKRST_REG_EMAC_RX_CLK_SRC_SEL: u32 = 1 << 28;
41const HP_SYS_CLKRST_REG_EMAC_RX_CLK_EN: u32 = 1 << 29;
42
43const HP_SYS_CLKRST_REG_EMAC_RX_CLK_DIV_NUM_SHIFT: u32 = 0;
44const HP_SYS_CLKRST_REG_EMAC_RX_CLK_DIV_NUM_MASK: u32 =
45    0xff << HP_SYS_CLKRST_REG_EMAC_RX_CLK_DIV_NUM_SHIFT;
46const HP_SYS_CLKRST_REG_EMAC_TX_CLK_SRC_SEL: u32 = 1 << 8;
47const HP_SYS_CLKRST_REG_EMAC_TX_CLK_EN: u32 = 1 << 9;
48const HP_SYS_CLKRST_REG_EMAC_TX_CLK_DIV_NUM_SHIFT: u32 = 10;
49const HP_SYS_CLKRST_REG_EMAC_TX_CLK_DIV_NUM_MASK: u32 =
50    0xff << HP_SYS_CLKRST_REG_EMAC_TX_CLK_DIV_NUM_SHIFT;
51
52const LP_CLKRST_HP_PAD_EMAC_TX_CLK_EN: u32 = 1 << 13;
53const LP_CLKRST_HP_PAD_EMAC_RX_CLK_EN: u32 = 1 << 14;
54const LP_CLKRST_HP_PAD_EMAC_TXRX_CLK_EN: u32 = 1 << 15;
55const LP_CLKRST_HP_MPLL_500M_CLK_EN: u32 = 1 << 28;
56const LP_CLKRST_RST_EN_EMAC: u32 = 1 << 30;
57const LP_CLKRST_FORCE_NORST_EMAC: u32 = 1 << 31;
58
59const IO_MUX_GPIO23_REG: usize = 0x500E_1060;
60const IO_MUX_GPIO32_REG: usize = 0x500E_1084;
61const IO_MUX_GPIO39_REG: usize = 0x500E_10A0;
62const IO_MUX_GPIO44_REG: usize = 0x500E_10B4;
63const IO_MUX_GPIO50_REG: usize = 0x500E_10CC;
64
65const FUN_IE: u32 = 1 << 9;
66const FUN_PU: u32 = 1 << 8;
67const FUN_PD: u32 = 1 << 7;
68const MCU_SEL_SHIFT: u32 = 12;
69const MCU_SEL_MASK: u32 = 0x7 << MCU_SEL_SHIFT;
70const REF_50M_CLK_PAD_FUNC: u32 = 3;
71const EMAC_RMII_CLK_PAD_FUNC: u32 = 3;
72
73// After deasserting the EMAC reset and enabling the AHB / RMII / RX / TX
74// clocks, downstream MMIO accesses to the EMAC must wait for the peripheral
75// to actually leave reset and for the clock tree to stabilise. The previous
76// 1 024-cycle wait (~3 µs at 360 MHz) was empirically insufficient; bumped
77// to 100 000 (~280 µs) which lines up with the IDF baseline post-reset wait.
78// The remaining 10 % cold-boot hang race seen at higher delays was unrelated
79// — it was caused by the descriptor-ring constructors writing the DMA
80// `*_DESC_LIST` peripheral registers BEFORE `dma_reset` had run; that is now
81// fixed in `Ethernet::reset_dma`.
82const CLOCK_CONFIG_DELAY_SPINS: usize = 100_000;
83const RMII_CLK_SRC_EXTERNAL: u32 = 0;
84const REF_50M_CLK_DIV_DEFAULT: u32 = 9;
85
86/// GPIO options that can feed the RMII reference clock into the MAC.
87#[derive(Clone, Copy, Debug, Eq, PartialEq)]
88pub enum RefClockPin {
89    /// Route REF_CLK through GPIO32.
90    Gpio32,
91    /// Route REF_CLK through GPIO44.
92    Gpio44,
93    /// Route REF_CLK through GPIO50.
94    Gpio50,
95}
96
97/// GPIO options that can emit the internally generated 50 MHz REF_CLK.
98#[derive(Clone, Copy, Debug, Eq, PartialEq)]
99pub enum MpllClockOutPin {
100    /// Route internally generated REF_CLK out through GPIO23.
101    Gpio23,
102    /// Route internally generated REF_CLK out through GPIO39.
103    Gpio39,
104}
105
106/// Enables the EMAC clock tree for an externally supplied 50 MHz `REF_CLK`.
107///
108/// Use this mode when the PHY or a dedicated oscillator already drives the
109/// RMII reference clock into one of the supported input pads.
110pub fn configure_clock_ext_in(pin: RefClockPin) {
111    select_rmii_phy_interface();
112    enable_emac_clock_tree();
113    configure_ref_clock_pin(pin);
114    select_external_rmii_clock();
115    configure_speed_divider(Speed::Mbps100);
116}
117
118/// Programs `HP_SYSTEM.sys_gmac_ctrl0` for RMII operation:
119/// - `phy_intf_sel = 4` (RMII)
120/// - `gmac_rst_clk_tx_n = 1` (active-low — deassert TX clock-domain reset)
121/// - `gmac_rst_clk_rx_n = 1` (active-low — deassert RX clock-domain reset)
122///
123/// The default value of `phy_intf_sel` is 0 which selects MII. Without `tx/rx_clk_n` deasserted
124/// the EMAC TX/RX clock domains stay in reset — MDC clock never gates through, MDIO transactions
125/// formally complete (BUSY bit clears) but PHY responses arrive as all-zeros.
126///
127/// Confirmed against a working IDF v5.3 baseline dump on Waveshare ESP32-P4-ETH after `Ethernet
128/// Link Up`: `HP_SYSTEM.SYS_GMAC_CTRL0 = 0xD0` (bits 4, 6, 7 set = phy_intf_sel=4 RMII, plus
129/// both clock-domain resets deasserted).
130fn select_rmii_phy_interface() {
131    const HP_SYSTEM_SYS_GMAC_CTRL0_REG: usize = 0x500E_514C;
132    const PHY_INTF_SEL_SHIFT: u32 = 2;
133    const PHY_INTF_SEL_MASK: u32 = 0x7 << PHY_INTF_SEL_SHIFT;
134    const PHY_INTF_SEL_RMII: u32 = 4;
135    const GMAC_RST_CLK_TX_N: u32 = 1 << 6;
136    const GMAC_RST_CLK_RX_N: u32 = 1 << 7;
137
138    let value = (regs::read(HP_SYSTEM_SYS_GMAC_CTRL0_REG) & !PHY_INTF_SEL_MASK)
139        | (PHY_INTF_SEL_RMII << PHY_INTF_SEL_SHIFT)
140        | GMAC_RST_CLK_TX_N
141        | GMAC_RST_CLK_RX_N;
142    regs::write(HP_SYSTEM_SYS_GMAC_CTRL0_REG, value);
143}
144
145/// Disables the EMAC clock tree and asserts peripheral reset.
146///
147/// This is the low-level power-down primitive used by [`crate::Ethernet`] on
148/// shutdown and drop.
149pub fn disable_emac_clock_tree() {
150    clear_bits(
151        HP_SYS_CLKRST_PERI_CLK_CTRL00_REG,
152        HP_SYS_CLKRST_REG_PAD_EMAC_REF_CLK_EN
153            | HP_SYS_CLKRST_REG_EMAC_RMII_CLK_EN
154            | HP_SYS_CLKRST_REG_EMAC_RX_CLK_EN,
155    );
156    clear_bits(
157        HP_SYS_CLKRST_PERI_CLK_CTRL01_REG,
158        HP_SYS_CLKRST_REG_EMAC_TX_CLK_EN,
159    );
160    clear_bits(
161        LP_CLKRST_HP_CLK_CTRL_REG,
162        LP_CLKRST_HP_PAD_EMAC_TX_CLK_EN
163            | LP_CLKRST_HP_PAD_EMAC_RX_CLK_EN
164            | LP_CLKRST_HP_PAD_EMAC_TXRX_CLK_EN,
165    );
166    clear_bits(
167        HP_SYS_CLKRST_SOC_CLK_CTRL1_REG,
168        HP_SYS_CLKRST_REG_EMAC_SYS_CLK_EN,
169    );
170
171    let mut reset = regs::read(LP_CLKRST_HP_SDMMC_EMAC_RST_CTRL_REG);
172    reset |= LP_CLKRST_RST_EN_EMAC;
173    reset &= !LP_CLKRST_FORCE_NORST_EMAC;
174    regs::write(LP_CLKRST_HP_SDMMC_EMAC_RST_CTRL_REG, reset);
175}
176
177/// Enables the EMAC clock tree for a 50 MHz REF_CLK derived from MPLL.
178///
179/// ESP-IDF documents the ESP32-P4 `EMAC_CLK_OUT` topology as an output-only
180/// `REF_CLK` source that must be externally looped back into one of the RMII
181/// clock input pads. Because of that requirement, this helper configures both
182/// the output pad and the input pad used for the external loopback.
183pub fn configure_clock_mpll_out(output_pin: MpllClockOutPin, input_pin: RefClockPin) {
184    select_rmii_phy_interface();
185    enable_emac_clock_tree();
186    enable_ref_50m_clock();
187    configure_ref_50m_output_pin(output_pin);
188    configure_ref_clock_pin(input_pin);
189    select_external_rmii_clock();
190    configure_speed_divider(Speed::Mbps100);
191}
192
193/// Programs the RMII TX/RX clock divider for the selected negotiated line speed.
194///
195/// ESP32-P4 uses divider `1` for 100 Mbps operation and divider `10` for
196/// 10 Mbps operation.
197pub fn configure_speed_divider(speed: Speed) {
198    let divider = divider_for_speed(speed);
199    let peri_clk_ctrl01 = regs::read(HP_SYS_CLKRST_PERI_CLK_CTRL01_REG);
200    let peri_clk_ctrl01 = replace_field(
201        peri_clk_ctrl01,
202        HP_SYS_CLKRST_REG_EMAC_RX_CLK_DIV_NUM_MASK,
203        HP_SYS_CLKRST_REG_EMAC_RX_CLK_DIV_NUM_SHIFT,
204        divider,
205    );
206    let peri_clk_ctrl01 = replace_field(
207        peri_clk_ctrl01,
208        HP_SYS_CLKRST_REG_EMAC_TX_CLK_DIV_NUM_MASK,
209        HP_SYS_CLKRST_REG_EMAC_TX_CLK_DIV_NUM_SHIFT,
210        divider,
211    );
212
213    regs::write(HP_SYS_CLKRST_PERI_CLK_CTRL01_REG, peri_clk_ctrl01);
214}
215
216#[cfg(test)]
217#[allow(dead_code)]
218pub(crate) fn emac_clock_tree_enabled() -> bool {
219    regs::read(HP_SYS_CLKRST_SOC_CLK_CTRL1_REG) & HP_SYS_CLKRST_REG_EMAC_SYS_CLK_EN != 0
220}
221
222#[cfg(test)]
223#[allow(dead_code)]
224pub(crate) fn emac_reset_asserted() -> bool {
225    regs::read(LP_CLKRST_HP_SDMMC_EMAC_RST_CTRL_REG) & LP_CLKRST_RST_EN_EMAC != 0
226}
227
228/// Enables the EMAC clock tree.
229///
230/// Bits programmed here are matched bit-for-bit against a working IDF v5.3
231/// baseline dump on Waveshare ESP32-P4-ETH after `Ethernet Link Up`. Three
232/// "pad enable" bits that earlier versions of this driver set are NOT set by
233/// IDF and are intentionally omitted here:
234///
235///   - `PAD_EMAC_REF_CLK_EN` (PERI_CLK_CTRL00 bit 24)
236///   - `HP_PAD_EMAC_TX_CLK_EN` (LP_HP_CLK_CTRL bit 13)
237///   - `HP_PAD_EMAC_RX_CLK_EN` (LP_HP_CLK_CTRL bit 14)
238///
239/// `LP_HP_CLK_CTRL` keeps only `HP_PAD_EMAC_TXRX_CLK_EN` (bit 15) which IDF
240/// also sets. Setting the extra three bits drove our `PERI_CLK_CTRL00 = 0x29`
241/// (vs IDF `0x28`) and `LP_HP_CLK_CTRL = 0x1FFFFFFC` (vs IDF `0x1FFF9FFD`)
242/// — see clk_dump example output captured 2026-04-25.
243///
244/// Similarly, `LP_CLKRST_FORCE_NORST_EMAC` is no longer asserted: IDF leaves
245/// `LP_EMAC_RST_CTRL = 0x00000000` after init while we previously had
246/// `0x80000000`. Clearing the reset gate is sufficient; forcing the
247/// "no-reset" override is not.
248fn enable_emac_clock_tree() {
249    set_bits(
250        HP_SYS_CLKRST_SOC_CLK_CTRL1_REG,
251        HP_SYS_CLKRST_REG_EMAC_SYS_CLK_EN,
252    );
253    set_bits(
254        HP_SYS_CLKRST_PERI_CLK_CTRL00_REG,
255        HP_SYS_CLKRST_REG_EMAC_RMII_CLK_EN | HP_SYS_CLKRST_REG_EMAC_RX_CLK_EN,
256    );
257    set_bits(
258        HP_SYS_CLKRST_PERI_CLK_CTRL01_REG,
259        HP_SYS_CLKRST_REG_EMAC_TX_CLK_EN,
260    );
261    // We rely on RAM-only loading via espflash --ram --no-stub which bypasses
262    // the IDF 2nd-stage bootloader. ROM startup leaves bits 13/14 of
263    // LP_HP_CLK_CTRL set; full IDF system_init clears them. We must clear them
264    // explicitly to land on the same state as the IDF baseline. `set_bits`
265    // alone (OR) cannot do that.
266    let hp_clk = (regs::read(LP_CLKRST_HP_CLK_CTRL_REG)
267        & !(LP_CLKRST_HP_PAD_EMAC_TX_CLK_EN | LP_CLKRST_HP_PAD_EMAC_RX_CLK_EN))
268        | LP_CLKRST_HP_PAD_EMAC_TXRX_CLK_EN;
269    regs::write(LP_CLKRST_HP_CLK_CTRL_REG, hp_clk);
270
271    let mut reset = regs::read(LP_CLKRST_HP_SDMMC_EMAC_RST_CTRL_REG);
272    reset &= !(LP_CLKRST_RST_EN_EMAC | LP_CLKRST_FORCE_NORST_EMAC);
273    regs::write(LP_CLKRST_HP_SDMMC_EMAC_RST_CTRL_REG, reset);
274
275    for _ in 0..CLOCK_CONFIG_DELAY_SPINS {
276        spin_loop();
277    }
278}
279
280fn configure_ref_clock_pin(pin: RefClockPin) {
281    let iomux = input_pin_iomux_reg(pin);
282    let value = (regs::read(iomux) & !(MCU_SEL_MASK | FUN_PU | FUN_PD))
283        | FUN_IE
284        | (EMAC_RMII_CLK_PAD_FUNC << MCU_SEL_SHIFT);
285
286    regs::write(iomux, value);
287}
288
289fn configure_ref_50m_output_pin(pin: MpllClockOutPin) {
290    let iomux = output_pin_iomux_reg(pin);
291    let value = (regs::read(iomux) & !(MCU_SEL_MASK | FUN_IE | FUN_PU | FUN_PD))
292        | (REF_50M_CLK_PAD_FUNC << MCU_SEL_SHIFT);
293
294    regs::write(iomux, value);
295}
296
297fn enable_ref_50m_clock() {
298    set_bits(LP_CLKRST_HP_CLK_CTRL_REG, LP_CLKRST_HP_MPLL_500M_CLK_EN);
299
300    let ref_clk_ctrl0 = replace_field(
301        regs::read(HP_SYS_CLKRST_REF_CLK_CTRL0_REG),
302        HP_SYS_CLKRST_REG_REF_50M_CLK_DIV_NUM_MASK,
303        HP_SYS_CLKRST_REG_REF_50M_CLK_DIV_NUM_SHIFT,
304        REF_50M_CLK_DIV_DEFAULT,
305    );
306    regs::write(HP_SYS_CLKRST_REF_CLK_CTRL0_REG, ref_clk_ctrl0);
307    set_bits(
308        HP_SYS_CLKRST_REF_CLK_CTRL1_REG,
309        HP_SYS_CLKRST_REG_REF_50M_CLK_EN,
310    );
311}
312
313fn select_external_rmii_clock() {
314    let peri_clk_ctrl00 = regs::read(HP_SYS_CLKRST_PERI_CLK_CTRL00_REG);
315    let peri_clk_ctrl00 = replace_field(
316        peri_clk_ctrl00,
317        HP_SYS_CLKRST_REG_EMAC_RMII_CLK_SRC_SEL_MASK,
318        HP_SYS_CLKRST_REG_EMAC_RMII_CLK_SRC_SEL_SHIFT,
319        RMII_CLK_SRC_EXTERNAL,
320    ) & !HP_SYS_CLKRST_REG_EMAC_RX_CLK_SRC_SEL;
321
322    regs::write(HP_SYS_CLKRST_PERI_CLK_CTRL00_REG, peri_clk_ctrl00);
323
324    let peri_clk_ctrl01 =
325        regs::read(HP_SYS_CLKRST_PERI_CLK_CTRL01_REG) & !HP_SYS_CLKRST_REG_EMAC_TX_CLK_SRC_SEL;
326    regs::write(HP_SYS_CLKRST_PERI_CLK_CTRL01_REG, peri_clk_ctrl01);
327}
328
329fn divider_for_speed(speed: Speed) -> u32 {
330    match speed {
331        Speed::Mbps100 => 1,
332        Speed::Mbps10 => 10,
333    }
334}
335
336fn input_pin_iomux_reg(pin: RefClockPin) -> usize {
337    match pin {
338        RefClockPin::Gpio32 => IO_MUX_GPIO32_REG,
339        RefClockPin::Gpio44 => IO_MUX_GPIO44_REG,
340        RefClockPin::Gpio50 => IO_MUX_GPIO50_REG,
341    }
342}
343
344fn output_pin_iomux_reg(pin: MpllClockOutPin) -> usize {
345    match pin {
346        MpllClockOutPin::Gpio23 => IO_MUX_GPIO23_REG,
347        MpllClockOutPin::Gpio39 => IO_MUX_GPIO39_REG,
348    }
349}
350
351fn replace_field(value: u32, mask: u32, shift: u32, field: u32) -> u32 {
352    (value & !mask) | ((field << shift) & mask)
353}
354
355fn set_bits(reg: usize, bits: u32) {
356    regs::write(reg, regs::read(reg) | bits);
357}
358
359fn clear_bits(reg: usize, bits: u32) {
360    regs::write(reg, regs::read(reg) & !bits);
361}
362
363#[cfg(test)]
364mod tests {
365    use super::{
366        configure_clock_ext_in, configure_clock_mpll_out, configure_speed_divider,
367        disable_emac_clock_tree, divider_for_speed, input_pin_iomux_reg, output_pin_iomux_reg,
368        replace_field, MpllClockOutPin, RefClockPin, EMAC_RMII_CLK_PAD_FUNC, FUN_IE, FUN_PD,
369        FUN_PU, HP_SYS_CLKRST_PERI_CLK_CTRL00_REG, HP_SYS_CLKRST_PERI_CLK_CTRL01_REG,
370        HP_SYS_CLKRST_REF_CLK_CTRL0_REG, HP_SYS_CLKRST_REF_CLK_CTRL1_REG,
371        HP_SYS_CLKRST_REG_EMAC_RMII_CLK_EN, HP_SYS_CLKRST_REG_EMAC_RMII_CLK_SRC_SEL_MASK,
372        HP_SYS_CLKRST_REG_EMAC_RX_CLK_DIV_NUM_MASK, HP_SYS_CLKRST_REG_EMAC_RX_CLK_DIV_NUM_SHIFT,
373        HP_SYS_CLKRST_REG_EMAC_RX_CLK_EN, HP_SYS_CLKRST_REG_EMAC_SYS_CLK_EN,
374        HP_SYS_CLKRST_REG_EMAC_TX_CLK_DIV_NUM_MASK, HP_SYS_CLKRST_REG_EMAC_TX_CLK_DIV_NUM_SHIFT,
375        HP_SYS_CLKRST_REG_EMAC_TX_CLK_EN, HP_SYS_CLKRST_REG_PAD_EMAC_REF_CLK_EN,
376        HP_SYS_CLKRST_REG_REF_50M_CLK_DIV_NUM_MASK, HP_SYS_CLKRST_REG_REF_50M_CLK_DIV_NUM_SHIFT,
377        HP_SYS_CLKRST_REG_REF_50M_CLK_EN, HP_SYS_CLKRST_SOC_CLK_CTRL1_REG, IO_MUX_GPIO23_REG,
378        IO_MUX_GPIO32_REG, IO_MUX_GPIO39_REG, IO_MUX_GPIO44_REG, IO_MUX_GPIO50_REG,
379        LP_CLKRST_FORCE_NORST_EMAC, LP_CLKRST_HP_CLK_CTRL_REG, LP_CLKRST_HP_MPLL_500M_CLK_EN,
380        LP_CLKRST_HP_PAD_EMAC_RX_CLK_EN, LP_CLKRST_HP_PAD_EMAC_TXRX_CLK_EN,
381        LP_CLKRST_HP_PAD_EMAC_TX_CLK_EN, LP_CLKRST_HP_SDMMC_EMAC_RST_CTRL_REG,
382        LP_CLKRST_RST_EN_EMAC, MCU_SEL_MASK, MCU_SEL_SHIFT, REF_50M_CLK_DIV_DEFAULT,
383        REF_50M_CLK_PAD_FUNC,
384    };
385    use crate::{regs, Speed};
386
387    #[test]
388    fn ref_clock_pin_maps_to_expected_iomux_register() {
389        assert_eq!(input_pin_iomux_reg(RefClockPin::Gpio32), IO_MUX_GPIO32_REG);
390        assert_eq!(input_pin_iomux_reg(RefClockPin::Gpio44), IO_MUX_GPIO44_REG);
391        assert_eq!(input_pin_iomux_reg(RefClockPin::Gpio50), IO_MUX_GPIO50_REG);
392    }
393
394    #[test]
395    fn mpll_output_pin_maps_to_expected_iomux_register() {
396        assert_eq!(
397            output_pin_iomux_reg(MpllClockOutPin::Gpio23),
398            IO_MUX_GPIO23_REG
399        );
400        assert_eq!(
401            output_pin_iomux_reg(MpllClockOutPin::Gpio39),
402            IO_MUX_GPIO39_REG
403        );
404    }
405
406    #[test]
407    fn speed_divider_matches_checklist() {
408        assert_eq!(divider_for_speed(Speed::Mbps100), 1);
409        assert_eq!(divider_for_speed(Speed::Mbps10), 10);
410    }
411
412    #[test]
413    fn configure_speed_divider_updates_rx_and_tx_fields_only() {
414        regs::reset_test_registers();
415        regs::write(HP_SYS_CLKRST_PERI_CLK_CTRL01_REG, u32::MAX);
416
417        configure_speed_divider(Speed::Mbps10);
418        let value = regs::read(HP_SYS_CLKRST_PERI_CLK_CTRL01_REG);
419        let divider_mask =
420            HP_SYS_CLKRST_REG_EMAC_RX_CLK_DIV_NUM_MASK | HP_SYS_CLKRST_REG_EMAC_TX_CLK_DIV_NUM_MASK;
421
422        assert_eq!(
423            (value & HP_SYS_CLKRST_REG_EMAC_RX_CLK_DIV_NUM_MASK)
424                >> HP_SYS_CLKRST_REG_EMAC_RX_CLK_DIV_NUM_SHIFT,
425            10
426        );
427        assert_eq!(
428            (value & HP_SYS_CLKRST_REG_EMAC_TX_CLK_DIV_NUM_MASK)
429                >> HP_SYS_CLKRST_REG_EMAC_TX_CLK_DIV_NUM_SHIFT,
430            10
431        );
432        assert_eq!(value & !divider_mask, u32::MAX & !divider_mask);
433    }
434
435    #[test]
436    fn replace_field_overwrites_only_target_bits() {
437        let value = replace_field(
438            0xffff_ffff,
439            MCU_SEL_MASK,
440            MCU_SEL_SHIFT,
441            EMAC_RMII_CLK_PAD_FUNC,
442        );
443        let expected_mcu_sel = EMAC_RMII_CLK_PAD_FUNC << MCU_SEL_SHIFT;
444
445        assert_eq!(value & MCU_SEL_MASK, expected_mcu_sel);
446        assert_eq!(value & FUN_IE, FUN_IE);
447        assert_eq!(value & FUN_PU, FUN_PU);
448        assert_eq!(value & FUN_PD, FUN_PD);
449    }
450
451    #[test]
452    fn disable_clock_tree_clears_gates_and_asserts_reset() {
453        regs::reset_test_registers();
454        configure_clock_ext_in(RefClockPin::Gpio32);
455
456        disable_emac_clock_tree();
457
458        assert_eq!(
459            regs::read(HP_SYS_CLKRST_SOC_CLK_CTRL1_REG) & HP_SYS_CLKRST_REG_EMAC_SYS_CLK_EN,
460            0
461        );
462        assert_eq!(
463            regs::read(HP_SYS_CLKRST_PERI_CLK_CTRL00_REG)
464                & (HP_SYS_CLKRST_REG_PAD_EMAC_REF_CLK_EN
465                    | HP_SYS_CLKRST_REG_EMAC_RMII_CLK_EN
466                    | HP_SYS_CLKRST_REG_EMAC_RX_CLK_EN),
467            0
468        );
469        assert_eq!(
470            regs::read(HP_SYS_CLKRST_PERI_CLK_CTRL01_REG) & HP_SYS_CLKRST_REG_EMAC_TX_CLK_EN,
471            0
472        );
473        assert_eq!(
474            regs::read(LP_CLKRST_HP_CLK_CTRL_REG)
475                & (LP_CLKRST_HP_PAD_EMAC_TX_CLK_EN
476                    | LP_CLKRST_HP_PAD_EMAC_RX_CLK_EN
477                    | LP_CLKRST_HP_PAD_EMAC_TXRX_CLK_EN),
478            0
479        );
480        assert_ne!(
481            regs::read(LP_CLKRST_HP_SDMMC_EMAC_RST_CTRL_REG) & LP_CLKRST_RST_EN_EMAC,
482            0
483        );
484        assert_eq!(
485            regs::read(LP_CLKRST_HP_SDMMC_EMAC_RST_CTRL_REG) & LP_CLKRST_FORCE_NORST_EMAC,
486            0
487        );
488    }
489
490    #[test]
491    fn mpll_clock_out_enables_ref_50m_and_loopback_input() {
492        regs::reset_test_registers();
493
494        configure_clock_mpll_out(MpllClockOutPin::Gpio23, RefClockPin::Gpio32);
495
496        assert_ne!(
497            regs::read(LP_CLKRST_HP_CLK_CTRL_REG) & LP_CLKRST_HP_MPLL_500M_CLK_EN,
498            0
499        );
500        assert_ne!(
501            regs::read(HP_SYS_CLKRST_REF_CLK_CTRL1_REG) & HP_SYS_CLKRST_REG_REF_50M_CLK_EN,
502            0
503        );
504        assert_eq!(
505            (regs::read(HP_SYS_CLKRST_REF_CLK_CTRL0_REG)
506                & HP_SYS_CLKRST_REG_REF_50M_CLK_DIV_NUM_MASK)
507                >> HP_SYS_CLKRST_REG_REF_50M_CLK_DIV_NUM_SHIFT,
508            REF_50M_CLK_DIV_DEFAULT
509        );
510        assert_eq!(
511            regs::read(HP_SYS_CLKRST_PERI_CLK_CTRL00_REG)
512                & HP_SYS_CLKRST_REG_EMAC_RMII_CLK_SRC_SEL_MASK,
513            0
514        );
515        assert_eq!(
516            regs::read(IO_MUX_GPIO23_REG) & MCU_SEL_MASK,
517            REF_50M_CLK_PAD_FUNC << MCU_SEL_SHIFT
518        );
519        assert_eq!(regs::read(IO_MUX_GPIO23_REG) & FUN_IE, 0);
520        assert_eq!(
521            regs::read(IO_MUX_GPIO32_REG) & MCU_SEL_MASK,
522            EMAC_RMII_CLK_PAD_FUNC << MCU_SEL_SHIFT
523        );
524        assert_ne!(regs::read(IO_MUX_GPIO32_REG) & FUN_IE, 0);
525    }
526}