esp_phy/
lib.rs

1//! PHY initialization handling for chips with a radio.
2//!
3//! # Usage
4//! ## Enabling and Disabling the PHY
5//! The [PhyController] trait is implemented for all modem peripherals (`WIFI`, `BT` and
6//! `IEEE802154`), that are present for a particular chip. See its documentation for further usage
7//! instructions.
8//!
9//! ## Backing Up and Restoring PHY Calibration Data
10//! If the PHY has already been calibrated, you can use [backup_phy_calibration_data] to persist
11//! calibration data elsewhere (e.g. in flash). Using [set_phy_calibration_data] you can restore
12//! previously persisted calibration data.
13//! ## Config Options
14#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_phy_config_table.md"))]
15//! ## Feature Flags
16#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
17#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
18#![no_std]
19
20// MUST be the first module
21mod fmt;
22
23#[cfg(esp32)]
24use esp_hal::time::{Duration, Instant};
25use esp_hal::{
26    clock::{ModemClockController, PhyClockGuard},
27    rtc_cntl::{SocResetReason, reset_reason},
28    system::Cpu,
29};
30use esp_sync::{NonReentrantMutex, RawMutex};
31use esp_wifi_sys::include::*;
32
33mod common_adapter;
34mod phy_init_data;
35
36pub(crate) mod private {
37    pub trait Sealed {}
38}
39
40/// Length of the PHY calibration data.
41pub const PHY_CALIBRATION_DATA_LENGTH: usize =
42    core::mem::size_of::<esp_wifi_sys::include::esp_phy_calibration_data_t>();
43
44/// Type alias for opaque calibration data.
45pub type PhyCalibrationData = [u8; PHY_CALIBRATION_DATA_LENGTH];
46
47#[cfg(phy_backed_up_digital_register_count_is_set)]
48type PhyDigRegsBackup =
49    [u32; esp_metadata_generated::property!("phy.backed_up_digital_register_count")];
50
51#[cfg(esp32)]
52/// Callback to update the MAC time.
53///
54/// The duration is the delta, that has been accumulated between the PHY clock and the normal
55/// system timers, since the last time this callback was called. This accounts for the PHY being
56/// enabled and disabled, before this callback was set.
57pub type MacTimeUpdateCb = fn(Duration);
58
59static ESP_PHY_LOCK: RawMutex = RawMutex::new();
60
61/// PHY initialization state
62struct PhyState {
63    /// Number of references to the PHY.
64    ref_count: usize,
65    /// The calibration data used for initialization.
66    ///
67    /// If this is [None], when `PhyController::enable_phy` is called, it will be initialized to
68    /// zero and a full calibration is performed.
69    calibration_data: Option<PhyCalibrationData>,
70    /// Has the PHY been calibrated since the chip was powered up.
71    calibrated: bool,
72
73    #[cfg(phy_backed_up_digital_register_count_is_set)]
74    /// Backup of the digital PHY registers.
75    phy_digital_register_backup: Option<PhyDigRegsBackup>,
76
77    // Chip specific.
78    #[cfg(esp32)]
79    /// Timestamp at which the modem clock domain state transitioned.
80    phy_clock_state_transition_timestamp: Instant,
81    #[cfg(esp32)]
82    /// The accumulated delta since the last time the callback was called.
83    mac_clock_delta_since_last_call: Duration,
84    #[cfg(esp32)]
85    /// Callback to update the MAC time.
86    mac_time_update_cb: Option<MacTimeUpdateCb>,
87}
88
89impl PhyState {
90    /// Initialize the PHY state.
91    pub const fn new() -> Self {
92        Self {
93            ref_count: 0,
94            calibration_data: None,
95            calibrated: false,
96
97            #[cfg(phy_backed_up_digital_register_count_is_set)]
98            phy_digital_register_backup: None,
99
100            #[cfg(esp32)]
101            phy_clock_state_transition_timestamp: Instant::EPOCH,
102            #[cfg(esp32)]
103            mac_clock_delta_since_last_call: Duration::ZERO,
104            #[cfg(esp32)]
105            mac_time_update_cb: None,
106        }
107    }
108
109    /// Get a reference to the calibration data.
110    ///
111    /// If no calibration data is available, it will be initialized to zero.
112    pub fn calibration_data(&mut self) -> &mut PhyCalibrationData {
113        self.calibration_data
114            .get_or_insert([0u8; PHY_CALIBRATION_DATA_LENGTH])
115    }
116
117    /// Calibrate the PHY.
118    fn calibrate(&mut self) {
119        #[cfg(esp32s2)]
120        unsafe {
121            use esp_hal::efuse::Efuse;
122            phy_eco_version_sel(Efuse::major_chip_version());
123        }
124        // Causes headaches for some reason.
125        // See: https://github.com/esp-rs/esp-hal/issues/4015
126        // #[cfg(phy_combo_module)]
127        // unsafe {
128        // phy_init_param_set(1);
129        // }
130
131        #[cfg(all(
132            phy_enable_usb,
133            any(soc_has_usb0, soc_has_usb_device),
134            not(any(esp32s2, esp32h2))
135        ))]
136        unsafe {
137            unsafe extern "C" {
138                fn phy_bbpll_en_usb(param: bool);
139            }
140            phy_bbpll_en_usb(true);
141        }
142
143        let calibration_data_available = self.calibration_data.is_some();
144        let calibration_mode = if calibration_data_available {
145            // If the SOC just woke up from deep sleep and
146            // `phy_skip_calibration_after_deep_sleep` is enabled, no calibration will be
147            // performed.
148            if cfg!(phy_skip_calibration_after_deep_sleep)
149                && reset_reason(Cpu::current()) == Some(SocResetReason::CoreDeepSleep)
150            {
151                esp_phy_calibration_mode_t_PHY_RF_CAL_NONE
152            } else if cfg!(phy_full_calibration) {
153                esp_phy_calibration_mode_t_PHY_RF_CAL_FULL
154            } else {
155                esp_phy_calibration_mode_t_PHY_RF_CAL_PARTIAL
156            }
157        } else {
158            esp_phy_calibration_mode_t_PHY_RF_CAL_FULL
159        };
160        let init_data = &phy_init_data::PHY_INIT_DATA_DEFAULT;
161        unsafe {
162            register_chipv7_phy(
163                init_data,
164                self.calibration_data() as *mut PhyCalibrationData as *mut _,
165                calibration_mode,
166            );
167        }
168        self.calibrated = true;
169    }
170
171    #[cfg(phy_backed_up_digital_register_count_is_set)]
172    /// Backup the digital PHY register into memory.
173    fn backup_digital_regs(&mut self) {
174        unsafe {
175            phy_dig_reg_backup(
176                true,
177                self.phy_digital_register_backup.get_or_insert_default() as *mut u32,
178            );
179        }
180    }
181
182    #[cfg(phy_backed_up_digital_register_count_is_set)]
183    /// Restore the digital PHY registers from memory.
184    ///
185    /// This panics if the registers weren't previously backed up.
186    fn restore_digital_regs(&mut self) {
187        unsafe {
188            phy_dig_reg_backup(
189                false,
190                self.phy_digital_register_backup
191                    .as_mut()
192                    .expect("Can't restore digital PHY registers from backup, without a backup.")
193                    as *mut u32,
194            );
195            self.phy_digital_register_backup = None;
196        }
197    }
198
199    /// Increase the number of references to the PHY.
200    ///
201    /// If the ref count was zero, the PHY will be initialized.
202    pub fn increase_ref_count(&mut self) {
203        if self.ref_count == 0 {
204            #[cfg(esp32)]
205            {
206                let now = Instant::now();
207                let delta = now - self.phy_clock_state_transition_timestamp;
208                self.phy_clock_state_transition_timestamp = now;
209                self.mac_clock_delta_since_last_call += delta;
210            }
211            if self.calibrated {
212                unsafe {
213                    phy_wakeup_init();
214                }
215                #[cfg(phy_backed_up_digital_register_count_is_set)]
216                self.restore_digital_regs();
217            } else {
218                self.calibrate();
219                self.calibrated = true;
220            }
221        }
222        #[cfg(esp32)]
223        if let Some(cb) = self.mac_time_update_cb {
224            (cb)(self.mac_clock_delta_since_last_call);
225            self.mac_clock_delta_since_last_call = Duration::ZERO;
226        }
227
228        self.ref_count += 1;
229    }
230
231    /// Decrease the number of reference to the PHY.
232    ///
233    /// If the ref count hits zero, the PHY will be deinitialized.
234    ///
235    /// # Panics
236    /// This panics, if the PHY ref count is already at zero.
237    pub fn decrease_ref_count(&mut self) {
238        self.ref_count = self
239            .ref_count
240            .checked_sub(1)
241            .expect("PHY init ref count dropped below zero.");
242        if self.ref_count == 0 {
243            #[cfg(phy_backed_up_digital_register_count_is_set)]
244            self.backup_digital_regs();
245            unsafe {
246                // Disable PHY and RF.
247                phy_close_rf();
248
249                // Power down PHY temperature sensor.
250                #[cfg(not(esp32))]
251                phy_xpd_tsens();
252            }
253            #[cfg(esp32)]
254            {
255                self.phy_clock_state_transition_timestamp = Instant::now();
256            }
257            // The PHY clock guard will get released in the drop code of the PhyInitGuard. Note
258            // that this accepts a slight skewing of the delta, since the clock will be disabled
259            // after we record the value. This shouldn't be too bad though.
260        }
261    }
262}
263
264/// Global PHY initialization state
265static PHY_STATE: NonReentrantMutex<PhyState> = NonReentrantMutex::new(PhyState::new());
266
267/// Prevents the PHY from being deinitialized.
268///
269/// As long as at least one [PhyInitGuard] exists, the PHY will remain initialized. To release this
270/// guard, you can either let it go out of scope, or use [PhyInitGuard::release] to explicitly
271/// release it.
272#[derive(Debug)]
273pub struct PhyInitGuard<'d> {
274    _phy_clock_guard: PhyClockGuard<'d>,
275}
276
277impl PhyInitGuard<'_> {
278    #[inline]
279    /// Release the init guard.
280    ///
281    /// The PHY will be disabled, if this is the last init guard.
282    pub fn release(self) {}
283}
284
285impl Drop for PhyInitGuard<'_> {
286    fn drop(&mut self) {
287        PHY_STATE.with(|phy_state| phy_state.decrease_ref_count());
288    }
289}
290
291/// Common functionality for controlling PHY initialization.
292pub trait PhyController<'d>: private::Sealed + ModemClockController<'d> {
293    fn enable_phy(&self) -> PhyInitGuard<'d> {
294        // In esp-idf, this is done after calculating the MAC time delta, but it shouldn't make
295        // much of a difference.
296        let _phy_clock_guard = self.enable_phy_clock();
297
298        PHY_STATE.with(|phy_state| phy_state.increase_ref_count());
299
300        PhyInitGuard { _phy_clock_guard }
301    }
302
303    /// Decreases the PHY init reference count for this modem ignoring
304    /// currently alive [PhyInitGuard]s.
305    ///
306    /// This will also decrease the PHY clock ref count.
307    /// # Panics
308    /// This function panics if the PHY is inactive. If the ref count is
309    /// lower than the number of alive [PhyInitGuard]s, dropping a guard can
310    /// now panic.
311    fn decrease_phy_ref_count(&self) {
312        PHY_STATE.with(|phy_state| phy_state.decrease_ref_count());
313        self.decrease_phy_clock_ref_count();
314    }
315}
316macro_rules! impl_phy_controller {
317    ($feature_gate:ident, $peripheral:tt) => {
318        #[cfg($feature_gate)]
319        impl private::Sealed for esp_hal::peripherals::$peripheral<'_> {}
320
321        #[cfg($feature_gate)]
322        impl<'d> PhyController<'d> for esp_hal::peripherals::$peripheral<'d> {}
323    };
324}
325impl_phy_controller!(wifi, WIFI);
326impl_phy_controller!(bt, BT);
327impl_phy_controller!(ieee802154, IEEE802154);
328
329#[cfg(esp32)]
330/// Trait providing MAC time functionality for the Wi-Fi peripheral.
331pub trait MacTimeExt {
332    /// Set the MAC time update callback.
333    ///
334    /// See [MacTimeUpdateCb] for details.
335    fn set_mac_time_update_cb(&self, mac_time_update_cb: MacTimeUpdateCb) {
336        PHY_STATE.with(|phy_state| phy_state.mac_time_update_cb = Some(mac_time_update_cb));
337    }
338}
339#[cfg(esp32)]
340impl MacTimeExt for esp_hal::peripherals::WIFI<'_> {}
341
342#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
343#[cfg_attr(feature = "defmt", derive(defmt::Format))]
344/// Calibration data was already set.
345pub struct CalibrationDataAlreadySetError;
346
347#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
348#[cfg_attr(feature = "defmt", derive(defmt::Format))]
349/// No calibration data is available.
350pub struct NoCalibrationDataError;
351
352/// Load previously backed up PHY calibration data.
353pub fn set_phy_calibration_data(
354    calibration_data: &PhyCalibrationData,
355) -> Result<(), CalibrationDataAlreadySetError> {
356    PHY_STATE.with(|phy_state| {
357        if phy_state.calibration_data.is_some() {
358            Err(CalibrationDataAlreadySetError)
359        } else {
360            phy_state.calibration_data = Some(*calibration_data);
361            Ok(())
362        }
363    })
364}
365
366/// Backup the PHY calibration data to the provided slice.
367pub fn backup_phy_calibration_data(
368    buffer: &mut PhyCalibrationData,
369) -> Result<(), NoCalibrationDataError> {
370    PHY_STATE.with(|phy_state| {
371        phy_state
372            .calibration_data
373            .as_mut()
374            .ok_or(NoCalibrationDataError)
375            .map(|calibration_data| buffer.copy_from_slice(calibration_data.as_slice()))
376    })
377}