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#[cfg_attr(docsrs, procmacros::doc_replace(
77    "clk_out_gpio" => gpio_for_signal!(EMAC_CLK_OUT),
78    "clk_180_gpio" => gpio_for_signal!(EMAC_CLK_180),
79))]
80/// RMII reference clock generated internally by the ESP32 APLL at 50 MHz.
81///
82/// Should not be used together with Wi-Fi. No other APLL consumer must be active.
83///
84/// The APLL output is routed to __clk_out_gpio__ (`EMAC_CLK_OUT`) or __clk_180_gpio__
85/// (`EMAC_CLK_180`).  Wrap the chosen GPIO:
86/// ```rust,ignore
87/// ApllClock::new(peripherals.__clk_out_gpio__)
88/// ```
89pub struct ApllClock<P>(P);
90
91impl<P> ApllClock<P> {
92    /// Wraps the GPIO pin that outputs the APLL-generated reference clock.
93    pub fn new(pin: P) -> Self {
94        Self(pin)
95    }
96}
97
98impl<P> Sealed for ApllClock<P> {}
99
100impl<P: RmiiClkOut> RmiiClockConfig for ApllClock<P> {
101    fn configure(self) {
102        // Configure the APLL clock output pad.
103        self.0.configure_iomux();
104
105        // TODO: refactor into clock tree code
106
107        // Reference formula:
108        // apll_freq = xtal_freq * (4 + sdm2 + sdm1/256 + sdm0/65536) / ((o_div + 2) * 2)
109        //             ----------------------------------------------   -----------------
110        //                  350 MHz <= Numerator <= 500 MHz                Denominator
111
112        const APLL_ODIV_50MHZ: u8 = 2; // Fixed - prescribes numerator = 400 MHz
113
114        // SDM = (400MHz / f_xtal) - 4; Q6.16
115        let f_xtal = crate::clock::ll::xtal_clk_frequency();
116        let f_xtal_mhz = f_xtal / 1_000_000;
117
118        // SDM is a Q6.16 fixed-point number
119        let sdm = ((400 << 16) / f_xtal_mhz) - (4 << 16);
120
121        let sdm2 = (sdm >> 16) as u8;
122        let sdm1 = ((sdm >> 8) & 0xff) as u8;
123        let sdm0 = (sdm & 0xff) as u8;
124
125        trace!("SDM2: {}, SDM1: {}, SDM0: {}", sdm2, sdm1, sdm0);
126
127        // Power up the APLL.
128        LPWR::regs().ana_conf().modify(|_, w| {
129            w.plla_force_pd().clear_bit();
130            w.plla_force_pu().set_bit()
131        });
132
133        regi2c::I2C_APLL_DSDM2.write_field(sdm2);
134        regi2c::I2C_APLL_DSDM1.write_field(sdm1);
135        regi2c::I2C_APLL_DSDM0.write_field(sdm0);
136        // APLL configuration parameters
137        const CLK_LL_APLL_SDM_STOP_VAL_1: u8 = 0x09;
138        const CLK_LL_APLL_SDM_STOP_VAL_2_REV0: u8 = 0x69;
139        const CLK_LL_APLL_SDM_STOP_VAL_2_REV1: u8 = 0x49;
140        regi2c::I2C_APLL_SDM_CTRL.write_reg(CLK_LL_APLL_SDM_STOP_VAL_1);
141        if crate::soc::chip_revision_above(ChipRevision::from_combined(100)) {
142            regi2c::I2C_APLL_SDM_CTRL.write_reg(CLK_LL_APLL_SDM_STOP_VAL_2_REV1);
143        } else {
144            regi2c::I2C_APLL_SDM_CTRL.write_reg(CLK_LL_APLL_SDM_STOP_VAL_2_REV0);
145        }
146        regi2c::I2C_APLL_OR_OUTPUT_DIV.write_field(APLL_ODIV_50MHZ);
147
148        // Trigger IR calibration / start SDM.
149        const APLL_CALIBRATION_DELAY: u8 = 0x0F;
150        const APLL_CALIBRATION_RSTB: u8 = 0x10;
151        const APLL_CALIBRATION_START: u8 = 0x20;
152        regi2c::I2C_APLL_IR_CAL.write_reg(APLL_CALIBRATION_DELAY);
153        regi2c::I2C_APLL_IR_CAL
154            .write_reg(APLL_CALIBRATION_DELAY | APLL_CALIBRATION_RSTB | APLL_CALIBRATION_START);
155        // This seems wrong, is RSTB and START swapped?
156        regi2c::I2C_APLL_IR_CAL.write_reg(APLL_CALIBRATION_DELAY | APLL_CALIBRATION_RSTB);
157
158        // Wait for calibration to complete.
159        while regi2c::I2C_APLL_OR_CAL_END.read() == 0 {
160            // use ets_delay_us so the RTC bus doesn't get flooded
161            ets_delay_us(1);
162        }
163
164        EMAC_EXT::regs()
165            .ex_phyinf_conf()
166            .modify(|_, w| unsafe { w.phy_intf_sel().bits(PHY_INTF_RMII) });
167
168        EMAC_EXT::regs().ex_clkout_conf().modify(|_, w| unsafe {
169            // div_num=0, h_div_num=0 → output divider = 1 (no division)
170            w.div_num().bits(0);
171            w.h_div_num().bits(0)
172        });
173
174        EMAC_EXT::regs().ex_oscclk_conf().modify(|_, w| {
175            // clk_sel = 0: select internal (APLL) clock path
176            w.clk_sel().clear_bit()
177        });
178
179        EMAC_EXT::regs().ex_clk_ctrl().modify(|_, w| {
180            w.int_en().set_bit();
181            w.ext_en().clear_bit()
182        });
183    }
184}
185
186// ── MiiClock ──────────────────────────────────────────────────────────────
187
188#[cfg_attr(docsrs, procmacros::doc_replace(
189    "tx_clk_gpio" => gpio_for_signal!(EMAC_TX_CLK),
190    "rx_clk_gpio" => gpio_for_signal!(EMAC_RX_CLK),
191))]
192/// MII clock configuration.
193///
194/// In MII mode the PHY drives both `TX_CLK` (__tx_clk_gpio__) and `RX_CLK` (__rx_clk_gpio__).
195/// The EMAC_EXT block only needs the MII clock buffers enabled;
196/// no internal clock source is required.
197pub(crate) struct MiiClock;
198
199impl MiiClock {
200    pub(super) fn configure(&self) {
201        EMAC_EXT::regs()
202            .ex_phyinf_conf()
203            .modify(|_, w| unsafe { w.phy_intf_sel().bits(PHY_INTF_MII) });
204
205        EMAC_EXT::regs().ex_clk_ctrl().modify(|_, w| {
206            w.mii_clk_tx_en().set_bit();
207            w.mii_clk_rx_en().set_bit()
208        });
209    }
210}