Skip to main content

esp_hal/soc/esp32c2/
clocks.rs

1//! Clock tree definitions and implementations for ESP32-C2.
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 (136k RC_SLOW and 17.5M RC_FAST) are not calibrated here, this system
9//!   can only give a rough estimate of their frequency. They can be calibrated separately using a
10//!   known crystal frequency.
11//! - Some of the SOC capabilities are not implemented: using external 32K oscillator, divider for
12//!   RC_FAST and possibly more.
13#![allow(dead_code, reason = "Some of this is bound to be unused")]
14#![allow(missing_docs, reason = "Experimental")]
15
16// TODO: This is a temporary place for this, should probably be moved into clocks_ll.
17
18use esp_rom_sys::rom::{ets_delay_us, ets_update_cpu_frequency_rom};
19
20use crate::{
21    clock::RtcClock,
22    peripherals::{I2C_ANA_MST, LPWR, SYSTEM, TIMG0},
23    rtc_cntl::Rtc,
24    soc::regi2c,
25    time::Rate,
26};
27
28define_clock_tree_types!();
29
30/// Clock configuration options.
31#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
32#[cfg_attr(feature = "defmt", derive(defmt::Format))]
33#[allow(
34    clippy::enum_variant_names,
35    reason = "MHz suffix indicates physical unit."
36)]
37#[non_exhaustive]
38pub enum CpuClock {
39    /// 80 MHz CPU clock
40    #[default]
41    _80MHz  = 80,
42
43    /// 120 MHz CPU clock
44    _120MHz = 120,
45}
46
47impl CpuClock {
48    const PRESET_80: ClockConfig = ClockConfig {
49        xtal_clk: None,
50        system_pre_div: None,
51        cpu_pll_div: Some(CpuPllDivConfig::new(CpuPllDivDivisor::_6)),
52        cpu_clk: Some(CpuClkConfig::Pll),
53        rc_fast_clk_div_n: Some(RcFastClkDivNConfig::new(0)),
54        rtc_slow_clk: Some(RtcSlowClkConfig::RcSlow),
55        rtc_fast_clk: Some(RtcFastClkConfig::Rc),
56        low_power_clk: Some(LowPowerClkConfig::RtcSlow),
57        timg_calibration_clock: None,
58    };
59    const PRESET_120: ClockConfig = ClockConfig {
60        xtal_clk: None,
61        system_pre_div: None,
62        cpu_pll_div: Some(CpuPllDivConfig::new(CpuPllDivDivisor::_4)),
63        cpu_clk: Some(CpuClkConfig::Pll),
64        rc_fast_clk_div_n: Some(RcFastClkDivNConfig::new(0)),
65        rtc_slow_clk: Some(RtcSlowClkConfig::RcSlow),
66        rtc_fast_clk: Some(RtcFastClkConfig::Rc),
67        low_power_clk: Some(LowPowerClkConfig::RtcSlow),
68        timg_calibration_clock: None,
69    };
70}
71
72impl From<CpuClock> for ClockConfig {
73    fn from(value: CpuClock) -> ClockConfig {
74        match value {
75            CpuClock::_80MHz => CpuClock::PRESET_80,
76            CpuClock::_120MHz => CpuClock::PRESET_120,
77        }
78    }
79}
80
81impl Default for ClockConfig {
82    fn default() -> Self {
83        Self::from(CpuClock::default())
84    }
85}
86
87impl ClockConfig {
88    pub(crate) fn try_get_preset(self) -> Option<CpuClock> {
89        match self {
90            v if v == CpuClock::PRESET_80 => Some(CpuClock::_80MHz),
91            v if v == CpuClock::PRESET_120 => Some(CpuClock::_120MHz),
92            _ => None,
93        }
94    }
95
96    pub(crate) fn configure(mut self, clocks: &mut ClockTree) {
97        // Switch CPU to XTAL before reconfiguring PLL.
98        configure_xtal_clk(clocks, XtalClkConfig::_40);
99        configure_system_pre_div(clocks, SystemPreDivConfig::new(0));
100        configure_cpu_clk(clocks, CpuClkConfig::Xtal);
101
102        // Detect XTAL if unset.
103        // FIXME: this doesn't support running from RC_FAST_CLK. We should rework detection to
104        // only run when requesting XTAL.
105        if self.xtal_clk.is_none() {
106            // While the bootloader stores a crystal frequency in a retention register,
107            // that comes from a constant that we should not trust. If the user did not
108            // provide a crystal frequency, we should detect it.
109            let xtal = detect_xtal_freq(clocks);
110            debug!("Auto-detected XTAL frequency: {}", xtal.value());
111            self.xtal_clk = Some(xtal);
112        }
113
114        self.apply(clocks);
115    }
116}
117
118fn detect_xtal_freq(clocks: &mut ClockTree) -> XtalClkConfig {
119    // Estimate XTAL frequency using RC_FAST/256 as the calibration clock. RC_SLOW is too
120    // imprecise for reliable detection.
121    const CALIBRATION_CYCLES: u32 = 10;
122
123    // The digital path for RC_FAST_D256 must be enabled for TIMG calibration to work.
124    LPWR::regs()
125        .clk_conf()
126        .modify(|_, w| w.dig_clk8m_d256_en().set_bit());
127
128    let (xtal_cycles, calibration_clock_frequency) = RtcClock::measure_rtc_clock(
129        clocks,
130        TimgCalibrationClockConfig::RcFastDivClk,
131        TimgFunctionClockConfig::XtalClk,
132        CALIBRATION_CYCLES,
133    );
134
135    LPWR::regs()
136        .clk_conf()
137        .modify(|_, w| w.dig_clk8m_d256_en().clear_bit());
138
139    let mhz = (calibration_clock_frequency * xtal_cycles / CALIBRATION_CYCLES).as_mhz();
140
141    if mhz.abs_diff(40) < mhz.abs_diff(26) {
142        XtalClkConfig::_40
143    } else {
144        XtalClkConfig::_26
145    }
146}
147
148// XTAL_CLK
149
150fn configure_xtal_clk_impl(
151    _clocks: &mut ClockTree,
152    _old_config: Option<XtalClkConfig>,
153    config: XtalClkConfig,
154) {
155    // The stored configuration affects PLL settings instead. We save the value in a register
156    // similar to ESP-IDF, just in case something relies on that, or, if we can in the future read
157    // back the value instead of wasting RAM on it.
158
159    // Used by `rtc_clk_xtal_freq_get` patched ROM function.
160    let freq_mhz = config.value() / 1_000_000;
161    LPWR::regs().store4().modify(|r, w| unsafe {
162        // The data is stored in two copies of 16-bit values. The first bit overwrites the LSB of
163        // the frequency value with DISABLE_ROM_LOG.
164
165        // Copy the DISABLE_ROM_LOG bit
166        let disable_rom_log_bit = r.bits() & Rtc::RTC_DISABLE_ROM_LOG;
167        let half = (freq_mhz & (0xFFFF & !Rtc::RTC_DISABLE_ROM_LOG)) | disable_rom_log_bit;
168        w.data().bits(half | (half << 16))
169    });
170}
171
172// PLL_CLK
173
174fn enable_pll_clk_impl(clocks: &mut ClockTree, en: bool) {
175    const I2C_BBPLL_OC_DCHGP_LSB: u32 = 4;
176    const I2C_BBPLL_OC_DHREF_SEL_LSB: u32 = 4;
177    const I2C_BBPLL_OC_DLREF_SEL_LSB: u32 = 6;
178
179    // regi2c_ctrl_ll_i2c_bbpll_enable
180    I2C_ANA_MST::regs()
181        .ana_config()
182        .modify(|_, w| w.bbpll_pd().bit(!en));
183
184    LPWR::regs().options0().modify(|_, w| {
185        let power_down = !en;
186        w.bb_i2c_force_pd().bit(power_down);
187        w.bbpll_force_pd().bit(power_down);
188        w.bbpll_i2c_force_pd().bit(power_down)
189    });
190
191    if !en {
192        return;
193    }
194
195    // Digital part
196
197    SYSTEM::regs()
198        .cpu_per_conf()
199        .modify(|_, w| w.pll_freq_sel().set_bit()); // Undocumented, selects 480MHz PLL.
200
201    // Analog part
202
203    // Start BBPLL self-calibration
204    I2C_ANA_MST::regs().ana_conf0().modify(|_, w| {
205        w.bbpll_stop_force_high().clear_bit();
206        w.bbpll_stop_force_low().set_bit()
207    });
208
209    let div_ref: u8;
210    let div7_0: u8;
211    let dr1: u8;
212    let dr3: u8;
213    let dchgp: u8;
214    let dcur: u8;
215    let dbias: u8;
216    // Configure 480M PLL
217    match unwrap!(clocks.xtal_clk) {
218        XtalClkConfig::_26 => {
219            // Divide by 13 -> reference = 2MHz
220            div_ref = 12;
221            // Multiply by 236 + 4 -> PLL output = 480MHz
222            div7_0 = 236;
223            dr1 = 4;
224            dr3 = 4;
225            dchgp = 0;
226            dcur = 0;
227            dbias = 2;
228        }
229        XtalClkConfig::_40 => {
230            // Divide by 1 -> reference = 40MHz
231            div_ref = 0;
232            // Multiply by 8 + 4 -> PLL output = 480MHz
233            div7_0 = 8;
234            dr1 = 0;
235            dr3 = 0;
236            dchgp = 5;
237            dcur = 3;
238            dbias = 2;
239        }
240    }
241
242    regi2c::I2C_BBPLL_REG4.write_reg(0x6b);
243
244    let i2c_bbpll_lref = (dchgp << I2C_BBPLL_OC_DCHGP_LSB) | div_ref;
245    let i2c_bbpll_dcur =
246        (1 << I2C_BBPLL_OC_DLREF_SEL_LSB) | (3 << I2C_BBPLL_OC_DHREF_SEL_LSB) | dcur;
247
248    regi2c::I2C_BBPLL_OC_REF.write_reg(i2c_bbpll_lref);
249    regi2c::I2C_BBPLL_OC_DIV_REG.write_reg(div7_0);
250    regi2c::I2C_BBPLL_OC_DR1.write_field(dr1);
251    regi2c::I2C_BBPLL_OC_DR3.write_field(dr3);
252    regi2c::I2C_BBPLL_REG6.write_reg(i2c_bbpll_dcur);
253    regi2c::I2C_BBPLL_OC_VCO_DBIAS.write_field(dbias);
254
255    while I2C_ANA_MST::regs()
256        .ana_conf0()
257        .read()
258        .bbpll_cal_done()
259        .bit_is_clear()
260    {}
261
262    ets_delay_us(10);
263
264    // Stop BBPLL self-calibration
265    I2C_ANA_MST::regs().ana_conf0().modify(|_, w| {
266        w.bbpll_stop_force_high().set_bit();
267        w.bbpll_stop_force_low().clear_bit()
268    });
269}
270
271// RC_FAST_CLK
272
273fn enable_rc_fast_clk_impl(_clocks: &mut ClockTree, en: bool) {
274    // XPD_RC_OSCILLATOR exists but we'll manage that separately
275    const RTC_CNTL_FOSC_DFREQ_DEFAULT: u8 = 172;
276    LPWR::regs().clk_conf().modify(|_, w| {
277        // Confusing CK8M naming inherited from ESP32?
278
279        // CK8M_DFREQ value controls tuning of 8M clock.
280        unsafe { w.ck8m_dfreq().bits(RTC_CNTL_FOSC_DFREQ_DEFAULT) };
281
282        w.enb_ck8m().bit(!en);
283        w.dig_clk8m_en().bit(en); // digital system clock gate
284        // Do not force the clock either way.
285        w.ck8m_force_pd().clear_bit();
286        w.ck8m_force_pu().clear_bit()
287    });
288    LPWR::regs()
289        .timer1()
290        .modify(|_, w| unsafe { w.ck8m_wait().bits(if en { 5 } else { 20 }) });
291}
292
293// OSC_SLOW_CLK
294
295fn enable_osc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) {
296    // Nothing to do.
297}
298
299// RC_SLOW_CLK
300
301fn enable_rc_slow_clk_impl(_clocks: &mut ClockTree, en: bool) {
302    if !en {
303        return;
304    }
305
306    // SCK_DCAP value controls tuning of 136k clock. The higher the value of DCAP, the lower the
307    // frequency. There is no separate enable bit, just make sure the calibration value is set.
308    const RTC_CNTL_SCK_DCAP_DEFAULT: u8 = 255;
309    LPWR::regs()
310        .rtc_cntl()
311        .modify(|_, w| unsafe { w.sck_dcap().bits(RTC_CNTL_SCK_DCAP_DEFAULT) });
312
313    // Also configure the divider here to its usual value of 1.
314
315    // Updating the divider should be part of the RC_SLOW_CLK divider config:
316    let slow_clk_conf = LPWR::regs().slow_clk_conf();
317    // Invalidate
318    let new_value = slow_clk_conf.modify(|_, w| w.ana_clk_div_vld().clear_bit());
319    // Update divider
320    let new_value = slow_clk_conf.write(|w| unsafe {
321        w.bits(new_value);
322        w.ana_clk_div().bits(0)
323    });
324    // Re-synchronize
325    slow_clk_conf.write(|w| {
326        unsafe { w.bits(new_value) };
327        w.ana_clk_div_vld().set_bit()
328    });
329}
330
331// RC_FAST_DIV_CLK
332
333fn enable_rc_fast_div_clk_impl(_clocks: &mut ClockTree, en: bool) {
334    LPWR::regs()
335        .clk_conf()
336        .modify(|_, w| w.enb_ck8m_div().bit(!en));
337}
338
339// SYSTEM_PRE_DIV_IN
340
341// Not an actual MUX, used to allow configuring the DIV divider as one block.
342// Related to CPU clock source configuration.
343fn enable_system_pre_div_in_impl(_clocks: &mut ClockTree, _en: bool) {
344    // Nothing to do.
345}
346
347fn configure_system_pre_div_in_impl(
348    _clocks: &mut ClockTree,
349    _old_config: Option<SystemPreDivInConfig>,
350    _new_config: SystemPreDivInConfig,
351) {
352    // Nothing to do.
353}
354
355// SYSTEM_PRE_DIV
356
357fn enable_system_pre_div_impl(_clocks: &mut ClockTree, _en: bool) {
358    // Nothing to do.
359}
360
361fn configure_system_pre_div_impl(
362    _clocks: &mut ClockTree,
363    _old_config: Option<SystemPreDivConfig>,
364    new_config: SystemPreDivConfig,
365) {
366    SYSTEM::regs()
367        .sysclk_conf()
368        .modify(|_, w| unsafe { w.pre_div_cnt().bits(new_config.divisor() as u16 & 0x3FF) });
369}
370
371// CPU_PLL_DIV
372
373fn enable_cpu_pll_div_impl(_clocks: &mut ClockTree, _en: bool) {
374    // Nothing to do.
375}
376
377fn configure_cpu_pll_div_impl(
378    _clocks: &mut ClockTree,
379    _old_config: Option<CpuPllDivConfig>,
380    _new_config: CpuPllDivConfig,
381) {
382    // Nothing to do.
383}
384
385// APB_CLK
386
387fn enable_apb_clk_impl(_clocks: &mut ClockTree, _en: bool) {
388    // Nothing to do.
389}
390
391fn configure_apb_clk_impl(
392    _clocks: &mut ClockTree,
393    _old_config: Option<ApbClkConfig>,
394    _new_config: ApbClkConfig,
395) {
396    // Nothing to do.
397}
398
399// CRYPTO_CLK
400
401fn enable_crypto_clk_impl(_clocks: &mut ClockTree, _en: bool) {
402    // Nothing to do.
403}
404
405fn configure_crypto_clk_impl(
406    _clocks: &mut ClockTree,
407    _old_config: Option<CryptoClkConfig>,
408    _new_config: CryptoClkConfig,
409) {
410    // Nothing to do, determined by CPU clock.
411}
412
413// MSPI_CLK
414
415fn enable_mspi_clk_impl(_clocks: &mut ClockTree, _en: bool) {
416    // Nothing to do.
417}
418
419fn configure_mspi_clk_impl(
420    _clocks: &mut ClockTree,
421    _old_config: Option<MspiClkConfig>,
422    _new_config: MspiClkConfig,
423) {
424    // Nothing to do, determined by CPU clock.
425}
426
427// CPU_CLK
428
429fn configure_cpu_clk_impl(
430    clocks: &mut ClockTree,
431    _old_config: Option<CpuClkConfig>,
432    new_config: CpuClkConfig,
433) {
434    // Based on TRM Table 6.2-2
435    if new_config == CpuClkConfig::Pll {
436        SYSTEM::regs().cpu_per_conf().modify(|_, w| unsafe {
437            w.cpuperiod_sel()
438                .bits(match unwrap!(clocks.cpu_pll_div).divisor {
439                    CpuPllDivDivisor::_4 => 1,
440                    CpuPllDivDivisor::_6 => 0,
441                })
442        });
443    }
444
445    SYSTEM::regs().sysclk_conf().modify(|_, w| unsafe {
446        w.soc_clk_sel().bits(match new_config {
447            CpuClkConfig::Xtal => 0,
448            CpuClkConfig::RcFast => 2,
449            CpuClkConfig::Pll => 1,
450        })
451    });
452
453    let apb_freq = Rate::from_hz(apb_clk_frequency());
454    update_apb_frequency(apb_freq);
455
456    let cpu_freq = Rate::from_hz(cpu_clk_frequency());
457    ets_update_cpu_frequency_rom(cpu_freq.as_mhz());
458}
459
460fn update_apb_frequency(freq: Rate) {
461    let freq_shifted = (freq.as_hz() >> 12) & 0xFFFF;
462    let value = freq_shifted | (freq_shifted << 16);
463    LPWR::regs()
464        .store5()
465        .modify(|_, w| unsafe { w.data().bits(value) });
466}
467
468// PLL_40M
469
470fn enable_pll_40m_impl(_clocks: &mut ClockTree, _en: bool) {
471    // Nothing to do.
472}
473
474// PLL_60M
475
476fn enable_pll_60m_impl(_clocks: &mut ClockTree, _en: bool) {
477    // Nothing to do.
478}
479
480// PLL_80M
481
482fn enable_pll_80m_impl(_clocks: &mut ClockTree, _en: bool) {
483    // Nothing to do.
484}
485
486// CPU_DIV2
487
488fn enable_cpu_div2_impl(_clocks: &mut ClockTree, _en: bool) {
489    // Nothing to do.
490}
491
492// RC_FAST_CLK_DIV_N
493
494fn enable_rc_fast_clk_div_n_impl(_clocks: &mut ClockTree, _en: bool) {
495    // Nothing to do.
496}
497
498fn configure_rc_fast_clk_div_n_impl(
499    _clocks: &mut ClockTree,
500    _old_config: Option<RcFastClkDivNConfig>,
501    new_config: RcFastClkDivNConfig,
502) {
503    let clk_conf = LPWR::regs().clk_conf();
504    // Invalidate because we may be changing the divider from some other value
505    let new_value = clk_conf.modify(|_, w| w.ck8m_div_sel_vld().clear_bit());
506    // Update divider
507    let new_value = clk_conf.write(|w| unsafe {
508        w.bits(new_value);
509        w.ck8m_div_sel().bits(new_config.divisor() as u8)
510    });
511    // Re-synchronize
512    clk_conf.write(|w| {
513        unsafe { w.bits(new_value) };
514        w.ck8m_div_sel_vld().set_bit()
515    });
516}
517
518// XTAL_DIV_CLK
519
520fn enable_xtal_div_clk_impl(_clocks: &mut ClockTree, _en: bool) {
521    // Nothing to do.
522}
523
524// RTC_SLOW_CLK
525
526fn enable_rtc_slow_clk_impl(_clocks: &mut ClockTree, _en: bool) {
527    // Nothing to do.
528}
529
530fn configure_rtc_slow_clk_impl(
531    _clocks: &mut ClockTree,
532    _old_config: Option<RtcSlowClkConfig>,
533    new_config: RtcSlowClkConfig,
534) {
535    LPWR::regs().clk_conf().modify(|_, w| unsafe {
536        // TODO: variants should be in PAC
537        w.ana_clk_rtc_sel().bits(match new_config {
538            RtcSlowClkConfig::OscSlow => 1,
539            RtcSlowClkConfig::RcSlow => 0,
540            RtcSlowClkConfig::RcFast => 2,
541        })
542    });
543
544    ets_delay_us(300);
545}
546
547// RTC_FAST_CLK
548
549fn enable_rtc_fast_clk_impl(_clocks: &mut ClockTree, _en: bool) {
550    // Nothing to do.
551}
552
553fn configure_rtc_fast_clk_impl(
554    _clocks: &mut ClockTree,
555    _old_config: Option<RtcFastClkConfig>,
556    new_config: RtcFastClkConfig,
557) {
558    // TODO: variants should be fixed in PAC
559    LPWR::regs().clk_conf().modify(|_, w| match new_config {
560        RtcFastClkConfig::Xtal => w.fast_clk_rtc_sel().clear_bit(),
561        RtcFastClkConfig::Rc => w.fast_clk_rtc_sel().set_bit(),
562    });
563    ets_delay_us(3);
564}
565
566// LOW_POWER_CLK
567
568fn enable_low_power_clk_impl(_clocks: &mut ClockTree, _en: bool) {
569    // Nothing in esp-idf does this - is this managed by hardware, or the radio blobs?
570    // SYSTEM::regs()
571    //     .bt_lpck_div_frac()
572    //     .modify(|_, w| w.lpclk_rtc_en().bit(en));
573}
574
575fn configure_low_power_clk_impl(
576    _clocks: &mut ClockTree,
577    _old_config: Option<LowPowerClkConfig>,
578    new_config: LowPowerClkConfig,
579) {
580    SYSTEM::regs().bt_lpck_div_frac().modify(|_, w| {
581        w.lpclk_sel_8m()
582            .bit(new_config == LowPowerClkConfig::RcFast);
583        w.lpclk_sel_rtc_slow()
584            .bit(new_config == LowPowerClkConfig::RtcSlow);
585        w.lpclk_sel_xtal()
586            .bit(new_config == LowPowerClkConfig::Xtal);
587        w.lpclk_sel_xtal32k()
588            .bit(new_config == LowPowerClkConfig::OscSlow)
589    });
590}
591
592// UART_MEM_CLK
593
594fn enable_uart_mem_clk_impl(_clocks: &mut ClockTree, en: bool) {
595    // TODO: these functions (peripheral bus clock control) should be generated,
596    // replacing current PeripheralClockControl code.
597    // Enabling clock should probably not reset the peripheral.
598    let regs = SYSTEM::regs();
599
600    if en {
601        regs.perip_rst_en0()
602            .modify(|_, w| w.uart_mem_rst().bit(true));
603        regs.perip_rst_en0()
604            .modify(|_, w| w.uart_mem_rst().bit(false));
605    }
606
607    regs.perip_clk_en0()
608        .modify(|_, w| w.uart_mem_clk_en().bit(en));
609}
610
611// TIMG_CALIBRATION_CLOCK
612
613fn enable_timg_calibration_clock_impl(_clocks: &mut ClockTree, _en: bool) {
614    // Nothing to do, calibration clocks can only be selected. They are gated by the CALI_START
615    // bit, which is managed by the calibration process.
616}
617
618fn configure_timg_calibration_clock_impl(
619    _clocks: &mut ClockTree,
620    _old_config: Option<TimgCalibrationClockConfig>,
621    new_config: TimgCalibrationClockConfig,
622) {
623    TIMG0::regs().rtccalicfg().modify(|_, w| unsafe {
624        w.rtc_cali_clk_sel().bits(match new_config {
625            TimgCalibrationClockConfig::RcSlowClk => 0,
626            TimgCalibrationClockConfig::RcFastDivClk => 1,
627            TimgCalibrationClockConfig::Osc32kClk => 2,
628        })
629    });
630}
631
632impl TimgInstance {
633    // TIMG_FUNCTION_CLOCK
634
635    fn enable_function_clock_impl(self, _clocks: &mut ClockTree, en: bool) {
636        // TODO: should we model T0_DIVIDER, too?
637        TIMG0::regs()
638            .regclk()
639            .modify(|_, w| w.timer_clk_is_active().bit(en));
640    }
641
642    fn configure_function_clock_impl(
643        self,
644        _clocks: &mut ClockTree,
645        _old_config: Option<TimgFunctionClockConfig>,
646        new_config: TimgFunctionClockConfig,
647    ) {
648        TIMG0::regs().t(0).config().modify(|_, w| {
649            w.use_xtal()
650                .bit(new_config == TimgFunctionClockConfig::XtalClk)
651        });
652    }
653
654    // TIMG_WDT_CLOCK
655
656    fn enable_wdt_clock_impl(self, _clocks: &mut ClockTree, _en: bool) {
657        // No separate clock control enable bit.
658    }
659
660    fn configure_wdt_clock_impl(
661        self,
662        _clocks: &mut ClockTree,
663        _old_config: Option<TimgWdtClockConfig>,
664        new_config: TimgWdtClockConfig,
665    ) {
666        TIMG0::regs().wdtconfig0().modify(|_, w| {
667            w.wdt_use_xtal()
668                .bit(new_config == TimgWdtClockConfig::XtalClk)
669        });
670    }
671}