psoc-drivers 0.1.0

Hardware driver implementations for psoc-rs
// 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::marker::PhantomData;

use crate::{
    regs::{self, srss::wdt_ctl},
    security::{self, Security},
};

/// The watchdog timer.
///
/// The WDT has a free-running
#[cfg_attr(mxs40srss, doc = "16-bit up-counter,")]
#[cfg_attr(mxs40ssrss, doc = "32-bit up-counter,")]
/// a configurable match value, and a configurable number of bits to compare. When the lowest N
/// bits of the counter are equal to the match value, an interrupt is raised. If two interrupts are
/// raised without being clearing by firmware the third interrupt generates a device reset.
///
#[non_exhaustive]
#[derive(Debug)]
pub struct Wdt<Sec: Security = security::Default>(Sec);

cfg_select! {
    mxs40srss => {
        /// The type of the WDT counter.
        pub type Counter = u16;
    }
    mxs40ssrss => {
        /// The type of the WDT counter.
        pub type Counter = u32;
    },
}

impl Wdt {
    /// Unsafely creates the WDT driver instance.
    ///
    /// # Safety
    ///
    /// The caller is responsible for ensuring the hardware is present on the device, configured
    /// correctly, and not accessed concurrently.
    pub unsafe fn steal() -> Self {
        Self(security::DEFAULT)
    }
}

impl<Sec: Security> Wdt<Sec> {
    /// Changes the security attribute which will be used to access the WDT registers.
    pub const fn with_security<New: Security>(self, new: New) -> Wdt<New> {
        Wdt(new)
    }

    /// Unlocks the watchdog timer, allowing writes to its configuration registers.
    pub fn unlock(&mut self) -> UnlockedWdt<'_, Sec> {
        // The low-frequency clocks that feed the WDT are protected by the WDT lock. This means
        // that the sysclock driver sometimes need to unlock the WDT. Thus, access to WDT_CTL must
        // be atomic, even though we have an &mut Wdt.
        critical_section::with(|_cs| unsafe {
            let wdt_ctl = regs::SRSS.wdt_ctl().read();
            regs::SRSS
                .wdt_ctl()
                .write(wdt_ctl.wdt_lock().set(wdt_ctl::WdtLock::CLR_0));
            regs::SRSS
                .wdt_ctl()
                .write(wdt_ctl.wdt_lock().set(wdt_ctl::WdtLock::CLR_1));
        });
        UnlockedWdt(Self(self.0), PhantomData)
    }

    /// Unlocks the watchdog timer, consuming `self`.
    pub fn into_unlocked(mut self) -> UnlockedWdt<'static, Sec> {
        self.unlock();
        UnlockedWdt(Self(self.0), PhantomData)
    }
}

/// A WDT that has been unlocked, allowing writes to its configuration registers.
#[derive(Debug)]
pub struct UnlockedWdt<'a, Sec: Security = security::Default>(Wdt<Sec>, PhantomData<&'a mut Wdt>);

/// Clock source for the WDT.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
#[cfg(mxs40ssrss)]
pub enum ClockSource {
    /// Internal low-speed oscillator.
    Ilo = 0,
    /// Backup domain clock source. Note that the backup domain is not protected by the WDT lock.
    Backup = 2,
}

impl<'a, Sec: Security> UnlockedWdt<'a, Sec> {
    /// Changes the security attribute which will be used to access the WDT registers.
    pub const fn with_security<New: Security>(self, new: New) -> UnlockedWdt<'a, New> {
        UnlockedWdt(Wdt(new), PhantomData)
    }

    /// Sets the WDT clock source.
    #[cfg(mxs40ssrss)]
    pub fn set_clock_source(&mut self, clock_source: ClockSource) {
        critical_section::with(|_cs| unsafe {
            regs::SRSS.wdt_ctl().modify(|r| {
                r.wdt_clk_sel()
                    .set(wdt_ctl::WdtClkSel::new(clock_source as u8))
            })
        })
    }

    /// Returns the current WDT clock source.
    #[cfg(mxs40ssrss)]
    pub fn clock_source(&self) -> ClockSource {
        critical_section::with(|_cs| unsafe {
            core::mem::transmute::<u8, ClockSource>(
                regs::SRSS.wdt_ctl().read().wdt_clk_sel().get().0,
            )
        })
    }

    /// Enables or disables the watchdog timer.
    ///
    /// Once enabled, the WDT counts continuously and generates an interrupt each time the counter
    /// matches [`match_value`][Self::match_value]. Two consecutive unserviced interrupts cause a
    /// device reset on the third match.
    ///
    /// The WDT is enabled by default.
    pub fn set_enabled(&mut self, enabled: bool) {
        critical_section::with(|_cs| unsafe {
            regs::SRSS.wdt_ctl().modify(|r| r.wdt_en().set(enabled))
        })
    }

    /// Returns `true` if the watchdog timer is enabled.
    pub fn enabled(&self) -> bool {
        critical_section::with(|_cs| unsafe { regs::SRSS.wdt_ctl().read().wdt_en().get() })
    }

    /// Sets the match value for the watchdog counter.
    ///
    /// An interrupt is generated each time the counter equals this value. Two consecutive
    /// unserviced interrupts cause a device reset on the third match. Upper counter bits may be
    /// excluded from the comparison; see [`set_matched_bits`][Self::set_matched_bits].
    pub fn set_match_value(&mut self, value: Counter) {
        unsafe { regs::SRSS.wdt_match().modify(|r| r.r#match().set(value)) }
    }

    /// Returns the current match value for the watchdog counter.
    pub fn match_value(&self) -> Counter {
        unsafe { regs::SRSS.wdt_match().read().r#match().get() }
    }

    /// Sets the index of the highest counter bit included in the match comparison.
    ///
    /// Counter bits above `msb` are not checked against the match value, effectively masking
    /// the counter  before comparison.
    ///
    /// The four LSBs are always included; values below 3 behave identically to a setting of 3.
    pub fn set_matched_bits(&mut self, msb: u8) {
        cfg_select! {
            mxs40ssrss => {
                debug_assert!(msb <= 31, "msb must be at most 31");
                unsafe {
                    regs::SRSS
                        .wdt_match2()
                        .modify(|r| r.ignore_bits_above().set(msb))
                }
            }
            mxs40srss => {
                debug_assert!(msb <= 15, "msb must be at most 15");
                unsafe {
                    regs::SRSS
                        .wdt_match()
                        .modify(|r| r.ignore_bits().set(15 - msb))
                }
            }
        }
    }

    /// Returns the index of the highest counter bit included in the match comparison.
    ///
    /// See [`set_matched_bits`][Self::set_matched_bits] for details.
    pub fn matched_bits(&self) -> u8 {
        cfg_select! {
            mxs40ssrss => {
                unsafe { regs::SRSS.wdt_match2().read().ignore_bits_above().get() }
            }
            mxs40srss => {
                15 - unsafe { regs::SRSS.wdt_match().read().ignore_bits().get() }
            }
        }
    }

    /// Locks the WDT.
    pub fn lock(self) {
        critical_section::with(|_cs| unsafe {
            regs::SRSS
                .wdt_ctl()
                .modify(|r| r.wdt_lock().set(wdt_ctl::WdtLock::SET_01))
        })
    }
}

impl UnlockedWdt<'static> {
    /// Consumes `self` and returns a locked WDT.
    pub fn into_locked(self) -> Wdt {
        self.lock();
        unsafe { Wdt::steal() }
    }
}