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}