atsamd-hal 0.23.3

HAL and Peripheral access API for ATSAMD11, ATSAMD21, ATSAMD51, ATSAME51, ATSAME53 and ATSAME54 microcontrollers
Documentation
//! Async APIs for timers.
//!
//! Use [`TimerCounter::into_future`] to convert a regular [`TimerCounter`] into
//! an asynchronous [`TimerFuture`].

use crate::{
    async_hal::interrupts::{Binding, Handler, Interrupt},
    pac,
    timer_traits::InterruptDrivenTimer,
    typelevel::Sealed,
};
use atsamd_hal_macros::hal_cfg;
use core::{
    future::poll_fn,
    marker::PhantomData,
    sync::atomic::Ordering,
    task::{Poll, Waker},
};
use embassy_sync::waitqueue::AtomicWaker;
use fugit::NanosDurationU32;
use portable_atomic::AtomicBool;

use crate::peripherals::timer;

#[hal_cfg("tc1")]
#[allow(unused_imports)]
use crate::pac::Tc1;

#[hal_cfg("tc2")]
#[allow(unused_imports)]
use crate::pac::Tc2;

#[hal_cfg("tc3")]
#[allow(unused_imports)]
use crate::pac::Tc3;

#[hal_cfg("tc4")]
#[allow(unused_imports)]
use crate::pac::Tc4;

#[hal_cfg("tc5")]
#[allow(unused_imports)]
use crate::pac::Tc5;

use timer::{Count16, TimerCounter};

#[hal_cfg("tc1-d11")]
type RegBlock = pac::tc1::RegisterBlock;

#[hal_cfg("tc3-d21")]
type RegBlock = pac::tc3::RegisterBlock;

#[hal_cfg("tc1-d5x")]
type RegBlock = pac::tc0::RegisterBlock;

/// Trait enabling the use of a Timer/Counter in async mode. Specifically, this
/// trait enables us to register a `TC*` interrupt as a waker for timer futures.
///
/// **⚠️ Warning** This trait should not be implemented outside of this crate!
pub trait AsyncCount16: Count16 + Sealed {
    /// Index of this TC in the `STATE` tracker
    const STATE_ID: usize;

    /// Get a reference to the timer's register block
    fn reg_block(peripherals: &pac::Peripherals) -> &RegBlock;

    /// Interrupt type for this timer
    type Interrupt: Interrupt;
}

/// Interrupt handler for async timer operarions
pub struct InterruptHandler<T: AsyncCount16> {
    _private: (),
    _tc: PhantomData<T>,
}

impl<T: AsyncCount16> crate::typelevel::Sealed for InterruptHandler<T> {}

impl<A: AsyncCount16> Handler<A::Interrupt> for InterruptHandler<A> {
    /// Callback function when the corresponding TC interrupt is fired
    ///
    /// # Safety
    ///
    /// This method may [`steal`](crate::pac::Peripherals::steal) the `TC`
    /// peripheral instance to check the interrupt flags. The only
    /// modifications it is allowed to apply to the peripheral is to clear
    /// the interrupt flag (to prevent re-firing). This method should ONLY be
    /// able to be called while a [`TimerFuture`] holds an unique reference
    /// to the underlying `TC` peripheral.
    unsafe fn on_interrupt() {
        let periph = unsafe { crate::pac::Peripherals::steal() };
        let tc = A::reg_block(&periph);
        let intflag = &tc.count16().intflag();

        if intflag.read().ovf().bit_is_set() {
            // Clear the flag
            intflag.modify(|_, w| w.ovf().set_bit());
            STATE[A::STATE_ID].wake();
        }
    }
}

macro_rules! impl_async_count16 {
    ($TC: ident, $id: expr) => {
        paste::paste! {
            impl AsyncCount16 for $TC {
                const STATE_ID: usize = $id;

                type Interrupt = crate::async_hal::interrupts::[< $TC:upper >];

                fn reg_block(peripherals: &pac::Peripherals) -> &RegBlock {
                    &*peripherals.[< $TC:lower >]
                }
            }

            impl Sealed for $TC {}
        }
    };
}

