Skip to main content

esp_hal/ethernet/clock/
esp32.rs

1//! EMAC clock configuration for ESP32.
2//!
3//! # RMII reference clock sources
4//!
5//! The ESP32 EMAC needs a 50 MHz reference clock for RMII.  There are two
6//! hardware modes:
7//!
8//! - **[`ExternalRefClock`]** — the PHY drives `EMAC_TX_CLK`. Set
9//!   [`RmiiPinBundle::clock`][crate::ethernet::RmiiPinBundle::clock] when calling
10//!   [`Ethernet::new`][crate::ethernet::Ethernet::new]; use this when the PHY has an oscillator.
11//!
12//! - **[`ApllClock`]** — the ESP32 APLL is tuned to 50 MHz and the output is routed through
13//!   `EMAC_CLK_OUT` or `EMAC_CLK_180`. The EMAC_EXT block feeds this clock back into the MAC. Use
14//!   this when the PHY requires an external clock reference (e.g. LAN8720A in clock-output mode).
15//!
16//!   **Warning**: the APLL is shared with I²S and LCD. Requesting 50 MHz while another subsystem
17//!   already uses the APLL at a different frequency will result in silent corruption. There is
18//!   currently no shared-APLL arbitration in esp-hal. Also, APLL is unstable when Wi-Fi is
19//!   active, so Wi-Fi must not be used together with this clock source.
20//!
21//! # MII clock source
22//!
23//! Using [`Ethernet::new`][crate::ethernet::Ethernet::new] with
24//! [`MiiPinBundle`][crate::ethernet::MiiPinBundle] enables the EMAC_EXT MII clock buffers. In MII
25//! mode the PHY drives both `TX_CLK` and `RX_CLK` at 25 MHz (100 Mbps) or 2.5 MHz (10 Mbps) — no
26//! internal clock generation is required.
27
28use esp_rom_sys::rom::ets_delay_us;
29
30use crate::{
31    efuse::ChipRevision,
32    ethernet::{RmiiClkIn, RmiiClkOut, RmiiClockConfig},
33    peripherals::{EMAC_EXT, LPWR},
34    private::Sealed,
35    soc::regi2c,
36};
37
38/// PHY interface selection for RMII mode in `EMAC_EXT.ex_phyinf_conf`.
39pub(super) const PHY_INTF_RMII: u8 = 4;
40/// PHY interface selection for MII mode in `EMAC_EXT.ex_phyinf_conf`.
41pub(super) const PHY_INTF_MII: u8 = 0;
42
43/// RMII reference clock provided externally by the PHY.
44pub struct ExternalRefClock<P>(P);
45
46impl<P> ExternalRefClock<P> {
47    /// Wraps the GPIO pin that receives the PHY reference clock.
48    pub fn new(pin: P) -> Self {
49        Self(pin)
50    }
51}
52
53impl<P> Sealed for ExternalRefClock<P> {}
54
55impl<P: RmiiClkIn> RmiiClockConfig for ExternalRefClock<P> {
56    fn configure(self) {
57        // Configure the pad (IOMUX AF5, input buffer enabled).
58        self.0.configure_iomux();
59
60        EMAC_EXT::regs()
61            .ex_phyinf_conf()
62            .modify(|_, w| unsafe { w.phy_intf_sel().bits(PHY_INTF_RMII) });
63
64        EMAC_EXT::regs().ex_oscclk_conf().modify(|_, w| {
65            // clk_sel = 1: select external clock input
66            w.clk_sel().set_bit()
67        });
68
69        EMAC_EXT::regs().ex_clk_ctrl().modify(|_, w| {
70            w.ext_en().set_bit();
71            w.int_en().clear_bit()
72        });
73    }
74}
75
76/// RMII reference clock generated internally by the ESP32 APLL at 50 MHz.
77///
78/// Should not be used together with Wi-Fi. No other APLL consumer must be active.
79///
80/// The APLL output is routed to GPIO16 (`EMAC_CLK_OUT`) or GPIO17
81/// (`EMAC_CLK_180`).  Wrap the chosen GPIO:
82/// ```rust,ignore
83/// ApllClock::new(peripherals.GPIO16)
84/// ```
85pub struct ApllClock<P>(P);
86
87impl<P> ApllClock<P> {
88    /// Wraps the GPIO pin that outputs the APLL-generated reference clock.
89    pub fn new(pin: P) -> Self {
90        Self(pin)
91    }
92}
93
94impl<P> Sealed for ApllClock<P> {}
95
96impl<P: RmiiClkOut> RmiiClockConfig for ApllClock<P> {
97    fn configure(self) {
98        // Configure the APLL clock output pad.
99        self.0.configure_iomux();
100
101        // TODO: refactor into clock tree code
102
103        // Reference formula:
104        // apll_freq = xtal_freq * (4 + sdm2 + sdm1/256 + sdm0/65536) / ((o_div + 2) * 2)
105        //             ----------------------------------------------   -----------------
106        //                  350 MHz <= Numerator <= 500 MHz                Denominator
107
108        const APLL_ODIV_50MHZ: u8 = 2; // Fixed - prescribes numerator = 400 MHz
109
110        // SDM = (400MHz / f_xtal) - 4; Q6.16
111        let f_xtal = crate::clock::ll::xtal_clk_frequency();
112        let f_xtal_mhz = f_xtal / 1_000_000;
113
114        // SDM is a Q6.16 fixed-point number
115        let sdm = ((400 << 16) / f_xtal_mhz) - (4 << 16);
116
117        let sdm2 = (sdm >> 16) as u8;
118        let sdm1 = ((sdm >> 8) & 0xff) as u8;
119        let sdm0 = (sdm & 0xff) as u8;
120
121        trace!("SDM2: {}, SDM1: {}, SDM0: {}", sdm2, sdm1, sdm0);
122
123        // Power up the APLL.
124        LPWR::regs().ana_conf().modify(|_, w| {
125            w.plla_force_pd().clear_bit();
126            w.plla_force_pu().set_bit()
127        });
128
129        regi2c::I2C_APLL_DSDM2.write_field(sdm2);
130        regi2c::I2C_APLL_DSDM1.write_field(sdm1);
131        regi2c::I2C_APLL_DSDM0.write_field(sdm0);
132        // APLL configuration parameters
133        const CLK_LL_APLL_SDM_STOP_VAL_1: u8 = 0x09;
134        const CLK_LL_APLL_SDM_STOP_VAL_2_REV0: u8 = 0x69;
135        const CLK_LL_APLL_SDM_STOP_VAL_2_REV1: u8 = 0x49;
136        regi2c::I2C_APLL_SDM_CTRL.write_reg(CLK_LL_APLL_SDM_STOP_VAL_1);
137        if crate::soc::chip_revision_above(ChipRevision::from_combined(100)) {
138            regi2c::I2C_APLL_SDM_CTRL.write_reg(CLK_LL_APLL_SDM_STOP_VAL_2_REV1);
139        } else {
140            regi2c::I2C_APLL_SDM_CTRL.write_reg(CLK_LL_APLL_SDM_STOP_VAL_2_REV0);
141        }
142        regi2c::I2C_APLL_OR_OUTPUT_DIV.write_field(APLL_ODIV_50MHZ);
143
144        // Trigger IR calibration / start SDM.
145        const APLL_CALIBRATION_DELAY: u8 = 0x0F;
146        const APLL_CALIBRATION_RSTB: u8 = 0x10;
147        const APLL_CALIBRATION_START: u8 = 0x20;
148        regi2c::I2C_APLL_IR_CAL.write_reg(APLL_CALIBRATION_DELAY);
149        regi2c::I2C_APLL_IR_CAL
150            .write_reg(APLL_CALIBRATION_DELAY | APLL_CALIBRATION_RSTB | APLL_CALIBRATION_START);
151        // This seems wrong, is RSTB and START swapped?
152        regi2c::I2C_APLL_IR_CAL.write_reg(APLL_CALIBRATION_DELAY | APLL_CALIBRATION_RSTB);
153
154        // Wait for calibration to complete.
155        while regi2c::I2C_APLL_OR_CAL_END.read() == 0 {
156            // use ets_delay_us so the RTC bus doesn't get flooded
157            ets_delay_us(1);
158        }
159
160        EMAC_EXT::regs()
161            .ex_phyinf_conf()
162            .modify(|_, w| unsafe { w.phy_intf_sel().bits(PHY_INTF_RMII) });
163
164        EMAC_EXT::regs().ex_clkout_conf().modify(|_, w| unsafe {
165            // div_num=0, h_div_num=0 → output divider = 1 (no division)
166            w.div_num().bits(0);
167            w.h_div_num().bits(0)
168        });
169
170        EMAC_EXT::regs().ex_oscclk_conf().modify(|_, w| {
171            // clk_sel = 0: select internal (APLL) clock path
172            w.clk_sel().clear_bit()
173        });
174
175        EMAC_EXT::regs().ex_clk_ctrl().modify(|_, w| {
176            w.int_en().set_bit();
177            w.ext_en().clear_bit()
178        });
179    }
180}
181
182// ── MiiClock ──────────────────────────────────────────────────────────────
183
184/// MII clock configuration.
185///
186/// In MII mode the PHY drives both `TX_CLK` (GPIO0) and `RX_CLK` (GPIO5).
187/// The EMAC_EXT block only needs the MII clock buffers enabled;
188/// no internal clock source is required.
189pub(crate) struct MiiClock;
190
191impl MiiClock {
192    pub(super) fn configure(&self) {
193        EMAC_EXT::regs()
194            .ex_phyinf_conf()
195            .modify(|_, w| unsafe { w.phy_intf_sel().bits(PHY_INTF_MII) });
196
197        EMAC_EXT::regs().ex_clk_ctrl().modify(|_, w| {
198            w.mii_clk_tx_en().set_bit();
199            w.mii_clk_rx_en().set_bit()
200        });
201    }
202}