#![cfg_attr(docsrs, procmacros::doc_replace)]
use core::marker::PhantomData;
use super::Error;
#[cfg(timergroup_timg1)]
use crate::peripherals::TIMG1;
#[cfg(soc_has_clock_node_timg_function_clock)]
use crate::soc::clocks::TimgFunctionClockConfig;
#[cfg(soc_has_clock_node_timg_wdt_clock)]
use crate::soc::clocks::TimgWdtClockConfig;
use crate::{
asynch::AtomicWaker,
interrupt::{self, InterruptConfigurable, InterruptHandler},
pac::timg0::RegisterBlock,
peripherals::{Interrupt, TIMG0},
private::Sealed,
soc::clocks,
system::PeripheralClockControl,
time::{Duration, Instant, Rate},
};
const NUM_TIMG: usize = 1 + cfg!(timergroup_timg1) as usize;
cfg_if::cfg_if! {
if #[cfg(all(timergroup_timg_has_timer1, not(any(esp32, esp32s2))))] {
use esp_sync::RawMutex;
static INT_ENA_LOCK: [RawMutex; NUM_TIMG] = [const { RawMutex::new() }; NUM_TIMG];
}
}
#[procmacros::doc_replace(
"timers" => {
cfg(timergroup_timg_has_timer1) => "2 timers",
_ => "a general purpose timer",
}
)]
pub struct TimerGroup<'d, T>
where
T: TimerGroupInstance + 'd,
{
_timer_group: PhantomData<T>,
pub timer0: Timer<'d>,
#[cfg(timergroup_timg_has_timer1)]
pub timer1: Timer<'d>,
pub wdt: Wdt<T>,
}
#[doc(hidden)]
pub trait TimerGroupInstance {
fn id() -> u8;
fn register_block() -> *const RegisterBlock;
#[cfg(soc_has_clock_node_timg_function_clock)]
fn clock_instance() -> clocks::TimgInstance;
fn peripheral() -> crate::system::Peripheral;
fn wdt_interrupt() -> Interrupt;
}
#[cfg(timergroup_timg0)]
impl TimerGroupInstance for TIMG0<'_> {
fn id() -> u8 {
0
}
#[inline(always)]
fn register_block() -> *const RegisterBlock {
Self::regs()
}
#[cfg(soc_has_clock_node_timg_function_clock)]
fn clock_instance() -> clocks::TimgInstance {
clocks::TimgInstance::Timg0
}
fn peripheral() -> crate::system::Peripheral {
crate::system::Peripheral::Timg0
}
fn wdt_interrupt() -> Interrupt {
Interrupt::TG0_WDT_LEVEL
}
}
#[cfg(timergroup_timg1)]
impl TimerGroupInstance for crate::peripherals::TIMG1<'_> {
fn id() -> u8 {
1
}
#[inline(always)]
fn register_block() -> *const RegisterBlock {
Self::regs()
}
#[cfg(soc_has_clock_node_timg_function_clock)]
fn clock_instance() -> clocks::TimgInstance {
clocks::TimgInstance::Timg1
}
fn peripheral() -> crate::system::Peripheral {
crate::system::Peripheral::Timg1
}
fn wdt_interrupt() -> Interrupt {
Interrupt::TG1_WDT_LEVEL
}
}
impl<'d, T> TimerGroup<'d, T>
where
T: TimerGroupInstance + 'd,
{
pub fn new(_timer_group: T) -> Self {
if PeripheralClockControl::enable(T::peripheral()) {
PeripheralClockControl::reset(T::peripheral());
} else {
PeripheralClockControl::disable(T::peripheral());
}
#[cfg(soc_has_clock_node_timg_function_clock)]
clocks::ClockTree::with(|clocks| {
let clock = T::clock_instance();
clock.configure_function_clock(clocks, TimgFunctionClockConfig::default());
clock.request_function_clock(clocks);
});
Self {
_timer_group: PhantomData,
timer0: Timer {
timer: TimerId::Timer0,
tg: T::id(),
register_block: T::register_block(),
_lifetime: PhantomData,
},
#[cfg(timergroup_timg_has_timer1)]
timer1: Timer {
timer: TimerId::Timer1,
tg: T::id(),
register_block: T::register_block(),
_lifetime: PhantomData,
},
wdt: Wdt::new(),
}
}
}
impl super::Timer for Timer<'_> {
fn start(&self) {
self.set_counter_active(false);
self.set_alarm_active(false);
self.reset_counter();
self.set_counter_decrementing(false);
self.set_counter_active(true);
self.set_alarm_active(true);
}
fn stop(&self) {
self.set_counter_active(false);
}
fn reset(&self) {
self.reset_counter()
}
fn is_running(&self) -> bool {
self.is_counter_active()
}
fn now(&self) -> Instant {
self.now()
}
fn load_value(&self, value: Duration) -> Result<(), Error> {
self.load_value(value)
}
fn enable_auto_reload(&self, auto_reload: bool) {
self.set_auto_reload(auto_reload)
}
fn enable_interrupt(&self, state: bool) {
self.set_interrupt_enabled(state);
}
fn clear_interrupt(&self) {
self.clear_interrupt()
}
fn is_interrupt_set(&self) -> bool {
self.is_interrupt_set()
}
fn async_interrupt_handler(&self) -> InterruptHandler {
match (self.timer_group(), self.timer_number()) {
(0, 0) => asynch::timg0_timer0_handler,
#[cfg(timergroup_timg_has_timer1)]
(0, 1) => asynch::timg0_timer1_handler,
#[cfg(timergroup_timg1)]
(1, 0) => asynch::timg1_timer0_handler,
#[cfg(all(timergroup_timg_has_timer1, timergroup_timg1))]
(1, 1) => asynch::timg1_timer1_handler,
_ => unreachable!(),
}
}
fn peripheral_interrupt(&self) -> Interrupt {
match (self.timer_group(), self.timer_number()) {
(0, 0) => Interrupt::TG0_T0_LEVEL,
#[cfg(timergroup_timg_has_timer1)]
(0, 1) => Interrupt::TG0_T1_LEVEL,
#[cfg(timergroup_timg1)]
(1, 0) => Interrupt::TG1_T0_LEVEL,
#[cfg(all(timergroup_timg_has_timer1, timergroup_timg1))]
(1, 1) => Interrupt::TG1_T1_LEVEL,
_ => unreachable!(),
}
}
fn set_interrupt_handler(&self, handler: InterruptHandler) {
self.set_interrupt_handler(handler)
}
fn waker(&self) -> &AtomicWaker {
asynch::waker(self)
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Timer<'d> {
register_block: *const RegisterBlock,
_lifetime: PhantomData<&'d mut ()>,
timer: TimerId,
tg: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum TimerId {
Timer0,
#[cfg(timergroup_timg_has_timer1)]
Timer1,
}
impl Sealed for Timer<'_> {}
unsafe impl Send for Timer<'_> {}
impl Timer<'_> {
pub unsafe fn clone_unchecked(&self) -> Self {
Self {
register_block: self.register_block,
timer: self.timer,
tg: self.tg,
_lifetime: PhantomData,
}
}
pub fn reborrow(&mut self) -> Timer<'_> {
unsafe { self.clone_unchecked() }
}
pub(crate) fn set_interrupt_handler(&self, handler: InterruptHandler) {
let interrupt = match (self.timer_group(), self.timer_number()) {
(0, 0) => Interrupt::TG0_T0_LEVEL,
#[cfg(timergroup_timg_has_timer1)]
(0, 1) => Interrupt::TG0_T1_LEVEL,
#[cfg(timergroup_timg1)]
(1, 0) => Interrupt::TG1_T0_LEVEL,
#[cfg(all(timergroup_timg_has_timer1, timergroup_timg1))]
(1, 1) => Interrupt::TG1_T1_LEVEL,
_ => unreachable!(),
};
for core in crate::system::Cpu::other() {
crate::interrupt::disable(core, interrupt);
}
interrupt::bind_handler(interrupt, handler);
}
fn register_block(&self) -> &RegisterBlock {
unsafe { &*self.register_block }
}
fn timer_group(&self) -> u8 {
self.tg
}
fn timer_number(&self) -> u8 {
self.timer as u8
}
fn t(&self) -> &crate::pac::timg0::T {
self.register_block().t(self.timer_number().into())
}
fn reset_counter(&self) {
let t = self.t();
t.loadlo().write(|w| unsafe { w.load_lo().bits(0) });
t.loadhi().write(|w| unsafe { w.load_hi().bits(0) });
t.load().write(|w| unsafe { w.load().bits(1) });
}
fn set_counter_active(&self, state: bool) {
self.t().config().modify(|_, w| w.en().bit(state));
}
fn is_counter_active(&self) -> bool {
self.t().config().read().en().bit_is_set()
}
fn set_counter_decrementing(&self, decrementing: bool) {
self.t()
.config()
.modify(|_, w| w.increase().bit(!decrementing));
}
fn set_auto_reload(&self, auto_reload: bool) {
self.t()
.config()
.modify(|_, w| w.autoreload().bit(auto_reload));
}
fn set_alarm_active(&self, state: bool) {
self.t().config().modify(|_, w| w.alarm_en().bit(state));
}
fn source_frequency(&self) -> Rate {
let hz = clocks::ClockTree::with(|clocks| {
cfg_if::cfg_if! {
if #[cfg(soc_has_clock_node_timg_function_clock)] {
let timg = match self.timer_group() {
0 => crate::soc::clocks::TimgInstance::Timg0,
#[cfg(soc_has_timg1)]
1 => crate::soc::clocks::TimgInstance::Timg1,
_ => unreachable!()
};
timg.function_clock_frequency(clocks)
} else {
crate::soc::clocks::apb_clk_frequency(clocks)
}
}
});
Rate::from_hz(hz)
}
fn load_value(&self, value: Duration) -> Result<(), Error> {
let clk_src = self.source_frequency();
let Some(ticks) = timeout_to_ticks(value, clk_src, self.divider()) else {
return Err(Error::InvalidTimeout);
};
if (ticks & !0x3F_FFFF_FFFF_FFFF) != 0 {
return Err(Error::InvalidTimeout);
}
let high = (ticks >> 32) as u32;
let low = (ticks & 0xFFFF_FFFF) as u32;
let t = self.t();
t.alarmlo().write(|w| unsafe { w.alarm_lo().bits(low) });
t.alarmhi().write(|w| unsafe { w.alarm_hi().bits(high) });
Ok(())
}
fn clear_interrupt(&self) {
self.register_block()
.int_clr()
.write(|w| w.t(self.timer as _).clear_bit_by_one());
let periodic = self.t().config().read().autoreload().bit_is_set();
self.set_alarm_active(periodic);
}
fn now(&self) -> Instant {
let t = self.t();
t.update().write(|w| w.update().set_bit());
while t.update().read().update().bit_is_set() {
}
let value_lo = t.lo().read().bits() as u64;
let value_hi = t.hi().read().bits() as u64;
let ticks = (value_hi << 32) | value_lo;
let clk_src = self.source_frequency();
let micros = ticks_to_timeout(ticks, clk_src, self.divider());
Instant::from_ticks(micros)
}
fn divider(&self) -> u32 {
let t = self.t();
match t.config().read().divider().bits() {
0 => 65536,
1 | 2 => 2,
n => n as u32,
}
}
fn is_interrupt_set(&self) -> bool {
self.register_block()
.int_raw()
.read()
.t(self.timer as _)
.bit_is_set()
}
fn set_interrupt_enabled(&self, state: bool) {
cfg_if::cfg_if! {
if #[cfg(any(esp32, esp32s2))] {
self.t()
.config()
.modify(|_, w| w.level_int_en().bit(state));
} else if #[cfg(timergroup_timg_has_timer1)] {
INT_ENA_LOCK[self.timer_group() as usize].lock(|| {
self.register_block()
.int_ena()
.modify(|_, w| w.t(self.timer_number()).bit(state));
});
} else {
self.register_block()
.int_ena()
.modify(|_, w| w.t(0).bit(state));
}
}
}
}
fn ticks_to_timeout(ticks: u64, clock: Rate, divider: u32) -> u64 {
let period: u64 = 1_000_000 * 1_000_000 / (clock.as_hz() as u64 / divider as u64);
ticks * period / 1_000_000
}
fn timeout_to_ticks(timeout: Duration, clock: Rate, divider: u32) -> Option<u64> {
let micros = timeout.as_micros();
let ticks_per_sec = (clock.as_hz() / divider) as u64;
micros.checked_mul(ticks_per_sec).map(|n| n / 1_000_000)
}
#[allow(unused)]
#[derive(Debug, Clone, Copy)]
pub enum MwdtStageAction {
Off = 0,
Interrupt = 1,
ResetCpu = 2,
ResetSystem = 3,
}
#[derive(Debug, Clone, Copy)]
pub enum MwdtStage {
Stage0,
Stage1,
Stage2,
Stage3,
}
pub struct Wdt<TG> {
phantom: PhantomData<TG>,
}
impl<TG> Wdt<TG>
where
TG: TimerGroupInstance,
{
pub fn new() -> Self {
let mut this = Self {
phantom: PhantomData,
};
this.set_write_protection(false);
#[cfg(soc_has_clock_node_timg_wdt_clock)]
clocks::ClockTree::with(|clocks| {
TG::clock_instance().configure_wdt_clock(clocks, TimgWdtClockConfig::default())
});
this.set_write_protection(true);
this
}
pub fn enable(&mut self) {
unsafe { self.set_wdt_enabled(true) };
}
pub fn disable(&mut self) {
unsafe { self.set_wdt_enabled(false) };
}
pub unsafe fn set_wdt_enabled(&mut self, enabled: bool) {
let reg_block = unsafe { &*TG::register_block() };
self.set_write_protection(false);
#[cfg(soc_has_clock_node_timg_wdt_clock)]
if enabled {
clocks::ClockTree::with(|clocks| TG::clock_instance().request_wdt_clock(clocks));
}
reg_block
.wdtconfig0()
.modify(|_, w| w.wdt_en().bit(enabled));
if enabled {
reg_block.wdtconfig0().modify(|_, w| unsafe {
w.wdt_flashboot_mod_en().bit(false);
w.wdt_stg0().bits(MwdtStageAction::ResetSystem as u8);
w.wdt_cpu_reset_length().bits(7);
w.wdt_sys_reset_length().bits(7);
w.wdt_stg1().bits(MwdtStageAction::Off as u8);
w.wdt_stg2().bits(MwdtStageAction::Off as u8);
w.wdt_stg3().bits(MwdtStageAction::Off as u8)
});
#[cfg(any(esp32c2, esp32c3, esp32c6))]
reg_block
.wdtconfig0()
.modify(|_, w| w.wdt_conf_update_en().set_bit());
}
#[cfg(soc_has_clock_node_timg_wdt_clock)]
if !enabled {
clocks::ClockTree::with(|clocks| TG::clock_instance().release_wdt_clock(clocks));
}
self.set_write_protection(true);
}
pub fn feed(&mut self) {
let reg_block = unsafe { &*TG::register_block() };
self.set_write_protection(false);
reg_block.wdtfeed().write(|w| unsafe { w.bits(1) });
self.set_write_protection(true);
}
fn set_write_protection(&mut self, enable: bool) {
let reg_block = unsafe { &*TG::register_block() };
let wkey = if enable { 0u32 } else { 0x50D8_3AA1u32 };
reg_block
.wdtwprotect()
.write(|w| unsafe { w.wdt_wkey().bits(wkey) });
}
pub fn set_timeout(&mut self, stage: MwdtStage, timeout: Duration) {
let clk_src = clocks::ClockTree::with(|clocks| {
cfg_if::cfg_if! {
if #[cfg(soc_has_clock_node_timg_wdt_clock)] {
Rate::from_hz(TG::clock_instance().wdt_clock_frequency(clocks))
} else {
Rate::from_hz(clocks::apb_clk_frequency(clocks))
}
}
});
let timeout_ticks = timeout.as_micros() * clk_src.as_mhz() as u64;
let reg_block = unsafe { &*TG::register_block() };
let (prescaler, timeout) = if timeout_ticks > u32::MAX as u64 {
let prescaler = timeout_ticks
.div_ceil(u32::MAX as u64 + 1)
.min(u16::MAX as u64) as u16;
let timeout = timeout_ticks
.div_ceil(prescaler as u64)
.min(u32::MAX as u64);
(prescaler, timeout as u32)
} else {
(1, timeout_ticks as u32)
};
self.set_write_protection(false);
reg_block.wdtconfig1().write(|w| unsafe {
#[cfg(timergroup_timg_has_divcnt_rst)]
w.wdt_divcnt_rst().set_bit();
w.wdt_clk_prescale().bits(prescaler)
});
let config_register = match stage {
MwdtStage::Stage0 => reg_block.wdtconfig2(),
MwdtStage::Stage1 => reg_block.wdtconfig3(),
MwdtStage::Stage2 => reg_block.wdtconfig4(),
MwdtStage::Stage3 => reg_block.wdtconfig5(),
};
config_register.write(|w| unsafe { w.hold().bits(timeout) });
#[cfg(any(esp32c2, esp32c3, esp32c6))]
reg_block
.wdtconfig0()
.modify(|_, w| w.wdt_conf_update_en().set_bit());
self.set_write_protection(true);
}
pub fn set_stage_action(&mut self, stage: MwdtStage, action: MwdtStageAction) {
let reg_block = unsafe { &*TG::register_block() };
self.set_write_protection(false);
reg_block.wdtconfig0().modify(|_, w| unsafe {
match stage {
MwdtStage::Stage0 => w.wdt_stg0().bits(action as u8),
MwdtStage::Stage1 => w.wdt_stg1().bits(action as u8),
MwdtStage::Stage2 => w.wdt_stg2().bits(action as u8),
MwdtStage::Stage3 => w.wdt_stg3().bits(action as u8),
}
});
self.set_write_protection(true);
}
}
impl<TG> crate::private::Sealed for Wdt<TG> where TG: TimerGroupInstance {}
impl<TG> InterruptConfigurable for Wdt<TG>
where
TG: TimerGroupInstance,
{
fn set_interrupt_handler(&mut self, handler: interrupt::InterruptHandler) {
let interrupt = TG::wdt_interrupt();
interrupt::bind_handler(interrupt, handler);
}
}
impl<TG> Default for Wdt<TG>
where
TG: TimerGroupInstance,
{
fn default() -> Self {
Self::new()
}
}
mod asynch {
use procmacros::handler;
use super::*;
use crate::asynch::AtomicWaker;
const NUM_WAKERS: usize = {
let timer_per_group = 1 + cfg!(timergroup_timg_has_timer1) as usize;
NUM_TIMG * timer_per_group
};
static WAKERS: [AtomicWaker; NUM_WAKERS] = [const { AtomicWaker::new() }; NUM_WAKERS];
pub(super) fn waker(timer: &Timer<'_>) -> &'static AtomicWaker {
let index = (timer.timer_number() << 1) | timer.timer_group();
&WAKERS[index as usize]
}
#[inline]
fn handle_irq(timer: Timer<'_>) {
timer.set_interrupt_enabled(false);
waker(&timer).wake();
}
#[handler]
pub(crate) fn timg0_timer0_handler() {
handle_irq(Timer {
register_block: TIMG0::regs(),
_lifetime: PhantomData,
timer: TimerId::Timer0,
tg: 0,
});
}
#[cfg(timergroup_timg1)]
#[handler]
pub(crate) fn timg1_timer0_handler() {
handle_irq(Timer {
register_block: TIMG1::regs(),
_lifetime: PhantomData,
timer: TimerId::Timer0,
tg: 1,
});
}
#[cfg(timergroup_timg_has_timer1)]
#[handler]
pub(crate) fn timg0_timer1_handler() {
handle_irq(Timer {
register_block: TIMG0::regs(),
_lifetime: PhantomData,
timer: TimerId::Timer1,
tg: 0,
});
}
#[cfg(all(timergroup_timg1, timergroup_timg_has_timer1))]
#[handler]
pub(crate) fn timg1_timer1_handler() {
handle_irq(Timer {
register_block: TIMG1::regs(),
_lifetime: PhantomData,
timer: TimerId::Timer1,
tg: 1,
});
}
}
#[cfg(etm_driver_supported)]
pub mod etm {
use super::*;
use crate::etm::{EtmEvent, EtmTask};
pub struct Event {
id: u8,
}
pub struct Task {
id: u8,
}
impl EtmEvent for Event {
fn id(&self) -> u8 {
self.id
}
}
impl Sealed for Event {}
impl EtmTask for Task {
fn id(&self) -> u8 {
self.id
}
}
impl Sealed for Task {}
pub trait Events {
fn on_alarm(&self) -> Event;
}
pub trait Tasks {
fn cnt_start(&self) -> Task;
fn cnt_stop(&self) -> Task;
fn cnt_reload(&self) -> Task;
fn cnt_cap(&self) -> Task;
fn alarm_start(&self) -> Task;
}
impl Events for Timer<'_> {
fn on_alarm(&self) -> Event {
Event {
id: 48 + self.timer_group(),
}
}
}
impl Tasks for Timer<'_> {
fn cnt_start(&self) -> Task {
Task {
id: 88 + self.timer_group(),
}
}
fn alarm_start(&self) -> Task {
Task {
id: 90 + self.timer_group(),
}
}
fn cnt_stop(&self) -> Task {
Task {
id: 92 + self.timer_group(),
}
}
fn cnt_reload(&self) -> Task {
Task {
id: 94 + self.timer_group(),
}
}
fn cnt_cap(&self) -> Task {
Task {
id: 96 + self.timer_group(),
}
}
}
}