#[hal_cfg("tc1-d11")]
impl_async_count16!(Tc1, 0);

#[hal_cfg("tc3-d21")]
impl_async_count16!(Tc3, 0);

#[hal_cfg("tc4-d21")]
impl_async_count16!(Tc4, 1);

#[hal_cfg("tc5-d21")]
impl_async_count16!(Tc5, 2);

#[hal_cfg("tc2-d5x")]
impl_async_count16!(Tc2, 0);

#[hal_cfg("tc3-d5x")]
impl_async_count16!(Tc3, 1);

#[hal_cfg("tc4-d5x")]
impl_async_count16!(Tc4, 2);

#[hal_cfg("tc5-d5x")]
impl_async_count16!(Tc5, 3);

// Reserve space for the max number of timer peripherals based on chip type,
// even though some wakers may not be used on some chips if they actually don't
// exist on variant's hardware
#[hal_cfg("tc1-d11")]
const NUM_TIMERS: usize = 1;

#[hal_cfg("tc3-d21")]
const NUM_TIMERS: usize = 3;

#[hal_cfg("tc3-d5x")]
const NUM_TIMERS: usize = 4;

impl<T> TimerCounter<T>
where
    T: AsyncCount16,
{
    /// Transform a [`TimerCounter`] into an [`TimerFuture`]
    #[inline]
    pub fn into_future<I>(mut self, _irq: I) -> TimerFuture<T>
    where
        I: Binding<T::Interrupt, InterruptHandler<T>>,
    {
        T::Interrupt::unpend();
        unsafe { T::Interrupt::enable() };
        self.enable_interrupt();

        TimerFuture { timer: self }
    }
}

/// Wrapper around a [`TimerCounter`] with an `async` interface
pub struct TimerFuture<T>
where
    T: AsyncCount16,
{
    timer: TimerCounter<T>,
}

impl<T> TimerFuture<T>
where
    T: AsyncCount16,
{
    /// Delay asynchronously
    #[inline]
    pub async fn delay(&mut self, count: NanosDurationU32) {
        self.timer.start(count);
        self.timer.enable_interrupt();

        poll_fn(|cx| {
            STATE[T::STATE_ID].register(cx.waker());
            if STATE[T::STATE_ID].ready() {
                return Poll::Ready(());
            }

            Poll::Pending
        })
        .await;
    }
}

impl<T> Drop for TimerFuture<T>
where
    T: AsyncCount16,
{
    #[inline]
    fn drop(&mut self) {
        T::Interrupt::disable();
    }
}

impl<T> embedded_hal_async::delay::DelayNs for TimerFuture<T>
where
    T: AsyncCount16,
{
    async fn delay_ns(&mut self, ns: u32) {
        self.delay(NanosDurationU32::from_ticks(ns).convert()).await;
    }
}

// TODO instead of tracking the state manually, we could use ONESHOT
// mode and check the STATUS.STOP bit
struct State {
    waker: AtomicWaker,
    ready: AtomicBool,
}

impl State {
    #[inline]
    const fn new() -> Self {
        Self {
            waker: AtomicWaker::new(),
            ready: AtomicBool::new(false),
        }
    }

    #[inline]
    fn register(&self, waker: &Waker) {
        self.waker.register(waker)
    }

    #[inline]
    fn wake(&self) {
        self.ready.store(true, Ordering::SeqCst);
        self.waker.wake()
    }

    #[inline]
    fn ready(&self) -> bool {
        self.ready.swap(false, Ordering::SeqCst)
    }
}

#[allow(clippy::declare_interior_mutable_const)]
const STATE_NEW: State = State::new();
static STATE: [State; NUM_TIMERS] = [STATE_NEW; NUM_TIMERS];