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://avatars.githubusercontent.com/u/46717278")]
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}
113
114mod common_adapter;
115mod phy_clocks;
116mod phy_init_data;
117
118/// Length of the PHY calibration data.
119pub const PHY_CALIBRATION_DATA_LENGTH: usize =
120    core::mem::size_of::<sys::include::esp_phy_calibration_data_t>();
121
122/// Type alias for opaque calibration data.
123pub type PhyCalibrationData = [u8; PHY_CALIBRATION_DATA_LENGTH];
124
125#[cfg(phy_backed_up_digital_register_count_is_set)]
126type PhyDigRegsBackup =
127    [u32; esp_metadata_generated::property!("phy.backed_up_digital_register_count")];
128
129#[cfg(esp32)]
130/// Callback to update the MAC time.
131///
132/// The duration is the delta, that has been accumulated between the PHY clock and the normal
133/// system timers, since the last time this callback was called. This accounts for the PHY being
134/// enabled and disabled, before this callback was set.
135pub type MacTimeUpdateCb = fn(Duration);
136
137static ESP_PHY_LOCK: RawMutex = RawMutex::new();
138
139/// PHY initialization state
140struct PhyState {
141    /// Number of references to the PHY.
142    ref_count: usize,
143    /// The calibration data used for initialization.
144    ///
145    /// If this is [None], when `PhyController::enable_phy` is called, it will be initialized to
146    /// zero and a full calibration is performed.
147    calibration_data: Option<PhyCalibrationData>,
148    /// Has the PHY been calibrated since the chip was powered up.
149    calibrated: bool,
150    /// Last calibration result code.
151    calibration_result: i32,
152
153    #[cfg(phy_backed_up_digital_register_count_is_set)]
154    /// Backup of the digital PHY registers.
155    phy_digital_register_backup: Option<PhyDigRegsBackup>,
156
157    // Chip specific.
158    #[cfg(esp32)]
159    /// Timestamp at which the modem clock domain state transitioned.
160    phy_clock_state_transition_timestamp: Instant,
161    #[cfg(esp32)]
162    /// The accumulated delta since the last time the callback was called.
163    mac_clock_delta_since_last_call: Duration,
164    #[cfg(esp32)]
165    /// Callback to update the MAC time.
166    mac_time_update_cb: Option<MacTimeUpdateCb>,
167}
168
169impl PhyState {
170    /// Initialize the PHY state.
171    pub const fn new() -> Self {
172        Self {
173            ref_count: 0,
174            calibration_data: None,
175            calibrated: false,
176            calibration_result: 0,
177
178            #[cfg(phy_backed_up_digital_register_count_is_set)]
179            phy_digital_register_backup: None,
180
181            #[cfg(esp32)]
182            phy_clock_state_transition_timestamp: Instant::EPOCH,
183            #[cfg(esp32)]
184            mac_clock_delta_since_last_call: Duration::ZERO,
185            #[cfg(esp32)]
186            mac_time_update_cb: None,
187        }
188    }
189
190    /// Get a reference to the calibration data.
191    ///
192    /// If no calibration data is available, it will be initialized to zero.
193    pub fn calibration_data(&mut self) -> &mut PhyCalibrationData {
194        self.calibration_data
195            .get_or_insert([0u8; PHY_CALIBRATION_DATA_LENGTH])
196    }
197
198    /// Calibrate the PHY.
199    fn calibrate(&mut self) {
200        #[cfg(esp32s2)]
201        unsafe {
202            sys::include::phy_eco_version_sel(esp_hal::efuse::chip_revision().major);
203        }
204        // Causes headaches for some reason.
205        // See: https://github.com/esp-rs/esp-hal/issues/4015
206        // #[cfg(phy_combo_module)]
207        // unsafe {
208        // phy_init_param_set(1);
209        // }
210
211        #[cfg(all(
212            phy_enable_usb,
213            any(soc_has_usb0, soc_has_usb_device),
214            not(any(esp32s2, esp32h2))
215        ))]
216        unsafe {
217            // FIXME: we should be using from esp-wifi-sys, but the function is missing for C6
218            // (CONFIG_ESP_PHY_ENABLE_USB is not defined)
219            unsafe extern "C" {
220                fn phy_bbpll_en_usb(param: bool);
221            }
222            phy_bbpll_en_usb(true);
223        }
224
225        let calibration_data_available = self.calibration_data.is_some();
226        let calibration_mode = if calibration_data_available {
227            // If the SOC just woke up from deep sleep and
228            // `phy_skip_calibration_after_deep_sleep` is enabled, no calibration will be
229            // performed.
230            if cfg!(phy_skip_calibration_after_deep_sleep) && is_reset_from_deepsleep() {
231                sys::include::esp_phy_calibration_mode_t_PHY_RF_CAL_NONE
232            } else if cfg!(phy_full_calibration) {
233                sys::include::esp_phy_calibration_mode_t_PHY_RF_CAL_FULL
234            } else {
235                sys::include::esp_phy_calibration_mode_t_PHY_RF_CAL_PARTIAL
236            }
237        } else {
238            sys::include::esp_phy_calibration_mode_t_PHY_RF_CAL_FULL
239        };
240        let init_data = &phy_init_data::PHY_INIT_DATA_DEFAULT;
241        unsafe {
242            self.calibration_result = sys::include::register_chipv7_phy(
243                init_data,
244                self.calibration_data() as *mut PhyCalibrationData as *mut _,
245                calibration_mode,
246            );
247        }
248        self.calibrated = true;
249    }
250
251    #[cfg(phy_backed_up_digital_register_count_is_set)]
252    /// Backup the digital PHY register into memory.
253    fn backup_digital_regs(&mut self) {
254        unsafe {
255            sys::include::phy_dig_reg_backup(
256                true,
257                self.phy_digital_register_backup.get_or_insert_default() as *mut u32,
258            );
259        }
260    }
261
262    #[cfg(phy_backed_up_digital_register_count_is_set)]
263    /// Restore the digital PHY registers from memory.
264    ///
265    /// This panics if the registers weren't previously backed up.
266    fn restore_digital_regs(&mut self) {
267        unsafe {
268            sys::include::phy_dig_reg_backup(
269                false,
270                self.phy_digital_register_backup
271                    .as_mut()
272                    .expect("Can't restore digital PHY registers from backup, without a backup.")
273                    as *mut u32,
274            );
275            self.phy_digital_register_backup = None;
276        }
277    }
278
279    /// Increase the number of references to the PHY.
280    ///
281    /// If the ref count was zero, the PHY will be initialized.
282    pub fn increase_ref_count(&mut self) {
283        if self.ref_count == 0 {
284            #[cfg(esp32)]
285            {
286                let now = Instant::now();
287                let delta = now - self.phy_clock_state_transition_timestamp;
288                self.phy_clock_state_transition_timestamp = now;
289                self.mac_clock_delta_since_last_call += delta;
290            }
291            if self.calibrated {
292                unsafe {
293                    sys::include::phy_wakeup_init();
294                }
295                #[cfg(phy_backed_up_digital_register_count_is_set)]
296                self.restore_digital_regs();
297            } else {
298                self.calibrate();
299                self.calibrated = true;
300            }
301        }
302        #[cfg(esp32)]
303        if let Some(cb) = self.mac_time_update_cb {
304            (cb)(self.mac_clock_delta_since_last_call);
305            self.mac_clock_delta_since_last_call = Duration::ZERO;
306        }
307
308        self.ref_count += 1;
309    }
310
311    /// Decrease the number of reference to the PHY.
312    ///
313    /// If the ref count hits zero, the PHY will be deinitialized.
314    ///
315    /// # Panics
316    /// This panics, if the PHY ref count is already at zero.
317    pub fn decrease_ref_count(&mut self) {
318        self.ref_count = self
319            .ref_count
320            .checked_sub(1)
321            .expect("PHY init ref count dropped below zero.");
322        if self.ref_count == 0 {
323            #[cfg(phy_backed_up_digital_register_count_is_set)]
324            self.backup_digital_regs();
325            unsafe {
326                // Disable PHY and RF.
327                sys::include::phy_close_rf();
328
329                // Power down PHY temperature sensor.
330                #[cfg(not(esp32))]
331                sys::include::phy_xpd_tsens();
332            }
333            #[cfg(esp32)]
334            {
335                self.phy_clock_state_transition_timestamp = Instant::now();
336            }
337            // The PHY clock guard will get released in the drop code of the PhyInitGuard. Note
338            // that this accepts a slight skewing of the delta, since the clock will be disabled
339            // after we record the value. This shouldn't be too bad though.
340        }
341    }
342}
343
344fn is_reset_from_deepsleep() -> bool {
345    // feature gated to avoid forgetting to double check the correct value for future chips
346    #[cfg(any(
347        esp32, esp32c2, esp32c3, esp32c5, esp32c6, esp32c61, esp32h2, esp32s2, esp32s3
348    ))]
349    const CORE_DEEP_SLEEP: u32 = 5;
350
351    unsafe extern "C" {
352        fn rtc_get_reset_reason(cpu_num: u32) -> u32;
353    }
354
355    let reason = unsafe { rtc_get_reset_reason(Cpu::current() as u32) };
356
357    reason == CORE_DEEP_SLEEP
358}
359
360/// Global PHY initialization state
361static PHY_STATE: NonReentrantMutex<PhyState> = NonReentrantMutex::new(PhyState::new());
362
363/// Prevents the PHY from being deinitialized.
364///
365/// As long as at least one [PhyInitGuard] exists, the PHY will remain initialized. To release this
366/// guard, you can either let it go out of scope, or use [PhyInitGuard::release] to explicitly
367/// release it.
368#[derive(Debug)]
369pub struct PhyInitGuard<'d> {
370    _phy_clock_guard: PhyClockGuard<'d>,
371}
372
373impl PhyInitGuard<'_> {
374    #[inline]
375    /// Release the init guard.
376    ///
377    /// The PHY will be disabled, if this is the last init guard.
378    pub fn release(self) {
379        // Runs the Drop implementation
380    }
381}
382
383impl Drop for PhyInitGuard<'_> {
384    fn drop(&mut self) {
385        PHY_STATE.with(|phy_state| phy_state.decrease_ref_count());
386    }
387}
388
389/// Enable the PHY.
390///
391/// If no other [PhyInitGuard] is currently alive, this will also initialize the PHY, which
392/// will involve a full RF calibration, unless you loaded previously backed up calibration
393/// data with [set_phy_calibration_data].
394pub fn enable_phy<'d>() -> PhyInitGuard<'d> {
395    // In esp-idf, this is done after calculating the MAC time delta, but it shouldn't make
396    // much of a difference.
397    let _phy_clock_guard = enable_phy_clock();
398
399    PHY_STATE.with(|phy_state| phy_state.increase_ref_count());
400
401    PhyInitGuard { _phy_clock_guard }
402}
403
404/// Manually disable the PHY.
405///
406/// This is only useful if you [core::mem::forget] the [PhyInitGuard].
407pub fn disable_phy() {
408    PHY_STATE.with(|phy_state| phy_state.decrease_ref_count());
409    // Balance the PhyClockGuard that was mem::forget'd with PhyInitGuard.
410    // Without this, PHY_CLOCK_REF_COUNTER (u8) leaks on every phy_enable/phy_disable
411    // cycle from the WiFi blob C-callback interface, overflowing after ~9 TCP connects.
412    decrease_phy_clock_ref_count_internal();
413}
414
415/// Enable the PHY clock and acquire a [PhyClockGuard].
416///
417/// The PHY clock will only be disabled once all [PhyClockGuard]s are dropped.
418pub fn enable_phy_clock<'d>() -> PhyClockGuard<'d> {
419    increase_phy_clock_ref_count_internal();
420    PhyClockGuard {
421        _phantom: PhantomData,
422    }
423}
424
425/// Set the MAC time update callback.
426///
427/// See [MacTimeUpdateCb] for details.
428#[cfg(esp32)]
429pub fn set_mac_time_update_cb(mac_time_update_cb: MacTimeUpdateCb) {
430    PHY_STATE.with(|phy_state| phy_state.mac_time_update_cb = Some(mac_time_update_cb));
431}
432
433#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
434#[cfg_attr(feature = "defmt", derive(defmt::Format))]
435/// Calibration data was already set.
436pub struct CalibrationDataAlreadySetError;
437
438#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
439#[cfg_attr(feature = "defmt", derive(defmt::Format))]
440/// No calibration data is available.
441pub struct NoCalibrationDataError;
442
443/// Result of the PHY calibration.
444#[derive(Debug, Clone, Copy)]
445#[cfg_attr(feature = "defmt", derive(defmt::Format))]
446#[non_exhaustive]
447pub enum CalibrationResult {
448    /// The calibration data was valid and was used for calibration.
449    Ok,
450
451    /// The calibration data checksum check failed, or the calibration data was outdated.
452    DataCheckFailed,
453}
454
455/// Load previously backed up PHY calibration data.
456pub fn set_phy_calibration_data(
457    calibration_data: &PhyCalibrationData,
458) -> Result<(), CalibrationDataAlreadySetError> {
459    PHY_STATE.with(|phy_state| {
460        if phy_state.calibration_data.is_some() {
461            Err(CalibrationDataAlreadySetError)
462        } else {
463            phy_state.calibration_data = Some(*calibration_data);
464            Ok(())
465        }
466    })
467}
468
469/// Backup the PHY calibration data to the provided slice.
470pub fn backup_phy_calibration_data(
471    buffer: &mut PhyCalibrationData,
472) -> Result<(), NoCalibrationDataError> {
473    PHY_STATE.with(|phy_state| {
474        phy_state
475            .calibration_data
476            .as_mut()
477            .ok_or(NoCalibrationDataError)
478            .map(|calibration_data| buffer.copy_from_slice(calibration_data.as_slice()))
479    })
480}
481
482/// Get the last calibration result.
483///
484/// This can be used to know if any previously persisted calibration data is outdated/invalid and
485/// needs to get updated.
486pub fn last_calibration_result() -> Option<CalibrationResult> {
487    PHY_STATE.with(|phy_state| {
488        if phy_state.calibrated {
489            Some(
490                if phy_state.calibration_result == sys::include::ESP_CAL_DATA_CHECK_FAIL as i32 {
491                    CalibrationResult::DataCheckFailed
492                } else {
493                    CalibrationResult::Ok
494                },
495            )
496        } else {
497            None
498        }
499    })
500}