psoc-drivers 0.1.0

Hardware driver implementations for psoc-rs
//! An embasy-time driver using a multi-counter watchdog timer.
//! Provides a 63-bit timestamp which monotonically increases at 32,768 Hz,
//! and the ability to set alarms.
// Copyright (c) 2026, Infineon Technologies AG or an affiliate of Infineon Technologies AG.
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
// in compliance with the License. You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing permissions and
// limitations under the License.

use core::{cell::UnsafeCell, fmt::Debug};
use portable_atomic::{AtomicU32, Ordering};

use critical_section::CriticalSection;
use embassy_time_driver::{Driver, time_driver_impl};
use embassy_time_queue_utils::Queue;

use crate::interrupt;
use crate::sys::watchdog::mcwdt::{
    AnyUnlockedMultiWdt, CascadeMode, Config, Control, InterruptSet, MatchAction, MultiWdt,
    SubCounter,
};

/// Initializes the embassy-time timer.
pub fn init<const N: u8>(wdt: MultiWdt<N>)
where
    MultiWdt<N>: McwdtInterrupt,
{
    // Set counter_hi to u32::MAX-1 to indicate to others that we're currently initializing the timer.
    // ORDERING: relaxed ordering is OK, the only guarantee we need is that only one caller
    // succeeds at the compare-exchange
    DRIVER
        .counter_hi
        .compare_exchange(u32::MAX, u32::MAX - 1, Ordering::Relaxed, Ordering::Relaxed)
        .expect("Time driver already initialized");

    let mut wdt = wdt.into_unlocked();
    wdt.set_config(Config {
        mode_16bit_0: MatchAction::Interrupt,
        mode_16bit_1: MatchAction::Interrupt,
        interrupt_bit_32bit: Some(31),
        cascade_0_1: CascadeMode::CarryOnMatch,
        ..Default::default()
    });
    wdt.set_control(Control {
        reset_16bit_0: true,
        enable_16bit_0: true,
        reset_16bit_1: true,
        enable_16bit_1: true,
        reset_32bit: true,
        enable_32bit: true,
    });

    // Wait for the counters to reset and enable.
    wdt.wait_control();

    wdt.set_enabled_interrupts(InterruptSet::new());
    wdt.clear_interrupts(InterruptSet::all());

    unsafe {
        DRIVER
            .mcwdt
            .get()
            .as_mut_unchecked()
            .replace(wdt.into_erased());
    }

    // Set the current timestamp to 0, indicating that the timer is ready.
    // ORDERING: we want release ordering so that someone who tries to access the timer is
    // synchronized after its initialization
    DRIVER.counter_hi.store(0, Ordering::Release);

    MultiWdt::<N>::enable();
}

/// embassy-time driver using a multi-counter watchdog timer.
struct TimeDriver {
    /// Bits 31-62 of the counter.
    /// Note that we mirror the high bit of the WDT counter so that we can detect and handle the
    /// case where the 32-bit counter overflows while interrupts are disabled.
    ///
    /// A value of u32::MAX indicates that the time driver has not yet been initialized.
    /// A value of u32::MAX - 1indicates that the time driver is currently being initialized.
    counter_hi: AtomicU32,

    mcwdt: UnsafeCell<Option<AnyUnlockedMultiWdt<'static>>>,

    queue: UnsafeCell<Queue>,
}
unsafe impl Send for TimeDriver {}
unsafe impl Sync for TimeDriver {}

time_driver_impl!(static DRIVER: TimeDriver = TimeDriver::new());

impl TimeDriver {
    const fn new() -> TimeDriver {
        TimeDriver {
            counter_hi: AtomicU32::new(u32::MAX),
            mcwdt: UnsafeCell::new(None),
            queue: UnsafeCell::new(Queue::new()),
        }
    }
    /// Accesses the watchdog timer.
    ///
    /// # Safety
    ///
    /// The caller is responsible for ensuring the MCWDT is initialized
    /// and preventing invalid concurrent access to the MCWDT registers.
    unsafe fn wdt(&'_ self) -> AnyUnlockedMultiWdt<'_> {
        unsafe {
            self.mcwdt
                .get()
                .as_mut_unchecked()
                .as_mut()
                .unwrap_unchecked()
                .reborrow()
        }
    }
}

impl Driver for TimeDriver {
    fn now(&self) -> u64 {
        unsafe {
            // Load the high counter value first. That way, we will only ever see counter_hi lag
            // behind counter_lo -- it's not possible to see a value of counter_hi that is newer
            // than counter_lo
            // ORDERING: load-aquire to sync with the store-release in init (so we don't access the
            // WDT before it's ready), and to sync with the fetch-add-release in rollover_interrupt
            // (so that we can't see a value of counter_hi from after an interrupt while seeing a
            // value of counter_lo from before the interrupt)
            let mut counter_hi = self.counter_hi.load(Ordering::Acquire);
            if counter_hi & (1 << 31) != 0 {
                panic!(
                    "Time driver not initializedd\n  help: call TimeDriver::init() before using time functions"
                );
            }
            let counter_lo = self.wdt().counter_32bit();

            // counter_hi and counter_lo overlap by one bit. An interrupt fires whenever the top
            // bit of counter_lo toggles, to increment counter_hi. Thus, these bits will nearly
            // always be the same.
            // The only case where they differ is if the top bit of counter_lo recently toggled but
            // the value we have for counter_hi is not up to date to reflect that.
            //
            // If those bits are the same, the value of counter_hi is correct. If those bits
            // differ, then counter_hi needs to be incremented.

            counter_hi += (counter_hi & 1) ^ (counter_lo >> 31);
            counter_hi >>= 1;
            ((counter_hi as u64) << 32) | counter_lo as u64
        }
    }

    fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
        unsafe {
            critical_section::with(|cs| {
                if at & (1 << 63) != 0 {
                    // This alarm is too far in the future.
                    return;
                }

                // SAFETY: we're in a critical section,
                // so we have exclusive access to the queue
                let queue = &mut *self.queue.get();
                if !queue.schedule_wake(at, waker) {
                    return;
                }

                self.update_alarm(cs);
            });
        }
    }
}

impl TimeDriver {
    unsafe fn handle_interrupt(&self) {
        unsafe {
            let interrupts = DRIVER.wdt().masked_interrupts();
            self.wdt().clear_interrupts(InterruptSet::all());
            // "Note: When the watchdog counters are configured to generate an interrupt every LFCLK
            // cycle, make sure you read the MCWDTx_INTR register after clearing the watchdog
            // interrupt. Failure to do thhis may result in missing the next interrupt."
            self.wdt().requested_interrupts();

            if interrupts.contains(SubCounter::_16bit_0)
                || interrupts.contains(SubCounter::_16bit_1)
            {
                critical_section::with(|cs| self.update_alarm(cs));
            } else if interrupts.contains(SubCounter::_32bit) {
                self.rollover_interrupt();
            }
        }
    }

    unsafe fn rollover_interrupt(&self) {
        // ORDERING: relaxed ordering is OK for load, the interrupt handler can only trigger after
        // the WDT is properly initialized
        // Release ordering for store so we're synchronized with the load-acquire in `now`
        self.counter_hi.fetch_add(1, Ordering::Release);
    }

    fn update_alarm(&self, _cs: CriticalSection) {
        // SAFETY: critical section ensures exclusive access to the WDT
        let mut wdt = unsafe { self.wdt() };

        // SAFETY: critical section ensures exclusive access to the queue
        let queue = unsafe { &mut *self.queue.get() };

        let mut now = self.now();
        let mut timestamp = queue.next_expiration(now);

        loop {
            if timestamp >= (1 << 63) {
                // If this timestamp is beyond the precision of our clock, don't schedule anything.
                wdt.set_enabled_interrupts(SubCounter::_32bit);
                break;
            }
            if timestamp < now + 0x2_0000 {
                // The alarm is within the current 16-bit clock period, or the next one.
                // (If it's actually in the next one, we may cause a spurious interrupt, but that's
                // OK.)

                // Subtract 1 from the timestamp because the interrupt occurs the cycle after the match.
                wdt.set_match_16bit((timestamp as u16).saturating_sub(1), 0);
                wdt.clear_interrupts(SubCounter::_16bit_0);
                wdt.set_enabled_interrupts(
                    InterruptSet::new()
                        .insert(SubCounter::_16bit_0)
                        .insert(SubCounter::_32bit),
                );

                now = self.now();

                if timestamp < now + 3 {
                    // The reported timestamp lags the actual timestamp by up to 1 LFCLK cycle, and the
                    // WDT_MATCH takes up to 2 LFCLK cycles to update. Therefore, if this timestamp is
                    // too soon in the near future, we need to delay it by 3 clock cycles (up to about 92us).
                    timestamp = now + 3;
                    continue;
                } else {
                    break;
                }
            } else {
                // The alarm is in a future 16-bit clock period.
                wdt.set_control(Control {
                    enable_16bit_0: true,
                    enable_16bit_1: true,
                    enable_32bit: true,

                    reset_16bit_1: true,
                    ..Default::default()
                });

                // The cascaded counter increments two cycles after the prescalar counter.
                let target = timestamp - 2;
                // It takes two cycles to update the match value, and now lags the actual counter
                // value by up to one cycle. If the first match would have triggered within three
                // cycles of now, skip this match so we get a possible spurious interrupt instead
                // of a possible missed wakeup.
                let periods = (target - now - 3) >> 16;

                let match0 = target as u16;
                let match1 = periods.try_into().unwrap_or(u16::MAX);
                wdt.set_match_16bit(match0, match1);

                wdt.clear_interrupts(SubCounter::_16bit_1);
                wdt.set_enabled_interrupts(
                    InterruptSet::new()
                        .insert(SubCounter::_16bit_1)
                        .insert(SubCounter::_32bit),
                );

                // If enough time has elapsed that the counter 1 matched value could have changed,
                // try again.
                now = self.now();
                let new_periods = (target - now - 3) >> 16;
                if periods != new_periods {
                    continue;
                } else {
                    break;
                }
            }
        }

        // TRM states we must not enter deep sleep "for at least one LFCLK cycle" after configuring
        // the timer match values, but this is not actually necessary (CDT 342292)
    }
}

impl Debug for TimeDriver {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("TimeDriver")
            .field("now", &self.now())
            .finish()
    }
}

#[doc(hidden)]
pub trait McwdtInterrupt {
    fn enable();
}

macro_rules! impl_interrupt {
    ($n:literal) => {
        paste::paste! {
            impl McwdtInterrupt for MultiWdt<$n> {
                fn enable() {
                    interrupt::unmask(interrupt::Interrupt::[<SRSS_INTERRUPT_MCWDT_ $n>]);
                    psoc_macros::require_interrupt!([<SRSS_INTERRUPT_MCWDT_ $n>]);
                }
            }

            #[psoc_macros::optional_interrupt]
            fn [<SRSS_INTERRUPT_MCWDT_ $n>]() {
                unsafe { DRIVER.handle_interrupt() }
            }
        }
    };
}

#[cfg(mcwdt0)]
impl_interrupt!(0);

#[cfg(mcwdt1)]
impl_interrupt!(1);