Skip to main content

esp_phy/
lib.rs

1//! PHY initialization handling for chips with a radio.
2//!
3//! This should be considered an implementation detail of `esp-radio` and similar 3rd party crates.
4//!
5//! # Usage
6//! ## Enabling and Disabling the PHY
7//! Use [enable_phy] to enable the PHY. Drop the returned [PhyInitGuard] to disable the PHY.
8//! Enabling / disabling the PHY is ref-counted so these actions need to be balanced.
9//!
10//! ## Backing Up and Restoring PHY Calibration Data
11//! If the PHY has already been calibrated, you can use [backup_phy_calibration_data] to persist
12//! calibration data elsewhere (e.g. in flash). Using [set_phy_calibration_data] you can restore
13//! previously persisted calibration data.
14//! ## Config Options
15#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_phy_config_table.md"))]
16//! ## Feature Flags
17#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
18#![doc(html_logo_url = "https://docs.espressif.com/projects/rust/esp-rs-grey-bg.svg")]
19#![no_std]
20#![deny(missing_docs)]
21
22// MUST be the first module
23mod fmt;
24pub(crate) mod reg_access;
25
26use core::{cell::Cell, marker::PhantomData};
27
28use esp_hal::system::Cpu;
29#[cfg(esp32)]
30use esp_hal::time::{Duration, Instant};
31use esp_sync::{NonReentrantMutex, RawMutex};
32
33/// Tracks the number of references to the PHY clock.
34static PHY_CLOCK_REF_COUNTER: embassy_sync::blocking_mutex::Mutex<RawMutex, Cell<u8>> =
35    embassy_sync::blocking_mutex::Mutex::new(Cell::new(0));
36
37fn increase_phy_clock_ref_count_internal() {
38    PHY_CLOCK_REF_COUNTER.lock(|phy_clock_ref_counter| {
39        let phy_clock_ref_count = phy_clock_ref_counter.get();
40
41        if phy_clock_ref_count == 0 {
42            phy_clocks::enable_phy(true);
43        }
44        let new_phy_clock_ref_count = unwrap!(
45            phy_clock_ref_count.checked_add(1),
46            "PHY clock ref count overflowed."
47        );
48
49        phy_clock_ref_counter.set(new_phy_clock_ref_count);
50    })
51}
52
53fn decrease_phy_clock_ref_count_internal() {
54    PHY_CLOCK_REF_COUNTER.lock(|phy_clock_ref_counter| {
55        let new_phy_clock_ref_count = unwrap!(
56            phy_clock_ref_counter.get().checked_sub(1),
57            "PHY clock ref count underflowed. Either you forgot a PhyClockGuard, or used PhyController::decrease_phy_clock_ref_count incorrectly."
58        );
59
60        if new_phy_clock_ref_count == 0 {
61            phy_clocks::enable_phy(false);
62        }
63
64        phy_clock_ref_counter.set(new_phy_clock_ref_count);
65    })
66}
67
68#[derive(Debug)]
69/// Prevents the PHY clock from being disabled.
70///
71/// As long as at least one [PhyClockGuard] exists, the PHY clock will remain
72/// active. To release this guard, you can either let it go out of scope or use
73/// [PhyClockGuard::release] to explicitly release it.
74pub struct PhyClockGuard<'d> {
75    _phantom: PhantomData<&'d ()>,
76}
77
78impl PhyClockGuard<'_> {
79    #[inline]
80    /// Release the clock guard.
81    ///
82    /// The PHY clock will be disabled, if this is the last clock guard.
83    pub fn release(self) {
84        // Runs the Drop implementation
85    }
86}
87
88impl Drop for PhyClockGuard<'_> {
89    fn drop(&mut self) {
90        decrease_phy_clock_ref_count_internal();
91    }
92}
93pub(crate) mod sys {
94    #[cfg(esp32)]
95    pub use esp_wifi_sys_esp32::*;
96    #[cfg(esp32c2)]
97    pub use esp_wifi_sys_esp32c2::*;
98    #[cfg(esp32c3)]
99    pub use esp_wifi_sys_esp32c3::*;
100    #[cfg(esp32c5)]
101    pub use esp_wifi_sys_esp32c5::*;
102    #[cfg(esp32c6)]
103    pub use esp_wifi_sys_esp32c6::*;
104    #[cfg(esp32c61)]
105    pub use esp_wifi_sys_esp32c61::*;
106    #[cfg(esp32h2)]
107    pub use esp_wifi_sys_esp32h2::*;
108    #[cfg(esp32s2)]
109    pub use esp_wifi_sys_esp32s2::*;
110    #[cfg(esp32s3)]
111    pub use esp_wifi_sys_esp32s3::*;
112    #[cfg(esp32s31)]
113    pub use esp_wifi_sys_esp32s31::*;
114}
115
116mod common_adapter;
117mod phy_clocks;
118mod phy_init_data;
119
120/// Length of the PHY calibration data.
121pub const PHY_CALIBRATION_DATA_LENGTH: usize =
122    core::mem::size_of::<sys::include::esp_phy_calibration_data_t>();
123
124/// Type alias for opaque calibration data.
125pub type PhyCalibrationData = [u8; PHY_CALIBRATION_DATA_LENGTH];
126
127#[cfg(phy_backed_up_digital_register_count_is_set)]
128type PhyDigRegsBackup =
129    [u32; esp_metadata_generated::property!("phy.backed_up_digital_register_count")];
130
131#[cfg(esp32)]
132/// Callback to update the MAC time.
133///
134/// The duration is the delta, that has been accumulated between the PHY clock and the normal
135/// system timers, since the last time this callback was called. This accounts for the PHY being
136/// enabled and disabled, before this callback was set.
137pub type MacTimeUpdateCb = fn(Duration);
138
139static ESP_PHY_LOCK: RawMutex = RawMutex::new();
140
141/// PHY initialization state
142struct PhyState {
143    /// Number of references to the PHY.
144    ref_count: usize,
145    /// The calibration data used for initialization.
146    ///
147    /// If this is [None], when `PhyController::enable_phy` is called, it will be initialized to
148    /// zero and a full calibration is performed.
149    calibration_data: Option<PhyCalibrationData>,
150    /// Has the PHY been calibrated since the chip was powered up.
151    calibrated: bool,
152    /// Last calibration result code.
153    calibration_result: i32,
154
155    #[cfg(phy_backed_up_digital_register_count_is_set)]
156    /// Backup of the digital PHY registers.
157    phy_digital_register_backup: Option<PhyDigRegsBackup>,
158
159    // Chip specific.
160    #[cfg(esp32)]
161    /// Timestamp at which the modem clock domain state transitioned.
162    phy_clock_state_transition_timestamp: Instant,
163    #[cfg(esp32)]
164    /// The accumulated delta since the last time the callback was called.
165    mac_clock_delta_since_last_call: Duration,
166    #[cfg(esp32)]
167    /// Callback to update the MAC time.
168    mac_time_update_cb: Option<MacTimeUpdateCb>,
169}
170
171impl PhyState {
172    /// Initialize the PHY state.
173    pub const fn new() -> Self {
174        Self {
175            ref_count: 0,
176            calibration_data: None,
177            calibrated: false,
178            calibration_result: 0,
179
180            #[cfg(phy_backed_up_digital_register_count_is_set)]
181            phy_digital_register_backup: None,
182
183            #[cfg(esp32)]
184            phy_clock_state_transition_timestamp: Instant::EPOCH,
185            #[cfg(esp32)]
186            mac_clock_delta_since_last_call: Duration::ZERO,
187            #[cfg(esp32)]
188            mac_time_update_cb: None,
189        }
190    }
191
192    /// Get a reference to the calibration data.
193    ///
194    /// If no calibration data is available, it will be initialized to zero.
195    pub fn calibration_data(&mut self) -> &mut PhyCalibrationData {
196        self.calibration_data
197            .get_or_insert([0u8; PHY_CALIBRATION_DATA_LENGTH])
198    }
199
200    /// Calibrate the PHY.
201    fn calibrate(&mut self) {
202        #[cfg(esp32s2)]
203        unsafe {
204            sys::include::phy_eco_version_sel(esp_hal::efuse::chip_revision().major);
205        }
206        // For a combo module, PHY enable will not put the
207        // radio into the Wi-Fi RX state by default; the Wi-Fi driver is then responsible for
208        // turning Wi-Fi RX on/off via `set_wifi_rx_enabled` when it enables/disables the PHY.
209        // This mirrors `esp_phy_load_cal_and_init` in ESP-IDF.
210        cfg_select! {
211            esp32c5 => {
212                // C5 is intentionally excluded: ESP-IDF leaves `SOC_PHY_COMBO_MODULE` undefined
213                // for C5, so it doesn't call `phy_init_param_set` there. See:
214                // https://github.com/espressif/esp-idf/blob/7e3df61a/components/soc/esp32c5/include/soc/soc_caps.h#L658
215                // TODO: enable for C5.
216            }
217            phy_combo_module => unsafe {
218                sys::include::phy_init_param_set(1);
219            },
220            _ => {}
221        }
222
223        #[cfg(all(
224            phy_enable_usb,
225            any(soc_has_usb_fs, soc_has_usb_device),
226            not(any(esp32s2, esp32h2))
227        ))]
228        unsafe {
229            // FIXME: we should be using from esp-wifi-sys, but the function is missing for C6
230            // (CONFIG_ESP_PHY_ENABLE_USB is not defined)
231            unsafe extern "C" {
232                fn phy_bbpll_en_usb(param: bool);
233            }
234            phy_bbpll_en_usb(true);
235        }
236
237        let calibration_data_available = self.calibration_data.is_some();
238        let calibration_mode = if calibration_data_available {
239            // If the SOC just woke up from deep sleep and
240            // `phy_skip_calibration_after_deep_sleep` is enabled, no calibration will be
241            // performed.
242            if cfg!(phy_skip_calibration_after_deep_sleep) && is_reset_from_deepsleep() {
243                sys::include::esp_phy_calibration_mode_t_PHY_RF_CAL_NONE
244            } else if cfg!(phy_full_calibration) {
245                sys::include::esp_phy_calibration_mode_t_PHY_RF_CAL_FULL
246            } else {
247                sys::include::esp_phy_calibration_mode_t_PHY_RF_CAL_PARTIAL
248            }
249        } else {
250            sys::include::esp_phy_calibration_mode_t_PHY_RF_CAL_FULL
251        };
252        let init_data = &phy_init_data::PHY_INIT_DATA_DEFAULT;
253        unsafe {
254            self.calibration_result = sys::include::register_chipv7_phy(
255                init_data,
256                self.calibration_data() as *mut PhyCalibrationData as *mut _,
257                calibration_mode,
258            );
259        }
260        self.calibrated = true;
261    }
262
263    #[cfg(phy_backed_up_digital_register_count_is_set)]
264    /// Backup the digital PHY register into memory.
265    fn backup_digital_regs(&mut self) {
266        unsafe {
267            sys::include::phy_dig_reg_backup(
268                true,
269                self.phy_digital_register_backup.get_or_insert_default() as *mut u32,
270            );
271        }
272    }
273
274    #[cfg(phy_backed_up_digital_register_count_is_set)]
275    /// Restore the digital PHY registers from memory.
276    ///
277    /// This panics if the registers weren't previously backed up.
278    fn restore_digital_regs(&mut self) {
279        unsafe {
280            sys::include::phy_dig_reg_backup(
281                false,
282                self.phy_digital_register_backup
283                    .as_mut()
284                    .expect("Can't restore digital PHY registers from backup, without a backup.")
285                    as *mut u32,
286            );
287            self.phy_digital_register_backup = None;
288        }
289    }
290
291    /// Increase the number of references to the PHY.
292    ///
293    /// If the ref count was zero, the PHY will be initialized.
294    pub fn increase_ref_count(&mut self) {
295        if self.ref_count == 0 {
296            #[cfg(esp32)]
297            {
298                let now = Instant::now();
299                let delta = now - self.phy_clock_state_transition_timestamp;
300                self.phy_clock_state_transition_timestamp = now;
301                self.mac_clock_delta_since_last_call += delta;
302            }
303            if self.calibrated {
304                unsafe {
305                    sys::include::phy_wakeup_init();
306                }
307                #[cfg(phy_backed_up_digital_register_count_is_set)]
308                self.restore_digital_regs();
309            } else {
310                self.calibrate();
311                self.calibrated = true;
312            }
313        }
314        #[cfg(esp32)]
315        if let Some(cb) = self.mac_time_update_cb {
316            (cb)(self.mac_clock_delta_since_last_call);
317            self.mac_clock_delta_since_last_call = Duration::ZERO;
318        }
319
320        self.ref_count += 1;
321    }
322
323    /// Decrease the number of reference to the PHY.
324    ///
325    /// If the ref count hits zero, the PHY will be deinitialized.
326    ///
327    /// # Panics
328    /// This panics, if the PHY ref count is already at zero.
329    pub fn decrease_ref_count(&mut self) {
330        self.ref_count = self
331            .ref_count
332            .checked_sub(1)
333            .expect("PHY init ref count dropped below zero.");
334        if self.ref_count == 0 {
335            #[cfg(phy_backed_up_digital_register_count_is_set)]
336            self.backup_digital_regs();
337            unsafe {
338                // Disable PHY and RF.
339                sys::include::phy_close_rf();
340
341                // Power down PHY temperature sensor.
342                #[cfg(not(esp32))]
343                sys::include::phy_xpd_tsens();
344            }
345            #[cfg(esp32)]
346            {
347                self.phy_clock_state_transition_timestamp = Instant::now();
348            }
349            // The PHY clock guard will get released in the drop code of the PhyInitGuard. Note
350            // that this accepts a slight skewing of the delta, since the clock will be disabled
351            // after we record the value. This shouldn't be too bad though.
352        }
353    }
354}
355
356fn is_reset_from_deepsleep() -> bool {
357    // feature gated to avoid forgetting to double check the correct value for future chips
358    #[cfg(any(
359        esp32, esp32c2, esp32c3, esp32c5, esp32c6, esp32c61, esp32h2, esp32s2, esp32s3, esp32s31
360    ))]
361    const CORE_DEEP_SLEEP: u32 = 5;
362
363    unsafe extern "C" {
364        fn rtc_get_reset_reason(cpu_num: u32) -> u32;
365    }
366
367    let reason = unsafe { rtc_get_reset_reason(Cpu::current() as u32) };
368
369    reason == CORE_DEEP_SLEEP
370}
371
372/// Global PHY initialization state
373static PHY_STATE: NonReentrantMutex<PhyState> = NonReentrantMutex::new(PhyState::new());
374
375/// Prevents the PHY from being deinitialized.
376///
377/// As long as at least one [PhyInitGuard] exists, the PHY will remain initialized. To release this
378/// guard, you can either let it go out of scope, or use [PhyInitGuard::release] to explicitly
379/// release it.
380#[derive(Debug)]
381pub struct PhyInitGuard<'d> {
382    _phy_clock_guard: PhyClockGuard<'d>,
383}
384
385impl PhyInitGuard<'_> {
386    #[inline]
387    /// Release the init guard.
388    ///
389    /// The PHY will be disabled, if this is the last init guard.
390    pub fn release(self) {
391        // Runs the Drop implementation
392    }
393}
394
395impl Drop for PhyInitGuard<'_> {
396    fn drop(&mut self) {
397        PHY_STATE.with(|phy_state| phy_state.decrease_ref_count());
398    }
399}
400
401/// Enable the PHY.
402///
403/// If no other [PhyInitGuard] is currently alive, this will also initialize the PHY, which
404/// will involve a full RF calibration, unless you loaded previously backed up calibration
405/// data with [set_phy_calibration_data].
406pub fn enable_phy<'d>() -> PhyInitGuard<'d> {
407    // In esp-idf, this is done after calculating the MAC time delta, but it shouldn't make
408    // much of a difference.
409    let _phy_clock_guard = enable_phy_clock();
410
411    PHY_STATE.with(|phy_state| phy_state.increase_ref_count());
412
413    PhyInitGuard { _phy_clock_guard }
414}
415
416/// Manually disable the PHY.
417///
418/// This is only useful if you [core::mem::forget] the [PhyInitGuard].
419pub fn disable_phy() {
420    PHY_STATE.with(|phy_state| phy_state.decrease_ref_count());
421    // Balance the PhyClockGuard that was mem::forget'd with PhyInitGuard.
422    // Without this, PHY_CLOCK_REF_COUNTER (u8) leaks on every phy_enable/phy_disable
423    // cycle from the WiFi blob C-callback interface, overflowing after ~9 TCP connects.
424    decrease_phy_clock_ref_count_internal();
425}
426
427/// Enable the PHY for Wi-Fi: enables the PHY ([enable_phy]) and turns Wi-Fi RX on.
428///
429/// Wi-Fi must use this instead of [enable_phy] so that combo modules (which do not power up in the
430/// Wi-Fi RX state) actually receive.
431pub fn enable_phy_with_wifi_rx() {
432    core::mem::forget(enable_phy());
433    set_wifi_rx_enabled(true);
434}
435
436/// Disable the PHY for Wi-Fi: turns Wi-Fi RX off and disables the PHY ([disable_phy]).
437///
438/// Counterpart to [enable_phy_with_wifi_rx]. Mirrors ESP-IDF's `esp_phy_disable_wrapper`.
439pub fn disable_phy_with_wifi_rx() {
440    set_wifi_rx_enabled(false);
441    disable_phy();
442}
443
444/// Set the Wi-Fi RX state of the radio.
445///
446/// On combo modules the PHY does not power up in the Wi-Fi RX state, so Wi-Fi RX has to be enabled
447/// explicitly whenever the PHY is brought up, and disabled again when it is torn down. This
448/// mirrors the `phy_wifi_enable_set` calls ESP-IDF performs in its PHY enable/disable
449/// wrappers.
450///
451/// On non-combo modules this is a no-op.
452fn set_wifi_rx_enabled(enabled: bool) {
453    cfg_select! {
454        esp32c5 => {
455            // C5 is excluded for the same reason as `phy_init_param_set` (ESP-IDF leaves
456            // `SOC_PHY_COMBO_MODULE` undefined for C5, see:
457            // https://github.com/espressif/esp-idf/blob/7e3df61a/components/soc/esp32c5/include/soc/soc_caps.h#L658);
458            // its Wi-Fi adapter only calls `phy_wifi_enable_set` alongside a `set_bb_wdg`
459            // workaround we don't implement yet. TODO: enable for C5 once `set_bb_wdg`
460            // is handled.
461            let _ = enabled;
462        }
463        phy_combo_module => unsafe {
464            sys::include::phy_wifi_enable_set(enabled as u8);
465        },
466        _ => {
467            let _ = enabled;
468        }
469    }
470}
471
472/// Enable the PHY clock and acquire a [PhyClockGuard].
473///
474/// The PHY clock will only be disabled once all [PhyClockGuard]s are dropped.
475pub fn enable_phy_clock<'d>() -> PhyClockGuard<'d> {
476    increase_phy_clock_ref_count_internal();
477    PhyClockGuard {
478        _phantom: PhantomData,
479    }
480}
481
482/// Set the MAC time update callback.
483///
484/// See [MacTimeUpdateCb] for details.
485#[cfg(esp32)]
486pub fn set_mac_time_update_cb(mac_time_update_cb: MacTimeUpdateCb) {
487    PHY_STATE.with(|phy_state| phy_state.mac_time_update_cb = Some(mac_time_update_cb));
488}
489
490#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
491#[cfg_attr(feature = "defmt", derive(defmt::Format))]
492/// Calibration data was already set.
493pub struct CalibrationDataAlreadySetError;
494
495#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
496#[cfg_attr(feature = "defmt", derive(defmt::Format))]
497/// No calibration data is available.
498pub struct NoCalibrationDataError;
499
500/// Result of the PHY calibration.
501#[derive(Debug, Clone, Copy)]
502#[cfg_attr(feature = "defmt", derive(defmt::Format))]
503#[non_exhaustive]
504pub enum CalibrationResult {
505    /// The calibration data was valid and was used for calibration.
506    Ok,
507
508    /// The calibration data checksum check failed, or the calibration data was outdated.
509    DataCheckFailed,
510}
511
512/// Load previously backed up PHY calibration data.
513pub fn set_phy_calibration_data(
514    calibration_data: &PhyCalibrationData,
515) -> Result<(), CalibrationDataAlreadySetError> {
516    PHY_STATE.with(|phy_state| {
517        if phy_state.calibration_data.is_some() {
518            Err(CalibrationDataAlreadySetError)
519        } else {
520            phy_state.calibration_data = Some(*calibration_data);
521            Ok(())
522        }
523    })
524}
525
526/// Backup the PHY calibration data to the provided slice.
527pub fn backup_phy_calibration_data(
528    buffer: &mut PhyCalibrationData,
529) -> Result<(), NoCalibrationDataError> {
530    PHY_STATE.with(|phy_state| {
531        phy_state
532            .calibration_data
533            .as_mut()
534            .ok_or(NoCalibrationDataError)
535            .map(|calibration_data| buffer.copy_from_slice(calibration_data.as_slice()))
536    })
537}
538
539/// Get the last calibration result.
540///
541/// This can be used to know if any previously persisted calibration data is outdated/invalid and
542/// needs to get updated.
543pub fn last_calibration_result() -> Option<CalibrationResult> {
544    PHY_STATE.with(|phy_state| {
545        if phy_state.calibrated {
546            Some(
547                if phy_state.calibration_result == sys::include::ESP_CAL_DATA_CHECK_FAIL as i32 {
548                    CalibrationResult::DataCheckFailed
549                } else {
550                    CalibrationResult::Ok
551                },
552            )
553        } else {
554            None
555        }
556    })
557}