diff --git a/boards/esp32-c3-devkitM-1/src/main.rs b/boards/esp32-c3-devkitM-1/src/main.rs index b3ff394e9f..476146dfbb 100644 --- a/boards/esp32-c3-devkitM-1/src/main.rs +++ b/boards/esp32-c3-devkitM-1/src/main.rs @@ -11,6 +11,7 @@ use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; use esp32_c3::chip::Esp32C3DefaultPeripherals; +use esp32_c3::sysreg::{ClockPrescaler, CpuClock}; use kernel::capabilities; use kernel::component::Component; use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; @@ -148,10 +149,11 @@ unsafe fn setup() -> ( peripherals.rtc_cntl.disable_super_wdt(); peripherals.sysreg.disable_timg0(); peripherals.sysreg.enable_timg0(); + peripherals.sysreg.enable_ledc(); peripherals .sysreg - .use_pll_clock_source(PllFrequency::MHz320, CpuFrequency::MHz160); + .set_clock_source(CpuClock::Pll(PllFrequency::MHz320, CpuFrequency::MHz160)); // initialise capabilities let process_mgmt_cap = create_capability!(capabilities::ProcessManagementCapability); diff --git a/chips/esp32-c3/src/chip.rs b/chips/esp32-c3/src/chip.rs index 4a23cb7afc..438507e9b9 100644 --- a/chips/esp32-c3/src/chip.rs +++ b/chips/esp32-c3/src/chip.rs @@ -13,6 +13,7 @@ use rv32i::syscall::SysCall; use crate::intc::{Intc, IntcRegisters}; use crate::interrupts; +use crate::led_pwm; use crate::sysreg; use crate::timg; @@ -35,6 +36,7 @@ pub struct Esp32C3DefaultPeripherals<'a> { pub gpio: esp32::gpio::Port<'a>, pub rtc_cntl: esp32::rtc_cntl::RtcCntl, pub sysreg: sysreg::SysReg, + pub led_pwm: led_pwm::LedPwm, } impl<'a> Esp32C3DefaultPeripherals<'a> { @@ -46,6 +48,7 @@ impl<'a> Esp32C3DefaultPeripherals<'a> { gpio: esp32::gpio::Port::new(), rtc_cntl: esp32::rtc_cntl::RtcCntl::new(esp32::rtc_cntl::RTC_CNTL_BASE), sysreg: sysreg::SysReg::new(), + led_pwm: led_pwm::LedPwm::new(), } } } @@ -65,6 +68,11 @@ impl<'a> InterruptService<()> for Esp32C3DefaultPeripherals<'a> { interrupts::IRQ_GPIO | interrupts::IRQ_GPIO_NMI => { self.gpio.handle_interrupt(); } + interrupts::IRQ_LEDC => { + //handler is unimplemented yet + //not sure exactly what to do :) + self.led_pwm.handle_interrupt(); + } _ => return false, } true diff --git a/chips/esp32-c3/src/intc.rs b/chips/esp32-c3/src/intc.rs index adb8425b7e..1d92439d28 100644 --- a/chips/esp32-c3/src/intc.rs +++ b/chips/esp32-c3/src/intc.rs @@ -16,16 +16,18 @@ register_structs! { (0x048 => _reserved1), (0x054 => uart0_intr_map: ReadWrite), (0x058 => _reserved2), + (0x05C => ledc_intr_map: ReadWrite), + (0x060 => _reserved3), (0x080 => timg0_intr_map: ReadWrite), (0x084 => timg1_intr_map: ReadWrite), - (0x088 => _reserved3), + (0x088 => _reserved4), (0x0f8 => status: [ReadWrite; 2]), (0x100 => clk_en: ReadWrite), (0x104 => enable: ReadWrite), (0x108 => type_reg: ReadWrite), (0x10C => clear: ReadWrite), (0x110 => eip: ReadWrite), - (0x114 => _reserved4), + (0x114 => _reserved5), (0x118 => priority: [ReadWrite; 31]), (0x194 => thresh: ReadWrite), (0x198 => @END), @@ -71,6 +73,7 @@ impl Intc { /// In Tock we map them ourselves so we don't need to call into the ROM. pub fn map_interrupts(&self) { self.registers.uart0_intr_map.set(interrupts::IRQ_UART0); + self.registers.ledc_intr_map.set(interrupts::IRQ_LEDC); self.registers.timg0_intr_map.set(interrupts::IRQ_TIMER1); self.registers.timg1_intr_map.set(interrupts::IRQ_TIMER2); self.registers diff --git a/chips/esp32-c3/src/led_pwm.rs b/chips/esp32-c3/src/led_pwm.rs new file mode 100644 index 0000000000..6baa98f155 --- /dev/null +++ b/chips/esp32-c3/src/led_pwm.rs @@ -0,0 +1,226 @@ +use kernel::hil; +use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; +use kernel::utilities::registers::{register_bitfields, register_structs, ReadWrite}; +use kernel::utilities::StaticRef; +use kernel::ErrorCode; + +pub const LEDC_BASE: StaticRef = + unsafe { StaticRef::new(0x6001_9000 as *const LedPwmRegisters) }; + +register_structs! { + pub LedPwmChannel { + (0x0000 => conf0: ReadWrite), + (0x0004 => hpoint: ReadWrite), + (0x0008 => duty: ReadWrite), + (0x000C => conf1: ReadWrite), + (0x0010 => duty_r: ReadWrite), + (0x0014 => @END), + }, + pub LedPwmTimer { + (0x0000 => conf: ReadWrite), + (0x0004 => value: ReadWrite), + (0x0008 => @END), + }, + pub LedPwmRegisters { + (0x0000 => ledc_ch: [LedPwmChannel; 6]), + (0x0078 => _reserved0), + (0x00A0 => ledc_timer: [LedPwmTimer; 4]), + (0x00C0 => ledc_int_raw: ReadWrite), + (0x00C4 => ledc_int_st: ReadWrite), + (0x00C8 => ledc_int_ena: ReadWrite), + (0x00CC => ledc_int_clr: ReadWrite), + (0x00D0 => ledc_conf: ReadWrite), + (0x00D4 => _reserved1), + (0x00FC => ledc_date: ReadWrite), + (0x0100 => @END), + } +} + +register_bitfields! [u32, + // x used for timers, value between [0, 3] + // n used for pwm generators, value between [0, 5] + + // naming suggestions are appreciated + LEDC_CH_CONF0 [ + // used to select one of the timers on channel n + // 0: select Timer0 + // 1: select Timer1 + // 2: select Timer2 + // 3: select Timer3 + TIMER_SEL_CH OFFSET(0) NUMBITS(2) [], + // set to enable signal output on channel n + SIG_OUT_EN_CH OFFSET(2) NUMBITS(1) [], + // controls the output value when channel n is inactive(LEDC_SIG_OUT_EN_CHn == 0) + IDLE_LV_CH OFFSET(3) NUMBITS(1) [ + Low = 0, + High = 1, + ], + // used to update the listed fields below for channel n; is automatically cleared by hardware + // HPOINT_CHn, DUTY_START_CHn, SIG_OUT_EN_CHn, TIMER_SEL_CHn, DUTY_NUM_CHn, DUTY_CYCLE_CHn, + // DUTY_SCALE_CHn, DUTY_INC_CHn and OVF_CNT_EN_CHn + PARA_UP_CH OFFSET(4) NUMBITS(1) [], + // configures maximum times of overflow - 1 + // LEDC_OVF_CNT_CHn_INT will be triggered when channel n overflows for (LEDC_OVF_NUM_CHn + 1) times + OVF_NUM_CH OFFSET(5) NUMBITS(10) [], + // counts the number of times when the timer selected by channel n overflows + OVF_CNT_EN_CH OFFSET(15) NUMBITS(1) [], + // set to reset timer-overflow counter of channel n + OVF_CNT_RESET_CH OFFSET(16) NUMBITS(1) [], + ], + LEDC_CH_CONF1 [ + // configures the step size of the duty cucle change during fading + DUTY_SCALE_CH OFFSET(0) NUMBITS(10) [], + // sets the number of counter overflow cycles for every Lpointn increment/decrement + DUTY_CYCLE_CH OFFSET(10) NUMBITS(10) [], + // controls the number of times the duty cycle will be incremented/decremented + DUTY_NUM_CH OFFSET(20) NUMBITS(10) [], + // determines the monotony of the duty cycle of the output signal on channel n + // 0: Decreases + // 1: Increases + DUTY_INC_CH OFFSET(30) NUMBITS(1) [], + // set to enable duty cycle fading on channel n + DUTY_START_CH OFFSET(31) NUMBITS(1) [], + ], + LEDC_CONF [ + // used to select the clock source for all 4 timers + // 01: APB_CLK + // 10: RC_FAST_CLK + // 11: XTAL_CLK + APB_CLK_SEL OFFSET(0) NUMBITS(2) [ + APB_CLK = 0b01, + RC_FAST_CLK = 0b10, + XTAL_CLK = 0b11, + ], + // used to control clock + // 0: support clock only when application writes registers + // 1: force clock on register + CLK_EN OFFSET(31) NUMBITS(1) [], + ], + LEDC_CH_HPOINT [ + // signal output value changes to high when the selected timer + // has reached the value in this field + HPOINT_CH OFFSET(0) NUMBITS(14) [], + ], + LEDC_CH_DUTY [ + // signal output value changes to low when the selected timer + // has reached the value specified in this field + DUTY_CH OFFSET(0) NUMBITS(19) [], + ], + LEDC_CH_DUTY_R [ + DUTY_R_CH OFFSET(0) NUMBITS(19) [], + ], + LEDC_TIMER_CONF [ + // used to control range of counter of timer x + TIMER_DUTY_RES OFFSET(0) NUMBITS(4) [], + // used to configure the divisor for the divider of timer x + CLK_DIV_TIMER OFFSET(4) NUMBITS(18) [], + // used to suspend the counter of timer x + TIMER_PAUSE OFFSET(22) NUMBITS(1) [], + // used to reset timer x; counter will show 0 after reset + TIMER_RST OFFSET(23) NUMBITS(1) [], + // set to update LEDC_CLK_DIV_TIMERx and LEDC_TIMERx_DUTY_RES + TIMER_PARA_UP OFFSET(25) NUMBITS(1) [], + ], + LEDC_TIMER_VALUE [ + // stores the current counter value of timer x + TIMER_CNT OFFSET(0) NUMBITS(14) [], + ], + LEDC_INT [ + // RAW: + // triggered when timer x has reached its maximum counter value + // ST: + // masked interrupt status bit for the LEDC_TIMERx_OVF_INT interrupt + // when LEDC_TIMERx_OVF_INT_ENA is set to 1 + // ENA: + // set to enable LEDC_TIMERx_OVF_INT interrupt + // CLR: + // set to clear LEDC_TIMERx_OVF_INT interrupt + TIMER0_OVF OFFSET(0) NUMBITS(1) [], + TIMER1_OVF OFFSET(1) NUMBITS(1) [], + TIMER2_OVF OFFSET(2) NUMBITS(1) [], + TIMER3_OVF OFFSET(3) NUMBITS(1) [], + // RAW: + // interrupt raw bit for channel n + // triggered when the gradual change of duty has finished + // ST: + // masked interrupt status bit for LEDC_DUTY_CHNG_END_CHn_INT interrupt + // when LEDC_DUTY_CHNG_CHn_INT_ENA is set to 1 + // ENA: + // set to enable LEDC_DUTY_CHNG_END_CHn_INT interrupt + // CLR: + // set to clear LEDC_DUTY_CHNG_END_CHn_INT interrupt + DUTY_CHNG_END_CH0 OFFSET(4) NUMBITS(1) [], + DUTY_CHNG_END_CH1 OFFSET(5) NUMBITS(1) [], + DUTY_CHNG_END_CH2 OFFSET(6) NUMBITS(1) [], + DUTY_CHNG_END_CH3 OFFSET(7) NUMBITS(1) [], + DUTY_CHNG_END_CH4 OFFSET(8) NUMBITS(1) [], + DUTY_CHNG_END_CH5 OFFSET(9) NUMBITS(1) [], + // RAW: + // interrupt raw bit for channel n + // triggered when overflow counter has reached value specified by LEDC_OVF_NUM_CHn + // ST: + // masked interrupt status bit for LEDC_DUTY_CNT_CHn_INT interrupt + // when LEDC_OVF_CNT_CHn_INT_ENA is set to 1 + // ENA: + // set to enable LEDC_OVF_CHn_INT interrupt + // CLR: + // set to clear LEDC_OVF_CNT_CHn_INT interrupt + OVF_CNT_CH0 OFFSET(10) NUMBITS(1) [], + OVF_CNT_CH1 OFFSET(11) NUMBITS(1) [], + OVF_CNT_CH2 OFFSET(12) NUMBITS(1) [], + OVF_CNT_CH3 OFFSET(13) NUMBITS(1) [], + OVF_CNT_CH4 OFFSET(14) NUMBITS(1) [], + OVF_CNT_CH5 OFFSET(15) NUMBITS(1) [], + ], + LEDC_DATE [ + // version control register + LEDC_DATE OFFSET(0) NUMBITS(32) [], + ] +]; + +pub struct LedPwm { + registers: StaticRef, +} + +impl LedPwm { + pub const fn new() -> Self { + LedPwm { + registers: LEDC_BASE, + } + } + + pub fn handle_interrupt(&self) { + todo!() + } +} + +impl hil::pwm::Pwm for LedPwm { + type Pin = esp32::gpio::GpioPin<'static>; + + fn start( + &self, + pin: &Self::Pin, + frequency_hz: usize, + duty_cycle: usize, + ) -> Result<(), ErrorCode> { + todo!() + } + + fn stop(&self, pin: &Self::Pin) -> Result<(), ErrorCode> { + todo!() + } + + fn get_maximum_frequency_hz(&self) -> usize { + // frequency of a PWM generator output signal is given by: + // + // LEDC_CLKx / (LEDC_CLK_DIV_TIMERx * 2 ^ LEDC_TIMERx_DUTY_RES) + // + todo!() + } + + fn get_maximum_duty_cycle(&self) -> usize { + // the PWM can be set to high for the entire period of the signal, + // resulting in a 100% duty cycle + self.get_maximum_frequency_hz() + } +} diff --git a/chips/esp32-c3/src/lib.rs b/chips/esp32-c3/src/lib.rs index d5eae88432..d59e4e3ce8 100644 --- a/chips/esp32-c3/src/lib.rs +++ b/chips/esp32-c3/src/lib.rs @@ -8,6 +8,7 @@ pub mod chip; pub mod intc; pub mod interrupts; +pub mod led_pwm; pub mod sysreg; pub mod timg { diff --git a/chips/esp32-c3/src/sysreg.rs b/chips/esp32-c3/src/sysreg.rs index 33b687cb47..04b4f5e387 100644 --- a/chips/esp32-c3/src/sysreg.rs +++ b/chips/esp32-c3/src/sysreg.rs @@ -3,6 +3,7 @@ use kernel::utilities::registers::interfaces::{ReadWriteable, Readable}; use kernel::utilities::registers::{register_bitfields, register_structs, ReadWrite}; use kernel::utilities::StaticRef; +use kernel::ErrorCode; pub const SYS_REG_BASE: StaticRef = unsafe { StaticRef::new(0x600C_0000 as *const SysRegRegisters) }; @@ -14,6 +15,8 @@ register_structs! { (0x00c => _reserved1), (0x010 => perip_clk_en0: ReadWrite), (0x014 => _reserved3), + (0x018 => perip_rst_en0: ReadWrite), + (0x01C => _reserved4), (0x058 => sysclk_config: ReadWrite), (0x05C => _reserved_unimplemented_yet), (0x1000 => @END), @@ -22,7 +25,11 @@ register_structs! { register_bitfields![u32, PERIP_CLK_EN0 [ - TIMERGROUP0 OFFSET(13) NUMBITS(1) [] + LEDC OFFSET(11) NUMBITS(1) [], + TIMERGROUP0 OFFSET(13) NUMBITS(1) [], + ], + PERIP_RST_EN0 [ + LEDC OFFSET(11) NUMBITS(1) [] ], CPU_PER_CONF [ CPUPERIOD_SEL OFFSET(0) NUMBITS(2) [ @@ -31,7 +38,7 @@ register_bitfields![u32, ], PLL_FREQ_SEL OFFSET(2) NUMBITS(1) [ MHz320 = 0, - MHz480 = 1 + MHz480 = 1, ], CPU_WAIT_MODE_FORCE_ON OFFSET(3) NUMBITS(1) [], CPU_WAIT_DELAY_NUM OFFSET(4) NUMBITS(4) [], @@ -41,10 +48,10 @@ register_bitfields![u32, SOC_CLK_SEL OFFSET(10) NUMBITS(2) [ Xtal = 0, Pll = 1, - Fosc = 2 + RcFast = 2, ], CLK_XTAL_FREQ OFFSET(12) NUMBITS(6) [], - ] + ], ]; #[repr(u32)] @@ -53,12 +60,49 @@ pub enum PllFrequency { MHz480 = 1, } +impl From for PllFrequency { + fn from(value: u32) -> Self { + match value { + 0 => PllFrequency::MHz320, + 1 => PllFrequency::MHz480, + _ => unreachable!(), + } + } +} + #[repr(u32)] pub enum CpuFrequency { MHz80 = 0, MHz160 = 1, } +impl From for CpuFrequency { + fn from(value: u32) -> Self { + match value { + 0 => CpuFrequency::MHz80, + 1 => CpuFrequency::MHz160, + _ => unreachable!(), + } + } +} + +pub struct ClockPrescaler(u32); + +impl ClockPrescaler { + pub fn new(prescaler: u32) -> Self { + ClockPrescaler(match prescaler >= 1024 { + true => 1, + false => prescaler, + }) + } +} + +pub enum CpuClock { + Xtal(ClockPrescaler), + Pll(PllFrequency, CpuFrequency), + RcFast(ClockPrescaler), +} + pub struct SysReg { registers: StaticRef, } @@ -70,20 +114,80 @@ impl SysReg { } } - pub fn use_xtal_clock_source(&self) { - self.registers + pub fn set_clock_source(&self, clock: CpuClock) { + match clock { + CpuClock::Xtal(ClockPrescaler(prescaler)) => self.registers.sysclk_config.modify( + SYSCLK_CONFIG::SOC_CLK_SEL::Xtal + SYSCLK_CONFIG::PRE_DIV_CNT.val(prescaler as u32), + ), + CpuClock::Pll(pll_frequency, cpu_frequency) => { + self.registers + .sysclk_config + .modify(SYSCLK_CONFIG::SOC_CLK_SEL::Pll); + self.registers.cpu_per_conf.modify( + CPU_PER_CONF::PLL_FREQ_SEL.val(pll_frequency as u32) + + CPU_PER_CONF::CPUPERIOD_SEL.val(cpu_frequency as u32), + ); + } + CpuClock::RcFast(ClockPrescaler(prescaler)) => self.registers.sysclk_config.modify( + SYSCLK_CONFIG::SOC_CLK_SEL::RcFast + + SYSCLK_CONFIG::PRE_DIV_CNT.val(prescaler as u32), + ), + } + } + + pub fn get_clock_source(&self) -> Option { + match self + .registers .sysclk_config - .modify(SYSCLK_CONFIG::SOC_CLK_SEL::Xtal); + .read_as_enum(SYSCLK_CONFIG::SOC_CLK_SEL) + { + Some(SYSCLK_CONFIG::SOC_CLK_SEL::Value::Xtal) => Some(CpuClock::Xtal(ClockPrescaler( + self.registers + .sysclk_config + .read(SYSCLK_CONFIG::PRE_DIV_CNT), + ))), + Some(SYSCLK_CONFIG::SOC_CLK_SEL::Value::Pll) => Some(CpuClock::Pll( + PllFrequency::from(self.registers.cpu_per_conf.read(CPU_PER_CONF::PLL_FREQ_SEL)), + CpuFrequency::from( + self.registers + .cpu_per_conf + .read(CPU_PER_CONF::CPUPERIOD_SEL), + ), + )), + Some(SYSCLK_CONFIG::SOC_CLK_SEL::Value::RcFast) => { + Some(CpuClock::RcFast(ClockPrescaler( + self.registers + .sysclk_config + .read(SYSCLK_CONFIG::PRE_DIV_CNT), + ))) + } + None => None, + } } - pub fn use_pll_clock_source(&self, pll_frequency: PllFrequency, cpu_frequency: CpuFrequency) { + //Enable the APB_CLK signal to LED PWM + pub fn enable_ledc(&self) { self.registers - .sysclk_config - .modify(SYSCLK_CONFIG::SOC_CLK_SEL::Pll); - self.registers.cpu_per_conf.modify( - CPU_PER_CONF::PLL_FREQ_SEL.val(pll_frequency as u32) - + CPU_PER_CONF::CPUPERIOD_SEL.val(cpu_frequency as u32), - ); + .perip_clk_en0 + .modify(PERIP_CLK_EN0::LEDC::SET); + } + + //Disable the APB_CLK signal to LED PWM + pub fn disable_ledc(&self) { + self.registers + .perip_clk_en0 + .modify(PERIP_CLK_EN0::LEDC::CLEAR); + } + + pub fn is_enabled_ledc(&self) -> bool { + self.registers.perip_clk_en0.is_set(PERIP_CLK_EN0::LEDC) + } + + //Reset the APB_CLK signal for LED PWM + pub fn reset_ledc(&self) { + self.registers + .perip_rst_en0 + .modify(PERIP_RST_EN0::LEDC::SET); } pub fn enable_timg0(&self) {