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}