diff --git a/esp-hal/README.md b/esp-hal/README.md index 17ba134b35f..f1e67c8f288 100644 --- a/esp-hal/README.md +++ b/esp-hal/README.md @@ -75,7 +75,7 @@ For help getting started with this HAL, please refer to [The Rust on ESP Book] a | IOMUX | ⚒️ | ⚒️ | ⚒️ | | ⚒️ | ⚒️ | ⚒️ | ⚒️ | | Camera interface | ❌ | | | | | | ❌ | ⚒️ | | RGB display | ⚒️ | | | | | | ❌ | ⚒️ | -| LEDC | ⚒️ | ⚒️ | ⚒️ | | ⚒️ | ⚒️ | ⚒️ | ⚒️ | +| LEDC | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | | MCPWM | ⚒️ | | | | ⚒️ | ⚒️ | | ⚒️ | | PARL_IO | | | | | ⚒️ | ⚒️ | | | | PCNT | ⚒️ | | | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | diff --git a/esp-hal/src/ledc/channel.rs b/esp-hal/src/ledc/channel.rs index 9e94140f21d..609ec62c5d3 100644 --- a/esp-hal/src/ledc/channel.rs +++ b/esp-hal/src/ledc/channel.rs @@ -12,9 +12,7 @@ use super::timer::{TimerIFace, TimerSpeed}; use crate::{ gpio::{ - DriveMode, - OutputConfig, - OutputSignal, + DriveMode, OutputConfig, OutputSignal, interconnect::{self, PeripheralOutput}, }, pac::ledc::RegisterBlock, @@ -65,10 +63,10 @@ pub enum Number { Channel4 = 4, /// Channel 5 Channel5 = 5, - #[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32h2)))] + #[cfg(not(any(esp32c2, esp32c3, esp32c5, esp32c6, esp32h2)))] /// Channel 6 Channel6 = 6, - #[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32h2)))] + #[cfg(not(any(esp32c2, esp32c3, esp32c5, esp32c6, esp32h2)))] /// Channel 7 Channel7 = 7, } @@ -335,6 +333,40 @@ mod ehal1 { } impl Channel<'_, S> { + #[cfg(all(esp32c5, ledc_has_gamma_fade))] + const C5_GAMMA_RAM_BASE_OFFSET: usize = 0x400; + #[cfg(all(esp32c5, ledc_has_gamma_fade))] + const C5_GAMMA_RAM_CHANNEL_STRIDE: usize = 0x40; + #[cfg(all(esp32c5, ledc_has_gamma_fade))] + const C5_GAMMA_RANGE_COUNT: usize = 16; + + #[cfg(all(esp32c5, ledc_has_gamma_fade))] + fn c5_enable_gamma_ram_clock(&self) { + self.ledc.conf().modify(|_, w| match self.number { + Number::Channel0 => w.gamma_ram_clk_en_ch0().set_bit(), + Number::Channel1 => w.gamma_ram_clk_en_ch1().set_bit(), + Number::Channel2 => w.gamma_ram_clk_en_ch2().set_bit(), + Number::Channel3 => w.gamma_ram_clk_en_ch3().set_bit(), + Number::Channel4 => w.gamma_ram_clk_en_ch4().set_bit(), + Number::Channel5 => w.gamma_ram_clk_en_ch5().set_bit(), + }); + } + + #[cfg(all(esp32c5, ledc_has_gamma_fade))] + fn c5_write_gamma_range(&self, range: usize, value: u32) { + let offset = Self::C5_GAMMA_RAM_BASE_OFFSET + + (self.number as usize) * Self::C5_GAMMA_RAM_CHANNEL_STRIDE + + range * core::mem::size_of::(); + + let reg = (LEDC::PTR as *mut u8).wrapping_add(offset).cast::(); + + unsafe { + // SAFETY: `reg` is computed from the LEDC peripheral base and points to one + // of the documented gamma RAM entries for this channel/range. + reg.write_volatile(value); + } + } + #[cfg(esp32)] fn set_channel(&mut self, timer_number: u8) { if S::IS_HS { @@ -362,7 +394,7 @@ impl Channel<'_, S> { } // this is needed to make low duty-resolutions / high frequencies work - #[cfg(any(esp32h2, esp32c6))] + #[cfg(any(esp32c6, esp32h2))] self.ledc .ch_gamma_wr_addr(self.number as usize) .write(|w| unsafe { w.bits(0) }); @@ -412,7 +444,14 @@ impl Channel<'_, S> { } }); } - #[cfg(not(any(esp32, esp32c6, esp32h2)))] + #[cfg(all(esp32c5, ledc_has_gamma_fade))] + fn start_duty_without_fading(&self) { + self.ledc + .ch(self.number as usize) + .conf1() + .write(|w| w.duty_start().set_bit()); + } + #[cfg(not(any(esp32, esp32c5, esp32c6, esp32h2)))] fn start_duty_without_fading(&self) { self.ledc.ch(self.number as usize).conf1().write(|w| { w.duty_start().set_bit(); @@ -499,7 +538,35 @@ impl Channel<'_, S> { .write(|w| unsafe { w.ch_gamma_entry_num().bits(0x1) }); } - #[cfg(not(any(esp32, esp32c6, esp32h2)))] + #[cfg(all(esp32c5, ledc_has_gamma_fade))] + fn start_duty_fade_inner( + &self, + duty_inc: bool, + duty_steps: u16, + cycles_per_step: u16, + duty_per_cycle: u16, + ) { + let cnum = self.number as usize; + let range_cfg = u32::from(duty_inc) + | (u32::from(cycles_per_step) << 1) + | (u32::from(duty_per_cycle) << 11) + | (u32::from(duty_steps) << 21); + + self.c5_enable_gamma_ram_clock(); + self.c5_write_gamma_range(0, range_cfg); + for range in 1..Self::C5_GAMMA_RANGE_COUNT { + self.c5_write_gamma_range(range, 0); + } + self.ledc + .ch_gamma_conf(cnum) + .write(|w| unsafe { w.ch_gamma_entry_num().bits(0x1) }); + self.ledc + .ch(cnum) + .conf1() + .write(|w| w.duty_start().set_bit()); + } + + #[cfg(not(any(esp32, esp32c5, esp32c6, esp32h2)))] fn start_duty_fade_inner( &self, duty_inc: bool, @@ -583,9 +650,9 @@ where Number::Channel3 => OutputSignal::LEDC_LS_SIG3, Number::Channel4 => OutputSignal::LEDC_LS_SIG4, Number::Channel5 => OutputSignal::LEDC_LS_SIG5, - #[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32h2)))] + #[cfg(not(any(esp32c2, esp32c3, esp32c5, esp32c6, esp32h2)))] Number::Channel6 => OutputSignal::LEDC_LS_SIG6, - #[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32h2)))] + #[cfg(not(any(esp32c2, esp32c3, esp32c5, esp32c6, esp32h2)))] Number::Channel7 => OutputSignal::LEDC_LS_SIG7, } }; @@ -597,9 +664,9 @@ where Number::Channel3 => OutputSignal::LEDC_LS_SIG3, Number::Channel4 => OutputSignal::LEDC_LS_SIG4, Number::Channel5 => OutputSignal::LEDC_LS_SIG5, - #[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32h2)))] + #[cfg(not(any(esp32c2, esp32c3, esp32c5, esp32c6, esp32h2)))] Number::Channel6 => OutputSignal::LEDC_LS_SIG6, - #[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32h2)))] + #[cfg(not(any(esp32c2, esp32c3, esp32c5, esp32c6, esp32h2)))] Number::Channel7 => OutputSignal::LEDC_LS_SIG7, }; @@ -672,7 +739,31 @@ where } /// Start a duty-cycle fade HW - #[cfg(not(esp32))] + #[cfg(all(esp32c5, ledc_has_gamma_fade))] + fn start_duty_fade_hw( + &self, + start_duty: u32, + duty_inc: bool, + duty_steps: u16, + cycles_per_step: u16, + duty_per_cycle: u16, + ) { + self.ledc + .ch(self.number as usize) + .duty() + .write(|w| unsafe { w.duty().bits(start_duty << 4) }); + self.ledc + .int_clr() + .write(|w| w.duty_chng_end_ch(self.number as u8).clear_bit_by_one()); + self.ledc + .int_ena() + .modify(|_, w| w.duty_chng_end_ch(self.number as u8).set_bit()); + self.start_duty_fade_inner(duty_inc, duty_steps, cycles_per_step, duty_per_cycle); + self.update_channel(); + } + + /// Start a duty-cycle fade HW + #[cfg(not(any(esp32, esp32c5)))] fn start_duty_fade_hw( &self, start_duty: u32, @@ -702,7 +793,16 @@ where } } - #[cfg(not(esp32))] + #[cfg(all(esp32c5, ledc_has_gamma_fade))] + fn is_duty_fade_running_hw(&self) -> bool { + self.ledc + .int_st() + .read() + .duty_chng_end_ch(self.number as u8) + .bit_is_clear() + } + + #[cfg(not(any(esp32, esp32c5)))] fn is_duty_fade_running_hw(&self) -> bool { self.ledc .int_raw() diff --git a/esp-hal/src/ledc/mod.rs b/esp-hal/src/ledc/mod.rs index 7d06834b0ba..d3bd6aab55f 100644 --- a/esp-hal/src/ledc/mod.rs +++ b/esp-hal/src/ledc/mod.rs @@ -125,6 +125,11 @@ impl<'d> Ledc<'d> { PeripheralClockControl::reset(PeripheralEnable::Ledc); } + #[cfg(all(esp32c5, soc_has_pcr, ledc_has_gamma_fade))] + crate::peripherals::PCR::regs() + .ledc_pd_ctrl() + .modify(|_, w| w.ledc_mem_force_pd().clear_bit()); + let ledc = LEDC::regs(); Ledc { _instance, ledc } } @@ -142,24 +147,32 @@ impl<'d> Ledc<'d> { #[cfg(not(esp32))] /// Set global slow clock source pub fn set_global_slow_clock(&mut self, clock_source: LSGlobalClkSource) { - #[cfg(any(esp32c6, esp32h2))] - let pcr = unsafe { &*crate::peripherals::PCR::ptr() }; - - #[cfg(any(esp32c6, esp32h2))] - pcr.ledc_sclk_conf().write(|w| w.ledc_sclk_en().set_bit()); - match clock_source { LSGlobalClkSource::APBClk => { - #[cfg(not(any(esp32c6, esp32h2)))] - self.ledc - .conf() - .write(|w| unsafe { w.apb_clk_sel().bits(1) }); - #[cfg(esp32c6)] - pcr.ledc_sclk_conf() - .write(|w| unsafe { w.ledc_sclk_sel().bits(1) }); - #[cfg(esp32h2)] - pcr.ledc_sclk_conf() - .write(|w| unsafe { w.ledc_sclk_sel().bits(0) }); + cfg_if::cfg_if! { + if #[cfg(soc_has_clock_node_ledc_sclk)] { + crate::soc::clocks::ClockTree::with(|clocks| { + crate::soc::clocks::configure_ledc_sclk( + clocks, + crate::soc::clocks::LedcSclkConfig::PllF80m, + ); + }); + + #[cfg(soc_has_pcr)] + crate::peripherals::PCR::regs() + .ledc_sclk_conf() + .modify(|_, w| w.ledc_sclk_en().set_bit()); + } else if #[cfg(soc_has_pcr)] { + crate::peripherals::PCR::regs().ledc_sclk_conf().modify(|_, w| unsafe { + w.ledc_sclk_sel().bits(0); + w.ledc_sclk_en().set_bit() + }); + } else { + self.ledc + .conf() + .write(|w| unsafe { w.apb_clk_sel().bits(1) }); + } + } } } self.ledc diff --git a/esp-hal/src/ledc/timer.rs b/esp-hal/src/ledc/timer.rs index f8f11065911..4efc9a3c2d5 100644 --- a/esp-hal/src/ledc/timer.rs +++ b/esp-hal/src/ledc/timer.rs @@ -9,12 +9,15 @@ //! duty cycles and frequencies, making it ideal for Pulse-Width Modulation //! (PWM) applications and LED lighting control. //! -//! LEDC uses APB as clock source. +//! LEDC timer source clock is chip-specific. #[cfg(esp32)] use super::HighSpeed; use super::{LowSpeed, Speed}; -use crate::{clock::Clocks, pac, time::Rate}; +use crate::clock::Clocks; +#[cfg(soc_has_clock_node_ledc_sclk)] +use crate::soc::clocks::ClockTree; +use crate::{pac, time::Rate}; const LEDC_TIMER_DIV_NUM_MAX: u64 = 0x3FFFF; @@ -315,8 +318,21 @@ impl TimerHW for Timer<'_, LowSpeed> { fn freq_hw(&self) -> Option { self.clock_source.map(|source| match source { LSClockSource::APBClk => { - let clocks = Clocks::get(); - clocks.apb_clock + #[cfg(soc_has_clock_node_ledc_sclk)] + { + ClockTree::with(|clocks| { + if crate::soc::clocks::ledc_sclk_config(clocks).is_some() { + Rate::from_hz(crate::soc::clocks::ledc_sclk_frequency(clocks)) + } else { + Clocks::get().apb_clock + } + }) + } + #[cfg(not(soc_has_clock_node_ledc_sclk))] + { + let clocks = Clocks::get(); + clocks.apb_clock + } } }) } diff --git a/esp-hal/src/soc/esp32c5/clocks.rs b/esp-hal/src/soc/esp32c5/clocks.rs index 67a6c0031ba..bcd1336875e 100644 --- a/esp-hal/src/soc/esp32c5/clocks.rs +++ b/esp-hal/src/soc/esp32c5/clocks.rs @@ -32,7 +32,7 @@ define_clock_tree_types!(); pub enum CpuClock { /// 80 MHz CPU clock #[default] - _80MHz = 80, + _80MHz = 80, /// 160 MHz CPU clock _160MHz = 160, @@ -48,6 +48,7 @@ impl CpuClock { cpu_clk: Some(CpuClkConfig(1)), ahb_clk: Some(AhbClkConfig(3)), // 40MHz - cannot exceed XTAL_CLK apb_clk: Some(ApbClkConfig(0)), + ledc_sclk: Some(LedcSclkConfig::PllF80m), lp_fast_clk: Some(LpFastClkConfig::RcFast), lp_slow_clk: Some(LpSlowClkConfig::RcSlow), timg_calibration_clock: None, @@ -58,6 +59,7 @@ impl CpuClock { cpu_clk: Some(CpuClkConfig(0)), ahb_clk: Some(AhbClkConfig(3)), // 40MHz - cannot exceed XTAL_CLK apb_clk: Some(ApbClkConfig(0)), + ledc_sclk: Some(LedcSclkConfig::PllF80m), lp_fast_clk: Some(LpFastClkConfig::RcFast), lp_slow_clk: Some(LpSlowClkConfig::RcSlow), timg_calibration_clock: None, @@ -68,6 +70,7 @@ impl CpuClock { cpu_clk: Some(CpuClkConfig(0)), ahb_clk: Some(AhbClkConfig(5)), // 40MHz - cannot exceed XTAL_CLK apb_clk: Some(ApbClkConfig(0)), + ledc_sclk: Some(LedcSclkConfig::PllF80m), lp_fast_clk: Some(LpFastClkConfig::RcFast), lp_slow_clk: Some(LpSlowClkConfig::RcSlow), timg_calibration_clock: None, @@ -387,6 +390,28 @@ fn configure_apb_clk_impl(_clocks: &mut ClockTree, new_config: ApbClkConfig) { .modify(|_, w| unsafe { w.apb_div_num().bits(new_config.value() as u8) }); } +// LEDC_SCLK + +fn enable_ledc_sclk_impl(_clocks: &mut ClockTree, en: bool) { + PCR::regs() + .ledc_sclk_conf() + .modify(|_, w| w.ledc_sclk_en().bit(en)); +} + +fn configure_ledc_sclk_impl( + _clocks: &mut ClockTree, + _old_selector: Option, + new_selector: LedcSclkConfig, +) { + PCR::regs().ledc_sclk_conf().modify(|_, w| unsafe { + w.ledc_sclk_sel().bits(match new_selector { + LedcSclkConfig::XtalClk => 0, + LedcSclkConfig::RcFastClk => 1, + LedcSclkConfig::PllF80m => 2, + }) + }); +} + // XTAL_D2_CLK fn enable_xtal_d2_clk_impl(_clocks: &mut ClockTree, _en: bool) { diff --git a/esp-metadata-generated/src/_build_script_utils.rs b/esp-metadata-generated/src/_build_script_utils.rs index 0cce8397db7..67119725440 100644 --- a/esp-metadata-generated/src/_build_script_utils.rs +++ b/esp-metadata-generated/src/_build_script_utils.rs @@ -1668,6 +1668,7 @@ impl Chip { "soc_has_intpri", "soc_has_io_mux", "soc_has_keymng", + "soc_has_ledc", "soc_has_lp_ana", "soc_has_lp_aon", "soc_has_lp_apm0", @@ -1727,6 +1728,7 @@ impl Chip { "gpio_driver_supported", "dedicated_gpio_driver_supported", "interrupts_driver_supported", + "ledc_driver_supported", "pcnt_driver_supported", "spi_master_driver_supported", "spi_slave_driver_supported", @@ -1759,6 +1761,7 @@ impl Chip { "soc_has_clock_node_pll_f120m", "soc_has_clock_node_pll_f160m", "soc_has_clock_node_pll_f240m", + "soc_has_clock_node_ledc_sclk", "soc_has_clock_node_hp_root_clk", "soc_has_clock_node_cpu_clk", "soc_has_clock_node_ahb_clk", @@ -1790,6 +1793,7 @@ impl Chip { "gpio_output_signal_max=\"256\"", "interrupts_status_registers=\"3\"", "interrupt_controller=\"clic\"", + "ledc_has_gamma_fade", "spi_master_has_app_interrupts", "spi_master_has_dma_segmented_transfer", "spi_master_has_clk_pre_div", @@ -1823,6 +1827,7 @@ impl Chip { "cargo:rustc-cfg=soc_has_intpri", "cargo:rustc-cfg=soc_has_io_mux", "cargo:rustc-cfg=soc_has_keymng", + "cargo:rustc-cfg=soc_has_ledc", "cargo:rustc-cfg=soc_has_lp_ana", "cargo:rustc-cfg=soc_has_lp_aon", "cargo:rustc-cfg=soc_has_lp_apm0", @@ -1882,6 +1887,7 @@ impl Chip { "cargo:rustc-cfg=gpio_driver_supported", "cargo:rustc-cfg=dedicated_gpio_driver_supported", "cargo:rustc-cfg=interrupts_driver_supported", + "cargo:rustc-cfg=ledc_driver_supported", "cargo:rustc-cfg=pcnt_driver_supported", "cargo:rustc-cfg=spi_master_driver_supported", "cargo:rustc-cfg=spi_slave_driver_supported", @@ -1914,6 +1920,7 @@ impl Chip { "cargo:rustc-cfg=soc_has_clock_node_pll_f120m", "cargo:rustc-cfg=soc_has_clock_node_pll_f160m", "cargo:rustc-cfg=soc_has_clock_node_pll_f240m", + "cargo:rustc-cfg=soc_has_clock_node_ledc_sclk", "cargo:rustc-cfg=soc_has_clock_node_hp_root_clk", "cargo:rustc-cfg=soc_has_clock_node_cpu_clk", "cargo:rustc-cfg=soc_has_clock_node_ahb_clk", @@ -1945,6 +1952,7 @@ impl Chip { "cargo:rustc-cfg=gpio_output_signal_max=\"256\"", "cargo:rustc-cfg=interrupts_status_registers=\"3\"", "cargo:rustc-cfg=interrupt_controller=\"clic\"", + "cargo:rustc-cfg=ledc_has_gamma_fade", "cargo:rustc-cfg=spi_master_has_app_interrupts", "cargo:rustc-cfg=spi_master_has_dma_segmented_transfer", "cargo:rustc-cfg=spi_master_has_clk_pre_div", @@ -2291,6 +2299,7 @@ impl Chip { "lp_i2c_master_fifo_size=\"16\"", "interrupts_status_registers=\"3\"", "interrupt_controller=\"plic\"", + "ledc_has_gamma_fade", "rmt_ram_start=\"1610638336\"", "rmt_channel_ram_size=\"48\"", "rmt_has_tx_immediate_stop", @@ -2556,6 +2565,7 @@ impl Chip { "cargo:rustc-cfg=lp_i2c_master_fifo_size=\"16\"", "cargo:rustc-cfg=interrupts_status_registers=\"3\"", "cargo:rustc-cfg=interrupt_controller=\"plic\"", + "cargo:rustc-cfg=ledc_has_gamma_fade", "cargo:rustc-cfg=rmt_ram_start=\"1610638336\"", "cargo:rustc-cfg=rmt_channel_ram_size=\"48\"", "cargo:rustc-cfg=rmt_has_tx_immediate_stop", @@ -2933,6 +2943,7 @@ impl Chip { "i2c_master_fifo_size=\"32\"", "interrupts_status_registers=\"2\"", "interrupt_controller=\"plic\"", + "ledc_has_gamma_fade", "rmt_ram_start=\"1610642432\"", "rmt_channel_ram_size=\"48\"", "rmt_has_tx_immediate_stop", @@ -3159,6 +3170,7 @@ impl Chip { "cargo:rustc-cfg=i2c_master_fifo_size=\"32\"", "cargo:rustc-cfg=interrupts_status_registers=\"2\"", "cargo:rustc-cfg=interrupt_controller=\"plic\"", + "cargo:rustc-cfg=ledc_has_gamma_fade", "cargo:rustc-cfg=rmt_ram_start=\"1610642432\"", "cargo:rustc-cfg=rmt_channel_ram_size=\"48\"", "cargo:rustc-cfg=rmt_has_tx_immediate_stop", @@ -4981,6 +4993,7 @@ pub fn emit_check_cfg_directives() { println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_f120m)"); println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_f160m)"); println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_pll_f240m)"); + println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_ledc_sclk)"); println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_hp_root_clk)"); println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_ahb_clk)"); println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_xtal_d2_clk)"); @@ -4988,6 +5001,7 @@ pub fn emit_check_cfg_directives() { println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_lp_slow_clk)"); println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_timg_calibration_clock)"); println!("cargo:rustc-check-cfg=cfg(dma_separate_in_out_interrupts)"); + println!("cargo:rustc-check-cfg=cfg(ledc_has_gamma_fade)"); println!("cargo:rustc-check-cfg=cfg(spi_master_has_clk_pre_div)"); println!("cargo:rustc-check-cfg=cfg(uart_peripheral_controls_mem_clk)"); println!("cargo:rustc-check-cfg=cfg(esp32c6)"); @@ -5014,7 +5028,6 @@ pub fn emit_check_cfg_directives() { println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_mspi_fast_hs_clk)"); println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_mspi_fast_ls_clk)"); println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_mspi_fast_clk)"); - println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_ledc_sclk)"); println!("cargo:rustc-check-cfg=cfg(i2c_master_can_estimate_nack_reason)"); println!("cargo:rustc-check-cfg=cfg(i2c_master_has_reliable_fsm_reset)"); println!("cargo:rustc-check-cfg=cfg(rmt_has_tx_loop_auto_stop)"); diff --git a/esp-metadata-generated/src/_generated_esp32.rs b/esp-metadata-generated/src/_generated_esp32.rs index 8574d472490..207c653f818 100644 --- a/esp-metadata-generated/src/_generated_esp32.rs +++ b/esp-metadata-generated/src/_generated_esp32.rs @@ -171,6 +171,9 @@ macro_rules! property { ("interrupts.status_registers", str) => { stringify!(3) }; + ("ledc.has_gamma_fade") => { + false + }; ("rmt.ram_start") => { 1073047552 }; diff --git a/esp-metadata-generated/src/_generated_esp32c2.rs b/esp-metadata-generated/src/_generated_esp32c2.rs index 8e2993b1a04..f36a0863a5c 100644 --- a/esp-metadata-generated/src/_generated_esp32c2.rs +++ b/esp-metadata-generated/src/_generated_esp32c2.rs @@ -186,6 +186,9 @@ macro_rules! property { ("interrupts.disabled_interrupt") => { 0 }; + ("ledc.has_gamma_fade") => { + false + }; ("rng.apb_cycle_wait_num") => { 16 }; diff --git a/esp-metadata-generated/src/_generated_esp32c3.rs b/esp-metadata-generated/src/_generated_esp32c3.rs index 3a8e303bbb1..0d490b9a0c5 100644 --- a/esp-metadata-generated/src/_generated_esp32c3.rs +++ b/esp-metadata-generated/src/_generated_esp32c3.rs @@ -195,6 +195,9 @@ macro_rules! property { ("interrupts.disabled_interrupt") => { 0 }; + ("ledc.has_gamma_fade") => { + false + }; ("rmt.ram_start") => { 1610703872 }; diff --git a/esp-metadata-generated/src/_generated_esp32c5.rs b/esp-metadata-generated/src/_generated_esp32c5.rs index 98794a54530..653b9b0f454 100644 --- a/esp-metadata-generated/src/_generated_esp32c5.rs +++ b/esp-metadata-generated/src/_generated_esp32c5.rs @@ -138,6 +138,9 @@ macro_rules! property { ("interrupts.disabled_interrupt") => { 0 }; + ("ledc.has_gamma_fade") => { + true + }; ("spi_master.supports_dma") => { false }; @@ -265,6 +268,20 @@ macro_rules! property { /// todo!() /// } /// +/// // LEDC_SCLK +/// +/// fn enable_ledc_sclk_impl(_clocks: &mut ClockTree, _en: bool) { +/// todo!() +/// } +/// +/// fn configure_ledc_sclk_impl( +/// _clocks: &mut ClockTree, +/// _old_selector: Option, +/// _new_selector: LedcSclkConfig, +/// ) { +/// todo!() +/// } +/// /// // HP_ROOT_CLK /// /// fn enable_hp_root_clk_impl(_clocks: &mut ClockTree, _en: bool) { @@ -460,6 +477,17 @@ macro_rules! define_clock_tree_types { } } } + /// The list of clock signals that the `LEDC_SCLK` multiplexer can output. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum LedcSclkConfig { + /// Selects `PLL_F80M`. + PllF80m, + /// Selects `RC_FAST_CLK`. + RcFastClk, + /// Selects `XTAL_CLK`. + XtalClk, + } /// The list of clock signals that the `HP_ROOT_CLK` multiplexer can output. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -619,6 +647,7 @@ macro_rules! define_clock_tree_types { /// Represents the device's clock tree. pub struct ClockTree { xtal_clk: Option, + ledc_sclk: Option, hp_root_clk: Option, cpu_clk: Option, ahb_clk: Option, @@ -644,6 +673,7 @@ macro_rules! define_clock_tree_types { pll_f60m_refcount: u32, pll_f80m_refcount: u32, pll_f120m_refcount: u32, + ledc_sclk_refcount: u32, hp_root_clk_refcount: u32, cpu_clk_refcount: u32, apb_clk_refcount: u32, @@ -666,6 +696,10 @@ macro_rules! define_clock_tree_types { pub fn xtal_clk(&self) -> Option { self.xtal_clk } + /// Returns the current configuration of the LEDC_SCLK clock tree node + pub fn ledc_sclk(&self) -> Option { + self.ledc_sclk + } /// Returns the current configuration of the HP_ROOT_CLK clock tree node pub fn hp_root_clk(&self) -> Option { self.hp_root_clk @@ -722,6 +756,7 @@ macro_rules! define_clock_tree_types { static CLOCK_TREE: ::esp_sync::NonReentrantMutex = ::esp_sync::NonReentrantMutex::new(ClockTree { xtal_clk: None, + ledc_sclk: None, hp_root_clk: None, cpu_clk: None, ahb_clk: None, @@ -747,6 +782,7 @@ macro_rules! define_clock_tree_types { pll_f60m_refcount: 0, pll_f80m_refcount: 0, pll_f120m_refcount: 0, + ledc_sclk_refcount: 0, hp_root_clk_refcount: 0, cpu_clk_refcount: 0, apb_clk_refcount: 0, @@ -1022,6 +1058,60 @@ macro_rules! define_clock_tree_types { pub fn pll_f240m_frequency(clocks: &mut ClockTree) -> u32 { (pll_clk_frequency(clocks) / 2) } + pub fn configure_ledc_sclk(clocks: &mut ClockTree, new_selector: LedcSclkConfig) { + let old_selector = clocks.ledc_sclk.replace(new_selector); + if clocks.ledc_sclk_refcount > 0 { + match new_selector { + LedcSclkConfig::PllF80m => request_pll_f80m(clocks), + LedcSclkConfig::RcFastClk => request_rc_fast_clk(clocks), + LedcSclkConfig::XtalClk => request_xtal_clk(clocks), + } + configure_ledc_sclk_impl(clocks, old_selector, new_selector); + if let Some(old_selector) = old_selector { + match old_selector { + LedcSclkConfig::PllF80m => release_pll_f80m(clocks), + LedcSclkConfig::RcFastClk => release_rc_fast_clk(clocks), + LedcSclkConfig::XtalClk => release_xtal_clk(clocks), + } + } + } else { + configure_ledc_sclk_impl(clocks, old_selector, new_selector); + } + } + pub fn ledc_sclk_config(clocks: &mut ClockTree) -> Option { + clocks.ledc_sclk + } + pub fn request_ledc_sclk(clocks: &mut ClockTree) { + trace!("Requesting LEDC_SCLK"); + if increment_reference_count(&mut clocks.ledc_sclk_refcount) { + trace!("Enabling LEDC_SCLK"); + match unwrap!(clocks.ledc_sclk) { + LedcSclkConfig::PllF80m => request_pll_f80m(clocks), + LedcSclkConfig::RcFastClk => request_rc_fast_clk(clocks), + LedcSclkConfig::XtalClk => request_xtal_clk(clocks), + } + enable_ledc_sclk_impl(clocks, true); + } + } + pub fn release_ledc_sclk(clocks: &mut ClockTree) { + trace!("Releasing LEDC_SCLK"); + if decrement_reference_count(&mut clocks.ledc_sclk_refcount) { + trace!("Disabling LEDC_SCLK"); + enable_ledc_sclk_impl(clocks, false); + match unwrap!(clocks.ledc_sclk) { + LedcSclkConfig::PllF80m => release_pll_f80m(clocks), + LedcSclkConfig::RcFastClk => release_rc_fast_clk(clocks), + LedcSclkConfig::XtalClk => release_xtal_clk(clocks), + } + } + } + pub fn ledc_sclk_frequency(clocks: &mut ClockTree) -> u32 { + match unwrap!(clocks.ledc_sclk) { + LedcSclkConfig::PllF80m => pll_f80m_frequency(clocks), + LedcSclkConfig::RcFastClk => rc_fast_clk_frequency(clocks), + LedcSclkConfig::XtalClk => xtal_clk_frequency(clocks), + } + } pub fn configure_hp_root_clk(clocks: &mut ClockTree, new_selector: HpRootClkConfig) { let old_selector = clocks.hp_root_clk.replace(new_selector); if clocks.hp_root_clk_refcount > 0 { @@ -1705,6 +1795,8 @@ macro_rules! define_clock_tree_types { pub struct ClockConfig { /// `XTAL_CLK` configuration. pub xtal_clk: Option, + /// `LEDC_SCLK` configuration. + pub ledc_sclk: Option, /// `HP_ROOT_CLK` configuration. pub hp_root_clk: Option, /// `CPU_CLK` configuration. @@ -1726,6 +1818,9 @@ macro_rules! define_clock_tree_types { if let Some(config) = self.xtal_clk { configure_xtal_clk(clocks, config); } + if let Some(config) = self.ledc_sclk { + configure_ledc_sclk(clocks, config); + } if let Some(config) = self.hp_root_clk { configure_hp_root_clk(clocks, config); } @@ -1776,6 +1871,8 @@ macro_rules! implement_peripheral_clocks { pub enum Peripheral { /// DMA peripheral clock signal Dma, + /// LEDC peripheral clock signal + Ledc, /// PCNT peripheral clock signal Pcnt, /// SPI2 peripheral clock signal @@ -1797,6 +1894,7 @@ macro_rules! implement_peripheral_clocks { const COUNT: usize = Self::ALL.len(); const ALL: &[Self] = &[ Self::Dma, + Self::Ledc, Self::Pcnt, Self::Spi2, Self::Systimer, @@ -1813,6 +1911,11 @@ macro_rules! implement_peripheral_clocks { .gdma_conf() .modify(|_, w| w.gdma_clk_en().bit(enable)); } + Peripheral::Ledc => { + crate::peripherals::SYSTEM::regs() + .ledc_conf() + .modify(|_, w| w.ledc_clk_en().bit(enable)); + } Peripheral::Pcnt => { crate::peripherals::SYSTEM::regs() .pcnt_conf() @@ -1865,6 +1968,11 @@ macro_rules! implement_peripheral_clocks { .gdma_conf() .modify(|_, w| w.gdma_rst_en().bit(reset)); } + Peripheral::Ledc => { + crate::peripherals::SYSTEM::regs() + .ledc_conf() + .modify(|_, w| w.ledc_rst_en().bit(reset)); + } Peripheral::Pcnt => { crate::peripherals::SYSTEM::regs() .pcnt_conf() @@ -2301,12 +2409,14 @@ macro_rules! for_each_peripheral { _for_each_inner_peripheral!((@ peri_type #[doc = "IO_MUX peripheral singleton"] IO_MUX <= IO_MUX() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = "KEYMNG peripheral singleton"] KEYMNG <= KEYMNG() (unstable))); - _for_each_inner_peripheral!((@ peri_type #[doc = "LP_ANA peripheral singleton"] - LP_ANA <= LP_ANA() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = - "LP_AON peripheral singleton"] LP_AON <= LP_AON() (unstable))); - _for_each_inner_peripheral!((@ peri_type #[doc = "LP_APM0 peripheral singleton"] - LP_APM0 <= LP_APM0() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc - = "LP_CLKRST peripheral singleton"] LP_CLKRST <= LP_CLKRST() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LEDC peripheral singleton"] + LEDC <= LEDC() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "LP_ANA peripheral singleton"] LP_ANA <= LP_ANA() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = "LP_AON peripheral singleton"] + LP_AON <= LP_AON() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = + "LP_APM0 peripheral singleton"] LP_APM0 <= LP_APM0() (unstable))); + _for_each_inner_peripheral!((@ peri_type #[doc = + "LP_CLKRST peripheral singleton"] LP_CLKRST <= LP_CLKRST() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = "LP_I2C0 peripheral singleton"] LP_I2C0 <= LP_I2C0() (unstable))); _for_each_inner_peripheral!((@ peri_type #[doc = "LP_I2C_ANA_MST peripheral singleton"] LP_I2C_ANA_MST <= LP_I2C_ANA_MST() @@ -2421,6 +2531,7 @@ macro_rules! for_each_peripheral { _for_each_inner_peripheral!((INTPRI(unstable))); _for_each_inner_peripheral!((IO_MUX(unstable))); _for_each_inner_peripheral!((KEYMNG(unstable))); + _for_each_inner_peripheral!((LEDC(unstable))); _for_each_inner_peripheral!((LP_ANA(unstable))); _for_each_inner_peripheral!((LP_AON(unstable))); _for_each_inner_peripheral!((LP_APM0(unstable))); @@ -2590,30 +2701,30 @@ macro_rules! for_each_peripheral { (unstable)), (@ peri_type #[doc = "INTPRI peripheral singleton"] INTPRI <= INTPRI() (unstable)), (@ peri_type #[doc = "IO_MUX peripheral singleton"] IO_MUX <= IO_MUX() (unstable)), (@ peri_type #[doc = "KEYMNG peripheral singleton"] - KEYMNG <= KEYMNG() (unstable)), (@ peri_type #[doc = - "LP_ANA peripheral singleton"] LP_ANA <= LP_ANA() (unstable)), (@ peri_type #[doc - = "LP_AON peripheral singleton"] LP_AON <= LP_AON() (unstable)), (@ peri_type - #[doc = "LP_APM0 peripheral singleton"] LP_APM0 <= LP_APM0() (unstable)), (@ - peri_type #[doc = "LP_CLKRST peripheral singleton"] LP_CLKRST <= LP_CLKRST() - (unstable)), (@ peri_type #[doc = "LP_I2C0 peripheral singleton"] LP_I2C0 <= - LP_I2C0() (unstable)), (@ peri_type #[doc = - "LP_I2C_ANA_MST peripheral singleton"] LP_I2C_ANA_MST <= LP_I2C_ANA_MST() - (unstable)), (@ peri_type #[doc = "LP_IO_MUX peripheral singleton"] LP_IO_MUX <= - LP_IO_MUX() (unstable)), (@ peri_type #[doc = "LP_PERI peripheral singleton"] - LP_PERI <= LPPERI() (unstable)), (@ peri_type #[doc = - "LP_TEE peripheral singleton"] LP_TEE <= LP_TEE() (unstable)), (@ peri_type #[doc - = "LP_TIMER peripheral singleton"] LP_TIMER <= LP_TIMER() (unstable)), (@ - peri_type #[doc = "LP_UART peripheral singleton"] LP_UART <= LP_UART() - (unstable)), (@ peri_type #[doc = "LP_WDT peripheral singleton"] LP_WDT <= - LP_WDT() (unstable)), (@ peri_type #[doc = "LPWR peripheral singleton"] LPWR <= - LP_CLKRST() (unstable)), (@ peri_type #[doc = "MCPWM0 peripheral singleton"] - MCPWM0 <= MCPWM0() (unstable)), (@ peri_type #[doc = - "MEM_MONITOR peripheral singleton"] MEM_MONITOR <= MEM_MONITOR() (unstable)), (@ - peri_type #[doc = "MODEM_LPCON peripheral singleton"] MODEM_LPCON <= - MODEM_LPCON() (unstable)), (@ peri_type #[doc = - "MODEM_SYSCON peripheral singleton"] MODEM_SYSCON <= MODEM_SYSCON() (unstable)), - (@ peri_type #[doc = "PARL_IO peripheral singleton"] PARL_IO <= - PARL_IO(PARL_IO_RX : { bind_rx_interrupt, enable_rx_interrupt, + KEYMNG <= KEYMNG() (unstable)), (@ peri_type #[doc = "LEDC peripheral singleton"] + LEDC <= LEDC() (unstable)), (@ peri_type #[doc = "LP_ANA peripheral singleton"] + LP_ANA <= LP_ANA() (unstable)), (@ peri_type #[doc = + "LP_AON peripheral singleton"] LP_AON <= LP_AON() (unstable)), (@ peri_type #[doc + = "LP_APM0 peripheral singleton"] LP_APM0 <= LP_APM0() (unstable)), (@ peri_type + #[doc = "LP_CLKRST peripheral singleton"] LP_CLKRST <= LP_CLKRST() (unstable)), + (@ peri_type #[doc = "LP_I2C0 peripheral singleton"] LP_I2C0 <= LP_I2C0() + (unstable)), (@ peri_type #[doc = "LP_I2C_ANA_MST peripheral singleton"] + LP_I2C_ANA_MST <= LP_I2C_ANA_MST() (unstable)), (@ peri_type #[doc = + "LP_IO_MUX peripheral singleton"] LP_IO_MUX <= LP_IO_MUX() (unstable)), (@ + peri_type #[doc = "LP_PERI peripheral singleton"] LP_PERI <= LPPERI() + (unstable)), (@ peri_type #[doc = "LP_TEE peripheral singleton"] LP_TEE <= + LP_TEE() (unstable)), (@ peri_type #[doc = "LP_TIMER peripheral singleton"] + LP_TIMER <= LP_TIMER() (unstable)), (@ peri_type #[doc = + "LP_UART peripheral singleton"] LP_UART <= LP_UART() (unstable)), (@ peri_type + #[doc = "LP_WDT peripheral singleton"] LP_WDT <= LP_WDT() (unstable)), (@ + peri_type #[doc = "LPWR peripheral singleton"] LPWR <= LP_CLKRST() (unstable)), + (@ peri_type #[doc = "MCPWM0 peripheral singleton"] MCPWM0 <= MCPWM0() + (unstable)), (@ peri_type #[doc = "MEM_MONITOR peripheral singleton"] MEM_MONITOR + <= MEM_MONITOR() (unstable)), (@ peri_type #[doc = + "MODEM_LPCON peripheral singleton"] MODEM_LPCON <= MODEM_LPCON() (unstable)), (@ + peri_type #[doc = "MODEM_SYSCON peripheral singleton"] MODEM_SYSCON <= + MODEM_SYSCON() (unstable)), (@ peri_type #[doc = "PARL_IO peripheral singleton"] + PARL_IO <= PARL_IO(PARL_IO_RX : { bind_rx_interrupt, enable_rx_interrupt, disable_rx_interrupt }, PARL_IO_TX : { bind_tx_interrupt, enable_tx_interrupt, disable_tx_interrupt }) (unstable)), (@ peri_type #[doc = "PAU peripheral singleton"] PAU <= PAU() (unstable)), (@ peri_type #[doc = @@ -2668,15 +2779,15 @@ macro_rules! for_each_peripheral { (ETM(unstable)), (GPIO(unstable)), (GPIO_SD(unstable)), (HMAC(unstable)), (HP_APM(unstable)), (HP_SYS(unstable)), (HUK(unstable)), (I2C_ANA_MST(unstable)), (I2C0(unstable)), (I2S0(unstable)), (INTERRUPT_CORE0(unstable)), - (INTPRI(unstable)), (IO_MUX(unstable)), (KEYMNG(unstable)), (LP_ANA(unstable)), - (LP_AON(unstable)), (LP_APM0(unstable)), (LP_CLKRST(unstable)), - (LP_I2C0(unstable)), (LP_I2C_ANA_MST(unstable)), (LP_IO_MUX(unstable)), - (LP_PERI(unstable)), (LP_TEE(unstable)), (LP_TIMER(unstable)), - (LP_UART(unstable)), (LP_WDT(unstable)), (LPWR(unstable)), (MCPWM0(unstable)), - (MEM_MONITOR(unstable)), (MODEM_LPCON(unstable)), (MODEM_SYSCON(unstable)), - (PARL_IO(unstable)), (PAU(unstable)), (PCNT(unstable)), (PCR(unstable)), - (PMU(unstable)), (PVT_MONITOR(unstable)), (RMT(unstable)), (RSA(unstable)), - (SHA(unstable)), (SLC(unstable)), (SPI2), (SYSTEM(unstable)), + (INTPRI(unstable)), (IO_MUX(unstable)), (KEYMNG(unstable)), (LEDC(unstable)), + (LP_ANA(unstable)), (LP_AON(unstable)), (LP_APM0(unstable)), + (LP_CLKRST(unstable)), (LP_I2C0(unstable)), (LP_I2C_ANA_MST(unstable)), + (LP_IO_MUX(unstable)), (LP_PERI(unstable)), (LP_TEE(unstable)), + (LP_TIMER(unstable)), (LP_UART(unstable)), (LP_WDT(unstable)), (LPWR(unstable)), + (MCPWM0(unstable)), (MEM_MONITOR(unstable)), (MODEM_LPCON(unstable)), + (MODEM_SYSCON(unstable)), (PARL_IO(unstable)), (PAU(unstable)), (PCNT(unstable)), + (PCR(unstable)), (PMU(unstable)), (PVT_MONITOR(unstable)), (RMT(unstable)), + (RSA(unstable)), (SHA(unstable)), (SLC(unstable)), (SPI2), (SYSTEM(unstable)), (SYSTIMER(unstable)), (TEE(unstable)), (TIMG0(unstable)), (TIMG1(unstable)), (TRACE0(unstable)), (UART0), (UART1), (USB_DEVICE(unstable)), (BT(unstable)), (FLASH(unstable)), (GPIO_DEDICATED(unstable)), (LP_CORE(unstable)), diff --git a/esp-metadata-generated/src/_generated_esp32c6.rs b/esp-metadata-generated/src/_generated_esp32c6.rs index 33337b10bb7..bed6cd3fea1 100644 --- a/esp-metadata-generated/src/_generated_esp32c6.rs +++ b/esp-metadata-generated/src/_generated_esp32c6.rs @@ -207,6 +207,9 @@ macro_rules! property { ("interrupts.disabled_interrupt") => { 31 }; + ("ledc.has_gamma_fade") => { + true + }; ("rmt.ram_start") => { 1610638336 }; diff --git a/esp-metadata-generated/src/_generated_esp32h2.rs b/esp-metadata-generated/src/_generated_esp32h2.rs index 844b067703f..2c9afce45d6 100644 --- a/esp-metadata-generated/src/_generated_esp32h2.rs +++ b/esp-metadata-generated/src/_generated_esp32h2.rs @@ -201,6 +201,9 @@ macro_rules! property { ("interrupts.disabled_interrupt") => { 31 }; + ("ledc.has_gamma_fade") => { + true + }; ("rmt.ram_start") => { 1610642432 }; diff --git a/esp-metadata-generated/src/_generated_esp32s2.rs b/esp-metadata-generated/src/_generated_esp32s2.rs index de8a10d8d7a..3cd3e8cb0d1 100644 --- a/esp-metadata-generated/src/_generated_esp32s2.rs +++ b/esp-metadata-generated/src/_generated_esp32s2.rs @@ -180,6 +180,9 @@ macro_rules! property { ("interrupts.status_registers", str) => { stringify!(3) }; + ("ledc.has_gamma_fade") => { + false + }; ("rmt.ram_start") => { 1061250048 }; diff --git a/esp-metadata-generated/src/_generated_esp32s3.rs b/esp-metadata-generated/src/_generated_esp32s3.rs index f6ed1567cbf..7bb7e562226 100644 --- a/esp-metadata-generated/src/_generated_esp32s3.rs +++ b/esp-metadata-generated/src/_generated_esp32s3.rs @@ -192,6 +192,9 @@ macro_rules! property { ("interrupts.status_registers", str) => { stringify!(4) }; + ("ledc.has_gamma_fade") => { + false + }; ("rmt.ram_start") => { 1610704896 }; diff --git a/esp-metadata/devices/esp32c5.toml b/esp-metadata/devices/esp32c5.toml index 02729cc4d28..0c7a2eb7b99 100644 --- a/esp-metadata/devices/esp32c5.toml +++ b/esp-metadata/devices/esp32c5.toml @@ -41,7 +41,7 @@ peripherals = [ { name = "INTPRI" }, { name = "IO_MUX" }, { name = "KEYMNG" }, - # { name = "LEDC" }, # TODO: define later + { name = "LEDC" }, { name = "LP_ANA" }, { name = "LP_AON" }, # { name = "LP_APM" }, @@ -155,6 +155,11 @@ clocks = { system_clocks = { clock_tree = [ { name = "PLL_F120M", type = "divider", output = "PLL_CLK / 4" }, { name = "PLL_F160M", type = "divider", output = "PLL_CLK / 3" }, { name = "PLL_F240M", type = "divider", output = "PLL_CLK / 2" }, + { name = "LEDC_SCLK", type = "mux", variants = [ + { name = "PLL_F80M", outputs = "PLL_F80M" }, + { name = "RC_FAST_CLK", outputs = "RC_FAST_CLK" }, + { name = "XTAL_CLK", outputs = "XTAL_CLK" }, + ] }, # HP SOC clock root (PCR_SOC_CLK_SEL: XTAL, RC_FAST, PLL_F160M, PLL_F240M) { name = "HP_ROOT_CLK", type = "mux", variants = [ @@ -228,6 +233,7 @@ clocks = { system_clocks = { clock_tree = [ { name = "Timg1", template_params = { clk_en_template = "{{default_clk_en_template}} {{peri_clk_template}}", conf_register = "timergroup1_conf", peripheral = "tg1", peri_clk_template = "{{control}}::regs().timergroup1_timer_clk_conf().modify(|_, w| w.tg1_timer_clk_en().bit(enable));" }, clocks = "Timg0" }, { name = "Pcnt" }, + { name = "Ledc" }, { name = "Systimer", keep_enabled = true }, # can be clocked from XTAL_CLK or RC_FAST_CLK, the latter has no divider? { name = "Dma", template_params = { peripheral = "gdma" } }, { name = "Spi2" }, @@ -507,4 +513,6 @@ peripheral_controls_mem_clk = true # ## Miscellaneous # [device.io_mux] [device.systimer] +[device.ledc] +has_gamma_fade = true [device.pcnt] diff --git a/esp-metadata/devices/esp32c6.toml b/esp-metadata/devices/esp32c6.toml index 76f37d735cc..7e1334759bf 100644 --- a/esp-metadata/devices/esp32c6.toml +++ b/esp-metadata/devices/esp32c6.toml @@ -700,6 +700,7 @@ deep_sleep = true ## Interfaces [device.i2s] [device.ledc] +has_gamma_fade = true [device.mcpwm] [device.parl_io] [device.pcnt] diff --git a/esp-metadata/devices/esp32h2.toml b/esp-metadata/devices/esp32h2.toml index 2474aa255ca..67d1a1ee69c 100644 --- a/esp-metadata/devices/esp32h2.toml +++ b/esp-metadata/devices/esp32h2.toml @@ -589,6 +589,7 @@ deep_sleep = true ## Interfaces [device.i2s] [device.ledc] +has_gamma_fade = true [device.mcpwm] [device.parl_io] [device.pcnt] diff --git a/esp-metadata/src/cfg.rs b/esp-metadata/src/cfg.rs index 2eacdd78d08..61e7a746855 100644 --- a/esp-metadata/src/cfg.rs +++ b/esp-metadata/src/cfg.rs @@ -496,7 +496,10 @@ driver_configs![ LedcProperties { driver: ledc, name: "LEDC", - properties: {} + properties: { + #[serde(default)] + has_gamma_fade: bool, + } }, McpwmProperties { driver: mcpwm, diff --git a/hil-test/Cargo.toml b/hil-test/Cargo.toml index ca15dbbafd1..a314d801f98 100644 --- a/hil-test/Cargo.toml +++ b/hil-test/Cargo.toml @@ -54,6 +54,10 @@ harness = false name = "lcd_cam" harness = false +[[bin]] +name = "ledc" +harness = false + [[bin]] name = "misc_drivers" harness = false diff --git a/hil-test/src/bin/ledc.rs b/hil-test/src/bin/ledc.rs new file mode 100644 index 00000000000..5ec5130789b --- /dev/null +++ b/hil-test/src/bin/ledc.rs @@ -0,0 +1,289 @@ +//! LEDC HIL tests. +//! +//! This test uses one `common_test_pins!` pin split into input/output drivers. LEDC drives the +//! output half and the input half samples the resulting signal. + +//% CHIPS: esp32 esp32c2 esp32c3 esp32c5 esp32c6 esp32h2 esp32s2 esp32s3 +//% FEATURES: unstable + +#![no_std] +#![no_main] + +use esp_hal::{ + delay::Delay, + gpio::{AnyPin, Flex, Input, Pin}, + ledc::{ + LSGlobalClkSource, Ledc, LowSpeed, + channel::{self, ChannelIFace}, + timer::{self, TimerIFace}, + }, + peripherals::LEDC, + time::{Instant, Rate}, +}; + +const PWM_FREQUENCY: Rate = Rate::from_khz(2); +const SAMPLE_COUNT: usize = 1_000_000; +const PERIOD_SAMPLE_EDGES: usize = 32; +const PERIOD_TOLERANCE_PERCENT: u32 = 20; + +fn sample_high_count(input: &Input<'_>) -> usize { + let mut high_count = 0; + + for _ in 0..SAMPLE_COUNT { + if input.is_high() { + high_count += 1; + } + core::hint::spin_loop(); + } + + high_count +} + +fn average_period_us(input: &Input<'_>, edges: usize) -> u32 { + assert!(edges > 1); + + let mut prev = input.is_high(); + + // Wait for the first rising edge. + loop { + let now = input.is_high(); + if !prev && now { + prev = now; + break; + } + prev = now; + core::hint::spin_loop(); + } + + let first_edge = Instant::now(); + let mut last_edge = first_edge; + let mut seen_edges = 1usize; + + while seen_edges < edges { + let now = input.is_high(); + if !prev && now { + seen_edges += 1; + last_edge = Instant::now(); + } + prev = now; + core::hint::spin_loop(); + } + + let total_us = (last_edge - first_edge).as_micros() as u32; + total_us / (edges as u32 - 1) +} + +struct Context { + ledc: LEDC<'static>, + test_pin: AnyPin<'static>, + delay: Delay, +} + +#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())] +mod tests { + use super::*; + + #[init] + fn init() -> Context { + let peripherals = esp_hal::init(esp_hal::Config::default()); + let (_, test_pin) = hil_test::common_test_pins!(peripherals); + + Context { + ledc: peripherals.LEDC, + test_pin: test_pin.degrade(), + delay: Delay::new(), + } + } + + #[test] + fn duty_cycle_update_changes_high_ratio(ctx: Context) { + let Context { + ledc, + test_pin, + delay, + } = ctx; + + let pin = Flex::new(test_pin); + // SAFETY: we only configure the pin output through LEDC and only sample the input. + let (input, output) = unsafe { pin.split_into_drivers() }; + + let mut ledc = Ledc::new(ledc); + ledc.set_global_slow_clock(LSGlobalClkSource::APBClk); + + let mut timer0 = ledc.timer::(timer::Number::Timer0); + timer0 + .configure(timer::config::Config { + duty: timer::config::Duty::Duty8Bit, + clock_source: timer::LSClockSource::APBClk, + frequency: PWM_FREQUENCY, + }) + .unwrap(); + + let mut channel0 = ledc.channel(channel::Number::Channel0, output); + channel0 + .configure(channel::config::Config { + timer: &timer0, + duty_pct: 20, + drive_mode: esp_hal::gpio::DriveMode::PushPull, + }) + .unwrap(); + + delay.delay_millis(4); + let low_duty_high_count = sample_high_count(&input); + + channel0.set_duty(80).unwrap(); + + delay.delay_millis(4); + let high_duty_high_count = sample_high_count(&input); + + assert!(low_duty_high_count < SAMPLE_COUNT * 2 / 5); + assert!(high_duty_high_count > SAMPLE_COUNT * 3 / 5); + } + + #[test] + fn zero_percent_duty_is_constant_low(ctx: Context) { + let Context { + ledc, + test_pin, + delay, + } = ctx; + + let pin = Flex::new(test_pin); + // SAFETY: we only configure the pin output through LEDC and only sample the input. + let (input, output) = unsafe { pin.split_into_drivers() }; + + let mut ledc = Ledc::new(ledc); + ledc.set_global_slow_clock(LSGlobalClkSource::APBClk); + + let mut timer0 = ledc.timer::(timer::Number::Timer0); + timer0 + .configure(timer::config::Config { + duty: timer::config::Duty::Duty8Bit, + clock_source: timer::LSClockSource::APBClk, + frequency: PWM_FREQUENCY, + }) + .unwrap(); + + let mut channel0 = ledc.channel(channel::Number::Channel0, output); + channel0 + .configure(channel::config::Config { + timer: &timer0, + duty_pct: 0, + drive_mode: esp_hal::gpio::DriveMode::PushPull, + }) + .unwrap(); + + delay.delay_millis(4); + let zero_duty_high_count = sample_high_count(&input); + assert_eq!(zero_duty_high_count, 0); + + channel0.set_duty(50).unwrap(); + delay.delay_millis(4); + + let mid_duty_high_count = sample_high_count(&input); + + assert!(mid_duty_high_count > SAMPLE_COUNT / 5); + assert!(mid_duty_high_count < SAMPLE_COUNT * 4 / 5); + } + + #[test] + fn configured_frequency_matches_output_period(ctx: Context) { + let Context { + ledc, + test_pin, + delay, + } = ctx; + + let pin = Flex::new(test_pin); + // SAFETY: we only configure the pin output through LEDC and only sample the input. + let (input, output) = unsafe { pin.split_into_drivers() }; + + let mut ledc = Ledc::new(ledc); + ledc.set_global_slow_clock(LSGlobalClkSource::APBClk); + + let mut timer0 = ledc.timer::(timer::Number::Timer0); + timer0 + .configure(timer::config::Config { + duty: timer::config::Duty::Duty8Bit, + clock_source: timer::LSClockSource::APBClk, + frequency: PWM_FREQUENCY, + }) + .unwrap(); + + let mut channel0 = ledc.channel(channel::Number::Channel0, output); + channel0 + .configure(channel::config::Config { + timer: &timer0, + duty_pct: 50, + drive_mode: esp_hal::gpio::DriveMode::PushPull, + }) + .unwrap(); + + delay.delay_millis(4); + + let measured_period_us = average_period_us(&input, PERIOD_SAMPLE_EDGES); + + let expected_period_us = 1_000_000u32 / PWM_FREQUENCY.as_hz(); + let min_period_us = expected_period_us * (100 - PERIOD_TOLERANCE_PERCENT) / 100; + let max_period_us = expected_period_us * (100 + PERIOD_TOLERANCE_PERCENT) / 100; + + assert!( + measured_period_us >= min_period_us && measured_period_us <= max_period_us, + "measured period {} us, expected {} us (+/- {}%)", + measured_period_us, + expected_period_us, + PERIOD_TOLERANCE_PERCENT + ); + } + + #[cfg(all(esp32c5, ledc_has_gamma_fade))] + #[test] + fn duty_fade_changes_output_and_completes(ctx: Context) { + let Context { + ledc, + test_pin, + delay, + } = ctx; + + let pin = Flex::new(test_pin); + // SAFETY: we only configure the pin output through LEDC and only sample the input. + let (input, output) = unsafe { pin.split_into_drivers() }; + + let mut ledc = Ledc::new(ledc); + ledc.set_global_slow_clock(LSGlobalClkSource::APBClk); + + let mut timer0 = ledc.timer::(timer::Number::Timer0); + timer0 + .configure(timer::config::Config { + duty: timer::config::Duty::Duty8Bit, + clock_source: timer::LSClockSource::APBClk, + frequency: PWM_FREQUENCY, + }) + .unwrap(); + + let mut channel0 = ledc.channel(channel::Number::Channel0, output); + channel0 + .configure(channel::config::Config { + timer: &timer0, + duty_pct: 10, + drive_mode: esp_hal::gpio::DriveMode::PushPull, + }) + .unwrap(); + + delay.delay_millis(6); + let initial_high_count = sample_high_count(&input); + assert!(initial_high_count < SAMPLE_COUNT / 3); + + channel0.start_duty_fade(10, 90, 200).unwrap(); + + while !channel0.is_duty_fade_running() { + core::hint::spin_loop(); + } + + delay.delay_millis(260); + assert!(!channel0.is_duty_fade_running()); + + let final_high_count = sample_high_count(&input); + assert!(final_high_count > SAMPLE_COUNT * 2 / 3); + } +}