Skip to main content

esp_hal/soc/esp32c61/
clocks.rs

1//! Clock tree definitions and implementations for ESP32-C61.
2//!
3//! Remarks:
4//! - Enabling a clock node assumes it has first been configured. Some fixed clock nodes don't need
5//!   to be configured.
6//! - Some information may be assumed, e.g. the possibility to disable watchdog timers before clock
7//!   configuration.
8//! - Internal RC oscillators (32K OSC_SLOW, 130k RC_SLOW and 20M RC_FAST) are not calibrated here,
9//!   this system can only give a rough estimate of their frequency. They can be calibrated
10//!   separately using a known crystal frequency.
11//! - Some of the SOC capabilities are not implemented: I2S external pad clock source, external 32k
12//!   oscillator, others.
13#![allow(dead_code, reason = "Some of this is bound to be unused")]
14#![allow(missing_docs, reason = "Experimental")]
15
16use crate::{
17    peripherals::{I2C_ANA_MST, LP_CLKRST, MODEM_LPCON, PCR, PMU},
18    soc::{regi2c, xtal32k},
19};
20
21define_clock_tree_types!();
22
23/// Clock configuration options.
24#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
25#[cfg_attr(feature = "defmt", derive(defmt::Format))]
26#[allow(
27    clippy::enum_variant_names,
28    reason = "MHz suffix indicates physical unit."
29)]
30#[non_exhaustive]
31pub enum CpuClock {
32    /// Default CPU clock
33    #[default]
34    _80MHz  = 80,
35
36    /// 160 MHz CPU clock
37    _160MHz = 160,
38}
39
40impl CpuClock {
41    const PRESET_80: ClockConfig = ClockConfig {
42        xtal_clk: None,
43        hp_root_clk: Some(HpRootClkConfig::PllF160m),
44        cpu_clk: Some(CpuClkConfig::new(1)),
45        ahb_clk: Some(AhbClkConfig::new(3)),
46        apb_clk: Some(ApbClkConfig::new(0)),
47        lp_fast_clk: Some(LpFastClkConfig::RcFast),
48        lp_slow_clk: Some(xtal32k::default_lp_slow_clk()),
49        ble_lp_clk: Some(BleLpClkConfig::Xtal),
50        crypto_clk: Some(CryptoClkConfig::PllF480m),
51        timg_calibration_clock: None,
52    };
53    const PRESET_160: ClockConfig = ClockConfig {
54        xtal_clk: None,
55        hp_root_clk: Some(HpRootClkConfig::PllF160m),
56        cpu_clk: Some(CpuClkConfig::new(0)),
57        ahb_clk: Some(AhbClkConfig::new(3)),
58        apb_clk: Some(ApbClkConfig::new(0)),
59        lp_fast_clk: Some(LpFastClkConfig::RcFast),
60        lp_slow_clk: Some(xtal32k::default_lp_slow_clk()),
61        ble_lp_clk: Some(BleLpClkConfig::Xtal),
62        crypto_clk: Some(CryptoClkConfig::PllF480m),
63        timg_calibration_clock: None,
64    };
65}
66
67impl From<CpuClock> for ClockConfig {
68    fn from(value: CpuClock) -> ClockConfig {
69        match value {
70            CpuClock::_80MHz => CpuClock::PRESET_80,
71            CpuClock::_160MHz => CpuClock::PRESET_160,
72        }
73    }
74}
75
76impl Default for ClockConfig {
77    fn default() -> Self {
78        Self::from(CpuClock::default())
79    }
80}
81
82impl ClockConfig {
83    pub(crate) fn try_get_preset(self) -> Option<CpuClock> {
84        match self {
85            v if v == CpuClock::PRESET_80 => Some(CpuClock::_80MHz),
86            v if v == CpuClock::PRESET_160 => Some(CpuClock::_160MHz),
87            _ => None,
88        }
89    }
90
91    pub(crate) fn configure(mut self, clocks: &mut ClockTree) {
92        if self.xtal_clk.is_none() {
93            self.xtal_clk = Some(XtalClkConfig::_40);
94        }
95
96        self.apply(clocks);
97    }
98}
99
100// XTAL_CLK
101
102fn configure_xtal_clk_impl(
103    _clocks: &mut ClockTree,
104    _old_config: Option<XtalClkConfig>,
105    _config: XtalClkConfig,
106) {
107    // Nothing to do here.
108}
109
110/// Configures and calibrates the BBPLL again.
111///
112/// The BBPLL loses its analog configuration while the PMU powers it down, for example in a light
113/// sleep. ESP-IDF configures it again before it uses the PLL after a wake.
114pub(crate) fn reconfigure_pll(clocks: &mut ClockTree) {
115    if clocks.pll_clk_refcount > 0 {
116        enable_pll_clk_impl(clocks, true);
117    }
118}
119
120fn enable_pll_clk_impl(_clocks: &mut ClockTree, en: bool) {
121    if en {
122        PMU::regs().imm_hp_ck_power().write(|w| {
123            w.tie_high_xpd_bb_i2c().set_bit();
124            w.tie_high_xpd_bbpll().set_bit();
125            w.tie_high_xpd_bbpll_i2c().set_bit()
126        });
127        PMU::regs()
128            .imm_hp_ck_power()
129            .write(|w| w.tie_high_global_bbpll_icg().set_bit());
130    } else {
131        PMU::regs()
132            .imm_hp_ck_power()
133            .write(|w| w.tie_low_global_bbpll_icg().set_bit());
134        PMU::regs().imm_hp_ck_power().write(|w| {
135            w.tie_low_xpd_bb_i2c().set_bit();
136            w.tie_low_xpd_bbpll().set_bit();
137            w.tie_low_xpd_bbpll_i2c().set_bit()
138        });
139
140        return;
141    }
142
143    // Digital part - The target SPLL is fixed to 480MHz, do nothing.
144
145    // Analog part
146    I2C_ANA_MST::regs().ana_conf0().modify(|_, w| {
147        w.bbpll_stop_force_high().clear_bit();
148        w.bbpll_stop_force_low().set_bit()
149    });
150
151    const DIV7_0: u8 = 8;
152    const DIV_REF: u8 = 0; // Do not divide reference clock
153    const DCHGP: u8 = 5;
154    const DBIAS: u8 = 3;
155    const HREF: u8 = 3;
156    const LREF: u8 = 1;
157
158    const I2C_BBPLL_OC_DCHGP_LSB: u32 = 4;
159    // const I2C_BBPLL_OC_DHREF_SEL_LSB: u32 = 4;
160    // const I2C_BBPLL_OC_DLREF_SEL_LSB: u32 = 6;
161
162    const I2C_BBPLL_LREF: u8 = (DCHGP << I2C_BBPLL_OC_DCHGP_LSB) | DIV_REF;
163
164    regi2c::I2C_BBPLL_OC_REF.write_reg(I2C_BBPLL_LREF);
165    regi2c::I2C_BBPLL_OC_DIV_REG.write_reg(DIV7_0);
166    regi2c::I2C_BBPLL_OC_DLREF_SEL.write_field(LREF);
167    regi2c::I2C_BBPLL_OC_DHREF_SEL.write_field(HREF);
168    regi2c::I2C_BBPLL_OC_VCO_DBIAS.write_field(DBIAS);
169
170    // WAIT CALIBRATION DONE
171    while I2C_ANA_MST::regs()
172        .ana_conf0()
173        .read()
174        .cal_done()
175        .bit_is_clear()
176    {}
177    crate::rom::ets_delay_us(10);
178
179    // BBPLL CALIBRATION STOP
180    I2C_ANA_MST::regs().ana_conf0().modify(|_, w| {
181        w.bbpll_stop_force_high().set_bit();
182        w.bbpll_stop_force_low().clear_bit()
183    });
184}
185
186// RC_FAST_CLK
187
188fn enable_rc_fast_clk_impl(_clocks: &mut ClockTree, en: bool) {
189    LP_CLKRST::regs()
190        .clk_to_hp()
191        .modify(|_, w| w.icg_hp_fosc().bit(en));
192}
193
194// XTAL32K_CLK
195
196#[cfg(use_xtal32k)]
197fn enable_xtal32k_clk_impl(_clocks: &mut ClockTree, en: bool) {
198    LP_CLKRST::regs()
199        .clk_to_hp()
200        .modify(|_, w| w.icg_hp_xtal32k().bit(en));
201}
202
203// OSC_SLOW_CLK
204
205fn enable_osc_slow_clk_impl(_clocks: &mut ClockTree, en: bool) {
206    LP_CLKRST::regs()
207        .clk_to_hp()
208        .modify(|_, w| w.icg_hp_osc32k().bit(en));
209}
210
211// RC_SLOW_CLK
212
213fn enable_rc_slow_clk_impl(_clocks: &mut ClockTree, en: bool) {
214    LP_CLKRST::regs()
215        .clk_to_hp()
216        .modify(|_, w| w.icg_hp_sosc().bit(en));
217}
218
219// PLL_F20M
220
221fn enable_pll_f20m_impl(_clocks: &mut ClockTree, en: bool) {
222    PCR::regs()
223        .pll_div_clk_en()
224        .modify(|_, w| w.pll_20m_clk_en().bit(en));
225}
226
227// PLL_F40M
228
229fn enable_pll_f40m_impl(_clocks: &mut ClockTree, en: bool) {
230    PCR::regs()
231        .pll_div_clk_en()
232        .modify(|_, w| w.pll_40m_clk_en().bit(en));
233}
234
235// PLL_F48M
236
237fn enable_pll_f48m_impl(_clocks: &mut ClockTree, en: bool) {
238    PCR::regs()
239        .pll_div_clk_en()
240        .modify(|_, w| w.pll_48m_clk_en().bit(en));
241}
242
243// PLL_F60M
244
245fn enable_pll_f60m_impl(_clocks: &mut ClockTree, en: bool) {
246    PCR::regs()
247        .pll_div_clk_en()
248        .modify(|_, w| w.pll_60m_clk_en().bit(en));
249}
250
251// PLL_F80M
252
253fn enable_pll_f80m_impl(_clocks: &mut ClockTree, en: bool) {
254    PCR::regs()
255        .pll_div_clk_en()
256        .modify(|_, w| w.pll_80m_clk_en().bit(en));
257}
258
259// PLL_F120M
260
261fn enable_pll_f120m_impl(_clocks: &mut ClockTree, en: bool) {
262    PCR::regs()
263        .pll_div_clk_en()
264        .modify(|_, w| w.pll_120m_clk_en().bit(en));
265}
266
267// PLL_F160M
268
269fn enable_pll_f160m_impl(_clocks: &mut ClockTree, en: bool) {
270    PCR::regs()
271        .pll_div_clk_en()
272        .modify(|_, w| w.pll_160m_clk_en().bit(en));
273}
274
275// HP_ROOT_CLK
276
277fn enable_hp_root_clk_impl(_clocks: &mut ClockTree, _en: bool) {
278    // Nothing to do here.
279}
280
281fn configure_hp_root_clk_impl(
282    _clocks: &mut ClockTree,
283    _old_config: Option<HpRootClkConfig>,
284    new_config: HpRootClkConfig,
285) {
286    // TODO: Limit AHB to ensure it's running at <= XTAL_CLK && CPU_CLK must be an integer multiple
287    // of AHB
288    PCR::regs().sysclk_conf().modify(|_, w| unsafe {
289        w.soc_clk_sel().bits(match new_config {
290            HpRootClkConfig::Xtal => 0,
291            HpRootClkConfig::RcFast => 1,
292            HpRootClkConfig::PllF160m => 2,
293        })
294    });
295
296    bus_clock_update();
297}
298
299/// Applies the new `soc_clk_sel`, `cpu_div_num` and `ahb_div_num` values.
300fn bus_clock_update() {
301    PCR::regs()
302        .bus_clk_update()
303        .write(|w| w.bus_clock_update().set_bit());
304    while PCR::regs()
305        .bus_clk_update()
306        .read()
307        .bus_clock_update()
308        .bit_is_set()
309    {}
310}
311
312// CPU_CLK
313
314fn enable_cpu_clk_impl(_clocks: &mut ClockTree, _en: bool) {
315    // Nothing to do here.
316}
317
318fn configure_cpu_clk_impl(
319    _clocks: &mut ClockTree,
320    _old_config: Option<CpuClkConfig>,
321    new_config: CpuClkConfig,
322) {
323    PCR::regs()
324        .cpu_freq_conf()
325        .modify(|_, w| unsafe { w.cpu_div_num().bits(new_config.divisor() as u8) });
326
327    bus_clock_update();
328}
329
330// AHB_CLK
331
332fn enable_ahb_clk_impl(_clocks: &mut ClockTree, _en: bool) {
333    // Nothing to do here.
334}
335
336fn configure_ahb_clk_impl(
337    _clocks: &mut ClockTree,
338    _old_config: Option<AhbClkConfig>,
339    new_config: AhbClkConfig,
340) {
341    PCR::regs()
342        .ahb_freq_conf()
343        .modify(|_, w| unsafe { w.ahb_div_num().bits(new_config.divisor() as u8) });
344
345    bus_clock_update();
346}
347
348// APB_CLK
349
350fn enable_apb_clk_impl(_clocks: &mut ClockTree, _en: bool) {
351    // Nothing to do here.
352}
353
354fn configure_apb_clk_impl(
355    _clocks: &mut ClockTree,
356    _old_config: Option<ApbClkConfig>,
357    new_config: ApbClkConfig,
358) {
359    PCR::regs()
360        .apb_freq_conf()
361        .modify(|_, w| unsafe { w.apb_div_num().bits(new_config.divisor() as u8) });
362}
363
364// XTAL_D2_CLK
365
366fn enable_xtal_d2_clk_impl(_clocks: &mut ClockTree, _en: bool) {
367    // Nothing to do here.
368}
369
370// LP_FAST_CLK
371
372fn enable_lp_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) {
373    // Nothing to do here.
374}
375
376fn configure_lp_fast_clk_impl(
377    _clocks: &mut ClockTree,
378    _old_config: Option<LpFastClkConfig>,
379    new_config: LpFastClkConfig,
380) {
381    LP_CLKRST::regs().lp_clk_conf().modify(|_, w| unsafe {
382        w.fast_clk_sel().bits(match new_config {
383            LpFastClkConfig::RcFast => 0,
384            LpFastClkConfig::XtalD2 => 1,
385            LpFastClkConfig::Xtal => 2,
386        })
387    });
388}
389
390// LP_SLOW_CLK
391
392fn enable_lp_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) {
393    // Nothing to do here.
394}
395
396fn configure_lp_slow_clk_impl(
397    _clocks: &mut ClockTree,
398    _old_config: Option<LpSlowClkConfig>,
399    new_config: LpSlowClkConfig,
400) {
401    LP_CLKRST::regs().lp_clk_conf().modify(|_, w| unsafe {
402        w.slow_clk_sel().bits(match new_config {
403            LpSlowClkConfig::RcSlow => 0,
404            #[cfg(use_xtal32k)]
405            LpSlowClkConfig::Xtal32k => 1,
406            // LpSlowClkConfig::Ext32k => 2,
407            LpSlowClkConfig::OscSlow => 3,
408        })
409    });
410}
411
412// CRYPTO_PLL_DIV
413
414fn enable_crypto_pll_div_impl(_clocks: &mut ClockTree, _en: bool) {
415    // The divider is fixed in hardware, there is nothing to gate.
416}
417
418// CRYPTO_CLK
419
420fn enable_crypto_clk_impl(_clocks: &mut ClockTree, _en: bool) {
421    // Nothing to do here.
422}
423
424fn configure_crypto_clk_impl(
425    _clocks: &mut ClockTree,
426    _old_config: Option<CryptoClkConfig>,
427    new_config: CryptoClkConfig,
428) {
429    PCR::regs().sec_conf().modify(|_, w| unsafe {
430        w.sec_clk_sel().bits(match new_config {
431            CryptoClkConfig::Xtal => 0,
432            CryptoClkConfig::RcFast => 1,
433            CryptoClkConfig::PllF480m => 2,
434        })
435    });
436}
437
438// TIMG_CALIBRATION_CLOCK
439
440fn enable_timg_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) {
441    // Nothing to do here.
442}
443
444fn configure_timg_calibration_clock_impl(
445    _clocks: &mut ClockTree,
446    _old_config: Option<TimgCalibrationClockConfig>,
447    new_config: TimgCalibrationClockConfig,
448) {
449    PCR::regs().ctrl_32k_conf().modify(|_, w| unsafe {
450        w._32k_sel().bits(match new_config {
451            TimgCalibrationClockConfig::OscSlowClk => 0,
452            #[cfg(use_xtal32k)]
453            TimgCalibrationClockConfig::Xtal32kClk => 1,
454            // TimgCalibrationClockConfig::Ext32kClk => 2,
455            TimgCalibrationClockConfig::RcSlowClk => 3,
456            TimgCalibrationClockConfig::RcFastDivClk => 4,
457        })
458    });
459}
460
461impl AdcInstance {
462    // ADC_FUNCTION_CLOCK
463
464    fn enable_function_clock_impl(self, _clocks: &mut ClockTree, en: bool) {
465        PCR::regs()
466            .saradc_clkm_conf()
467            .modify(|_, w| w.saradc_clkm_en().bit(en));
468    }
469
470    fn configure_function_clock_impl(
471        self,
472        _clocks: &mut ClockTree,
473        _old_config: Option<AdcFunctionClockConfig>,
474        new_config: AdcFunctionClockConfig,
475    ) {
476        PCR::regs().saradc_clkm_conf().modify(|_, w| unsafe {
477            w.saradc_clkm_sel().bits(match new_config {
478                AdcFunctionClockConfig::Xtal => 0,
479                AdcFunctionClockConfig::RcFast => 1,
480                AdcFunctionClockConfig::PllF80m => 2,
481            })
482        });
483    }
484}
485
486impl TimgInstance {
487    // TIMG_FUNCTION_CLOCK
488
489    // TIMG0_FUNCTION_CLOCK
490
491    fn enable_function_clock_impl(self, _clocks: &mut ClockTree, en: bool) {
492        let timg = match self {
493            TimgInstance::Timg0 => 0,
494            TimgInstance::Timg1 => 1,
495        };
496        PCR::regs()
497            .timergroup(timg)
498            .timer_clk_conf()
499            .modify(|_, w| w.timer_clk_en().bit(en));
500    }
501
502    fn configure_function_clock_impl(
503        self,
504        _clocks: &mut ClockTree,
505        _old_config: Option<TimgFunctionClockConfig>,
506        new_config: TimgFunctionClockConfig,
507    ) {
508        // TODO: add variants to PAC
509        let timg = match self {
510            TimgInstance::Timg0 => 0,
511            TimgInstance::Timg1 => 1,
512        };
513        PCR::regs()
514            .timergroup(timg)
515            .timer_clk_conf()
516            .modify(|_, w| unsafe {
517                w.timer_clk_sel().bits(match new_config {
518                    TimgFunctionClockConfig::XtalClk => 0,
519                    TimgFunctionClockConfig::RcFastClk => 1,
520                    TimgFunctionClockConfig::PllF80m => 2,
521                })
522            });
523    }
524
525    // TIMG_WDT_CLOCK
526
527    fn enable_wdt_clock_impl(self, _clocks: &mut ClockTree, en: bool) {
528        let timg = match self {
529            TimgInstance::Timg0 => 0,
530            TimgInstance::Timg1 => 1,
531        };
532        PCR::regs()
533            .timergroup(timg)
534            .wdt_clk_conf()
535            .modify(|_, w| w.wdt_clk_en().bit(en));
536    }
537
538    fn configure_wdt_clock_impl(
539        self,
540        _clocks: &mut ClockTree,
541        _old_config: Option<TimgWdtClockConfig>,
542        new_config: TimgWdtClockConfig,
543    ) {
544        let timg = match self {
545            TimgInstance::Timg0 => 0,
546            TimgInstance::Timg1 => 1,
547        };
548        PCR::regs()
549            .timergroup(timg)
550            .wdt_clk_conf()
551            .modify(|_, w| unsafe {
552                w.wdt_clk_sel().bits(match new_config {
553                    TimgWdtClockConfig::XtalClk => 0,
554                    TimgWdtClockConfig::RcFastClk => 1,
555                    TimgWdtClockConfig::PllF80m => 2,
556                })
557            });
558    }
559}
560
561// BLE_LP_XTAL_CLK
562
563fn enable_ble_lp_xtal_clk_impl(_clocks: &mut ClockTree, _en: bool) {
564    // Nothing to do.
565}
566
567// BLE_LP_RC_SLOW_CLK
568
569fn enable_ble_lp_rc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) {
570    // Nothing to do, divisor and gate are configured in BLE_LP_CLK
571    // (see modem_clock_select_lp_clock_source() in modem_clock.c).
572}
573
574// BLE_LP_CLK
575
576fn enable_ble_lp_clk_impl(_clocks: &mut ClockTree, en: bool) {
577    MODEM_LPCON::regs()
578        .clk_conf()
579        .modify(|_, w| w.clk_lp_timer_en().bit(en));
580}
581
582fn configure_ble_lp_clk_impl(
583    _clocks: &mut ClockTree,
584    _old_config: Option<BleLpClkConfig>,
585    new_config: BleLpClkConfig,
586) {
587    let divisor = match new_config {
588        BleLpClkConfig::Xtal => xtal_clk_frequency() / ble_lp_xtal_clk_frequency() - 1,
589        // Divided by 5 (div_num = 4), see esp_bt_rtc_slow_clk_select() in esp32c6/bt.c.
590        BleLpClkConfig::RcSlow => rc_slow_clk_frequency() / ble_lp_rc_slow_clk_frequency() - 1,
591        #[cfg(use_xtal32k)]
592        BleLpClkConfig::Xtal32k => 0,
593    };
594    let sel_xtal32k = cfg_select! {
595        use_xtal32k => new_config == BleLpClkConfig::Xtal32k,
596        _ => false,
597    };
598
599    MODEM_LPCON::regs()
600        .test_conf()
601        .modify(|_, w| w.clk_en().set_bit());
602
603    MODEM_LPCON::regs().lp_timer_conf().modify(|_, w| unsafe {
604        w.clk_lp_timer_sel_osc_slow()
605            .bit(new_config == BleLpClkConfig::RcSlow);
606        w.clk_lp_timer_sel_osc_fast().clear_bit();
607        w.clk_lp_timer_sel_xtal()
608            .bit(new_config == BleLpClkConfig::Xtal);
609        w.clk_lp_timer_sel_xtal32k().bit(sel_xtal32k);
610        w.clk_lp_timer_div_num().bits(divisor as u16)
611    });
612
613    if sel_xtal32k {
614        // 0 routes XTAL32K to the modem 32 kHz input.
615        MODEM_LPCON::regs()
616            .modem_32k_clk_conf()
617            .modify(|_, w| unsafe { w.clk_modem_32k_sel().bits(0) });
618    }
619}