//! Low-level timer driver.
//!
//! This is an unopinionated, very low-level driver for all STM32 timers. It allows direct register
//! manipulation with the `regs_*()` methods, and has utility functions that are thin wrappers
//! over the registers.
//!
//! The available functionality depends on the timer type.

use core::mem::ManuallyDrop;

use embassy_hal_internal::Peri;
// Re-export useful enums
pub use stm32_metapac::timer::vals::{FilterValue, Mms as MasterMode, Sms as SlaveMode, Ts as TriggerSource};

use super::*;
use crate::dma::{self, Transfer, WritableRingBuffer};
use crate::pac::timer::vals;
use crate::rcc;
use crate::time::Hertz;

/// Input capture mode.
#[derive(Clone, Copy)]
pub enum InputCaptureMode {
    /// Rising edge only.
    Rising,
    /// Falling edge only.
    Falling,
    /// Both rising or falling edges.
    BothEdges,
}

/// Input TI selection.
#[derive(Clone, Copy)]
pub enum InputTISelection {
    /// Normal
    Normal,
    /// Alternate
    Alternate,
    /// TRC
    TRC,
}

impl From<InputTISelection> for stm32_metapac::timer::vals::CcmrInputCcs {
    fn from(tisel: InputTISelection) -> Self {
        match tisel {
            InputTISelection::Normal => stm32_metapac::timer::vals::CcmrInputCcs::TI4,
            InputTISelection::Alternate => stm32_metapac::timer::vals::CcmrInputCcs::TI3,
            InputTISelection::TRC => stm32_metapac::timer::vals::CcmrInputCcs::TRC,
        }
    }
}

/// Timer counting mode.
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CountingMode {
    #[default]
    /// The timer counts up to the reload value and then resets back to 0.
    EdgeAlignedUp,
    /// The timer counts down to 0 and then resets back to the reload value.
    EdgeAlignedDown,
    /// The timer counts up to the reload value and then counts back to 0.
    ///
    /// The output compare interrupt flags of channels configured in output are
    /// set when the counter is counting down.
    CenterAlignedDownInterrupts,
    /// The timer counts up to the reload value and then counts back to 0.
    ///
    /// The output compare interrupt flags of channels configured in output are
    /// set when the counter is counting up.
    CenterAlignedUpInterrupts,
    /// The timer counts up to the reload value and then counts back to 0.
    ///
    /// The output compare interrupt flags of channels configured in output are
    /// set when the counter is counting both up or down.
    CenterAlignedBothInterrupts,
}

impl CountingMode {
    /// Return whether this mode is edge-aligned (up or down).
    pub fn is_edge_aligned(&self) -> bool {
        matches!(self, CountingMode::EdgeAlignedUp | CountingMode::EdgeAlignedDown)
    }

    /// Return whether this mode is center-aligned.
    pub fn is_center_aligned(&self) -> bool {
        matches!(
            self,
            CountingMode::CenterAlignedDownInterrupts
                | CountingMode::CenterAlignedUpInterrupts
                | CountingMode::CenterAlignedBothInterrupts
        )
    }
}

impl From<CountingMode> for (vals::Cms, vals::Dir) {
    fn from(value: CountingMode) -> Self {
        match value {
            CountingMode::EdgeAlignedUp => (vals::Cms::EDGE_ALIGNED, vals::Dir::UP),
            CountingMode::EdgeAlignedDown => (vals::Cms::EDGE_ALIGNED, vals::Dir::DOWN),
            CountingMode::CenterAlignedDownInterrupts => (vals::Cms::CENTER_ALIGNED1, vals::Dir::UP),
            CountingMode::CenterAlignedUpInterrupts => (vals::Cms::CENTER_ALIGNED2, vals::Dir::UP),
            CountingMode::CenterAlignedBothInterrupts => (vals::Cms::CENTER_ALIGNED3, vals::Dir::UP),
        }
    }
}

impl From<(vals::Cms, vals::Dir)> for CountingMode {
    fn from(value: (vals::Cms, vals::Dir)) -> Self {
        match value {
            (vals::Cms::EDGE_ALIGNED, vals::Dir::UP) => CountingMode::EdgeAlignedUp,
            (vals::Cms::EDGE_ALIGNED, vals::Dir::DOWN) => CountingMode::EdgeAlignedDown,
            (vals::Cms::CENTER_ALIGNED1, _) => CountingMode::CenterAlignedDownInterrupts,
            (vals::Cms::CENTER_ALIGNED2, _) => CountingMode::CenterAlignedUpInterrupts,
            (vals::Cms::CENTER_ALIGNED3, _) => CountingMode::CenterAlignedBothInterrupts,
        }
    }
}

/// Output compare mode.
#[derive(Clone, Copy)]
pub enum OutputCompareMode {
    /// The comparison between the output compare register TIMx_CCRx and
    /// the counter TIMx_CNT has no effect on the outputs.
    /// (this mode is used to generate a timing base).
    Frozen,
    /// Set channel to active level on match. OCxREF signal is forced high when the
    /// counter TIMx_CNT matches the capture/compare register x (TIMx_CCRx).
    ActiveOnMatch,
    /// Set channel to inactive level on match. OCxREF signal is forced low when the
    /// counter TIMx_CNT matches the capture/compare register x (TIMx_CCRx).
    InactiveOnMatch,
    /// Toggle - OCxREF toggles when TIMx_CNT=TIMx_CCRx.
    Toggle,
    /// Force inactive level - OCxREF is forced low.
    ForceInactive,
    /// Force active level - OCxREF is forced high.
    ForceActive,
    /// PWM mode 1 - In upcounting, channel is active as long as TIMx_CNT<TIMx_CCRx
    /// else inactive. In downcounting, channel is inactive (OCxREF=0) as long as
    /// TIMx_CNT>TIMx_CCRx else active (OCxREF=1).
    PwmMode1,
    /// PWM mode 2 - In upcounting, channel is inactive as long as
    /// TIMx_CNT<TIMx_CCRx else active. In downcounting, channel is active as long as
    /// TIMx_CNT>TIMx_CCRx else inactive.
    PwmMode2,

    #[cfg(timer_v2)]
    /// In up-counting mode, the channel is active until a trigger
    /// event is detected (on tim_trgi signal). Then, a comparison is performed as in PWM
    /// mode 1 and the channels becomes active again at the next update. In down-counting
    /// mode, the channel is inactive until a trigger event is detected (on tim_trgi signal).
    /// Then, a comparison is performed as in PWM mode 1 and the channels becomes
    /// inactive again at the next update.
    OnePulseMode1,

    #[cfg(timer_v2)]
    /// In up-counting mode, the channel is inactive until a
    /// trigger event is detected (on tim_trgi signal). Then, a comparison is performed as in
    /// PWM mode 2 and the channels becomes inactive again at the next update. In down
    /// counting mode, the channel is active until a trigger event is detected (on tim_trgi
    /// signal). Then, a comparison is performed as in PWM mode 1 and the channels
    /// becomes active again at the next update.
    OnePulseMode2,

    #[cfg(timer_v2)]
    /// Combined PWM mode 1 - tim_oc1ref has the same behavior as in PWM mode 1.
    /// tim_oc1refc is the logical OR between tim_oc1ref and tim_oc2ref.
    CombinedPwmMode1,

    #[cfg(timer_v2)]
    /// Combined PWM mode 2 - tim_oc1ref has the same behavior as in PWM mode 2.
    /// tim_oc1refc is the logical AND between tim_oc1ref and tim_oc2ref.
    CombinedPwmMode2,

    #[cfg(timer_v2)]
    /// tim_oc1ref has the same behavior as in PWM mode 1. tim_oc1refc outputs tim_oc1ref
    /// when the counter is counting up, tim_oc2ref when it is counting down.
    AsymmetricPwmMode1,

    #[cfg(timer_v2)]
    /// tim_oc1ref has the same behavior as in PWM mode 2. tim_oc1refc outputs tim_oc1ref
    /// when the counter is counting up, tim_oc2ref when it is counting down.
    AsymmetricPwmMode2,
}

impl From<OutputCompareMode> for crate::pac::timer::vals::Ocm {
    fn from(mode: OutputCompareMode) -> Self {
        match mode {
            OutputCompareMode::Frozen => crate::pac::timer::vals::Ocm::FROZEN,
            OutputCompareMode::ActiveOnMatch => crate::pac::timer::vals::Ocm::ACTIVE_ON_MATCH,
            OutputCompareMode::InactiveOnMatch => crate::pac::timer::vals::Ocm::INACTIVE_ON_MATCH,
            OutputCompareMode::Toggle => crate::pac::timer::vals::Ocm::TOGGLE,
            OutputCompareMode::ForceInactive => crate::pac::timer::vals::Ocm::FORCE_INACTIVE,
            OutputCompareMode::ForceActive => crate::pac::timer::vals::Ocm::FORCE_ACTIVE,
            OutputCompareMode::PwmMode1 => crate::pac::timer::vals::Ocm::PWM_MODE1,
            OutputCompareMode::PwmMode2 => crate::pac::timer::vals::Ocm::PWM_MODE2,
            #[cfg(timer_v2)]
            OutputCompareMode::OnePulseMode1 => crate::pac::timer::vals::Ocm::RETRIGERRABLE_OPM_MODE_1,
            #[cfg(timer_v2)]
            OutputCompareMode::OnePulseMode2 => crate::pac::timer::vals::Ocm::RETRIGERRABLE_OPM_MODE_2,
            #[cfg(timer_v2)]
            OutputCompareMode::CombinedPwmMode1 => crate::pac::timer::vals::Ocm::COMBINED_PWM_MODE_1,
            #[cfg(timer_v2)]
            OutputCompareMode::CombinedPwmMode2 => crate::pac::timer::vals::Ocm::COMBINED_PWM_MODE_2,
            #[cfg(timer_v2)]
            OutputCompareMode::AsymmetricPwmMode1 => crate::pac::timer::vals::Ocm::ASYMMETRIC_PWM_MODE_1,
            #[cfg(timer_v2)]
            OutputCompareMode::AsymmetricPwmMode2 => crate::pac::timer::vals::Ocm::ASYMMETRIC_PWM_MODE_2,
        }
    }
}

/// Timer output pin polarity.
#[derive(Clone, Copy)]
pub enum OutputPolarity {
    /// Active high (higher duty value makes the pin spend more time high).
    ActiveHigh,
    /// Active low (higher duty value makes the pin spend more time low).
    ActiveLow,
}

impl From<OutputPolarity> for bool {
    fn from(mode: OutputPolarity) -> Self {
        match mode {
            OutputPolarity::ActiveHigh => false,
            OutputPolarity::ActiveLow => true,
        }
    }
}

/// Low-level timer driver.
pub struct Timer<'d, T: CoreInstance> {
    tim: Peri<'d, T>,
}

impl<'d, T: CoreInstance> Drop for Timer<'d, T> {
    fn drop(&mut self) {
        rcc::disable::<T>();
    }
}

impl<'d, T: CoreInstance> Timer<'d, T> {
    /// Create a new timer driver.
    pub fn new(tim: Peri<'d, T>) -> Self {
        rcc::enable_and_reset::<T>();

        Self { tim }
    }

    pub(crate) unsafe fn clone_unchecked(&self) -> ManuallyDrop<Self> {
        let tim = unsafe { self.tim.clone_unchecked() };
        ManuallyDrop::new(Self { tim })
    }

    /// Get access to the virutal core 16bit timer registers.
    ///
    /// Note: This works even if the timer is more capable, because registers
    /// for the less capable timers are a subset. This allows writing a driver
    /// for a given set of capabilities, and having it transparently work with
    /// more capable timers.
    pub fn regs_core(&self) -> crate::pac::timer::TimCore {
        unsafe { crate::pac::timer::TimCore::from_ptr(T::regs()) }
    }

    #[cfg(not(stm32l0))]
    fn regs_gp32_unchecked(&self) -> crate::pac::timer::TimGp32 {
        unsafe { crate::pac::timer::TimGp32::from_ptr(T::regs()) }
    }

    #[cfg(stm32l0)]
    fn regs_gp32_unchecked(&self) -> crate::pac::timer::TimGp16 {
        unsafe { crate::pac::timer::TimGp16::from_ptr(T::regs()) }
    }

    /// Start the timer.
    pub fn start(&self) {
        self.regs_core().cr1().modify(|r| r.set_cen(true));
    }

    /// Generate timer update event from software.
    ///
    /// Set URS to avoid generating interrupt or DMA request. This update event is only
    /// used to load value from pre-load registers. If called when the timer is running,
    /// it may disrupt the output waveform.
    pub fn generate_update_event(&self) {
        self.regs_core().cr1().modify(|r| r.set_urs(vals::Urs::COUNTER_ONLY));
        self.regs_core().egr().write(|r| r.set_ug(true));
        self.regs_core().cr1().modify(|r| r.set_urs(vals::Urs::ANY_EVENT));
    }

    /// Stop the timer.
    pub fn stop(&self) {
        self.regs_core().cr1().modify(|r| r.set_cen(false));
    }

    /// Reset the counter value to 0
    pub fn reset(&self) {
        self.regs_core().cnt().write(|r| r.set_cnt(0));
    }

    /// get the capability of the timer
    pub fn bits(&self) -> TimerBits {
        match T::Word::bits() {
            16 => TimerBits::Bits16,
            #[cfg(not(stm32l0))]
            32 => TimerBits::Bits32,
            _ => unreachable!(),
        }
    }

    /// Set the frequency of how many times per second the timer counts up to the max value or down to 0.
    ///
    /// This means that in the default edge-aligned mode,
    /// the timer counter will wrap around at the same frequency as is being set.
    /// In center-aligned mode (which not all timers support), the wrap-around frequency is effectively halved
    /// because it needs to count up and down.
    pub fn set_frequency(&self, frequency: Hertz) {
        self.set_frequency_internal(frequency, T::Word::bits());
    }

    pub(crate) fn set_frequency_internal(&self, frequency: Hertz, max_divide_by_bits: usize) {
        let f = frequency.0;
        assert!(f > 0);
        let timer_f = T::frequency().0;

        let pclk_ticks_per_timer_period = (timer_f / f) as u64;
        let psc: u16 = unwrap!(((pclk_ticks_per_timer_period - 1) / (1 << max_divide_by_bits)).try_into());
        let divide_by = pclk_ticks_per_timer_period / (u64::from(psc) + 1);

        // the timer counts `0..=arr`, we want it to count `0..divide_by`
        let arr: T::Word = unwrap!(T::Word::try_from(divide_by - 1));

        let regs = self.regs_gp32_unchecked();
        regs.psc().write_value(psc);
        #[cfg(stm32l0)]
        regs.arr().write(|r| r.set_arr(unwrap!(arr.try_into())));
        #[cfg(not(stm32l0))]
        regs.arr().write_value(arr.into());
    }

    /// Set tick frequency.
    pub fn set_tick_freq(&mut self, freq: Hertz) {
        let f = freq;
        assert!(f.0 > 0);
        let timer_f = self.get_clock_frequency();

        let pclk_ticks_per_timer_period = timer_f / f;
        let psc: u16 = unwrap!((pclk_ticks_per_timer_period - 1).try_into());

        let regs = self.regs_core();
        regs.psc().write_value(psc);

        // Generate an Update Request
        regs.egr().write(|r| r.set_ug(true));
    }

    /// Clear update interrupt.
    ///
    /// Returns whether the update interrupt flag was set.
    pub fn clear_update_interrupt(&self) -> bool {
        let regs = self.regs_core();
        let sr = regs.sr().read();
        if sr.uif() {
            regs.sr().modify(|r| {
                r.set_uif(false);
            });
            true
        } else {
            false
        }
    }

    /// Enable/disable the update interrupt.
    pub fn enable_update_interrupt(&self, enable: bool) {
        self.regs_core().dier().modify(|r| r.set_uie(enable));
    }

    /// Enable/disable autoreload preload.
    pub fn set_autoreload_preload(&self, enable: bool) {
        self.regs_core().cr1().modify(|r| r.set_arpe(enable));
    }

    /// Get the timer frequency.
    pub fn get_frequency(&self) -> Hertz {
        let timer_f = T::frequency();

        let regs = self.regs_gp32_unchecked();
        #[cfg(not(stm32l0))]
        let arr = regs.arr().read();
        #[cfg(stm32l0)]
        let arr = regs.arr().read().arr();
        let psc = regs.psc().read();

        timer_f / arr / (psc + 1)
    }

    /// Get the clock frequency of the timer (before prescaler is applied).
    pub fn get_clock_frequency(&self) -> Hertz {
        T::frequency()
    }
}

impl<'d, T: BasicNoCr2Instance> Timer<'d, T> {
    /// Get access to the Baisc 16bit timer registers.
    ///
    /// Note: This works even if the timer is more capable, because registers
    /// for the less capable timers are a subset. This allows writing a driver
    /// for a given set of capabilities, and having it transparently work with
    /// more capable timers.
    pub fn regs_basic_no_cr2(&self) -> crate::pac::timer::TimBasicNoCr2 {
        unsafe { crate::pac::timer::TimBasicNoCr2::from_ptr(T::regs()) }
    }

    /// Enable/disable the update dma.
    pub fn enable_update_dma(&self, enable: bool) {
        self.regs_basic_no_cr2().dier().modify(|r| r.set_ude(enable));
    }

    /// Get the update dma enable/disable state.
    pub fn get_update_dma_state(&self) -> bool {
        self.regs_basic_no_cr2().dier().read().ude()
    }
}

impl<'d, T: BasicInstance> Timer<'d, T> {
    /// Get access to the Baisc 16bit timer registers.
    ///
    /// Note: This works even if the timer is more capable, because registers
    /// for the less capable timers are a subset. This allows writing a driver
    /// for a given set of capabilities, and having it transparently work with
    /// more capable timers.
    pub fn regs_basic(&self) -> crate::pac::timer::TimBasic {
        unsafe { crate::pac::timer::TimBasic::from_ptr(T::regs()) }
    }
}

impl<'d, T: GeneralInstance1Channel> Timer<'d, T> {
    /// Get access to the general purpose 1 channel 16bit timer registers.
    ///
    /// Note: This works even if the timer is more capable, because registers
    /// for the less capable timers are a subset. This allows writing a driver
    /// for a given set of capabilities, and having it transparently work with
    /// more capable timers.
    pub fn regs_1ch(&self) -> crate::pac::timer::Tim1ch {
        unsafe { crate::pac::timer::Tim1ch::from_ptr(T::regs()) }
    }

    /// Set clock divider.
    pub fn set_clock_division(&self, ckd: vals::Ckd) {
        self.regs_1ch().cr1().modify(|r| r.set_ckd(ckd));
    }

    /// Get max compare value. This depends on the timer frequency and the clock frequency from RCC.
    pub fn get_max_compare_value(&self) -> T::Word {
        #[cfg(not(stm32l0))]
        return unwrap!(self.regs_gp32_unchecked().arr().read().try_into());
        #[cfg(stm32l0)]
        return unwrap!(self.regs_gp32_unchecked().arr().read().arr().try_into());
    }

    /// Set the max compare value.
    ///
    /// An update event is generated to load the new value. The update event is
    /// generated such that it will not cause an interrupt or DMA request.
    pub fn set_max_compare_value(&self, ticks: T::Word) {
        let arr = ticks;

        let regs = self.regs_gp32_unchecked();
        #[cfg(not(stm32l0))]
        regs.arr().write_value(arr.into());
        #[cfg(stm32l0)]
        regs.arr().write(|r| r.set_arr(unwrap!(arr.try_into())));

        regs.cr1().modify(|r| r.set_urs(vals::Urs::COUNTER_ONLY));
        regs.egr().write(|r| r.set_ug(true));
        regs.cr1().modify(|r| r.set_urs(vals::Urs::ANY_EVENT));
    }
}

impl<'d, T: GeneralInstance2Channel> Timer<'d, T> {
    /// Get access to the general purpose 2 channel 16bit timer registers.
    ///
    /// Note: This works even if the timer is more capable, because registers
    /// for the less capable timers are a subset. This allows writing a driver
    /// for a given set of capabilities, and having it transparently work with
    /// more capable timers.
    pub fn regs_2ch(&self) -> crate::pac::timer::Tim2ch {
        unsafe { crate::pac::timer::Tim2ch::from_ptr(T::regs()) }
    }
}

impl<'d, T: GeneralInstance4Channel> Timer<'d, T> {
    /// Get access to the general purpose 16bit timer registers.
    ///
    /// Note: This works even if the timer is more capable, because registers
    /// for the less capable timers are a subset. This allows writing a driver
    /// for a given set of capabilities, and having it transparently work with
    /// more capable timers.
    pub fn regs_gp16(&self) -> crate::pac::timer::TimGp16 {
        unsafe { crate::pac::timer::TimGp16::from_ptr(T::regs()) }
    }

    /// Enable timer outputs.
    pub fn enable_outputs(&self) {
        self.tim.enable_outputs()
    }

    /// Set counting mode.
    pub fn set_counting_mode(&self, mode: CountingMode) {
        let (cms, dir) = mode.into();

        let timer_enabled = self.regs_core().cr1().read().cen();
        // Changing from edge aligned to center aligned (and vice versa) is not allowed while the timer is running.
        // Changing direction is discouraged while the timer is running.
        assert!(!timer_enabled);

        self.regs_gp16().cr1().modify(|r| r.set_dir(dir));
        self.regs_gp16().cr1().modify(|r| r.set_cms(cms))
    }

    /// Get counting mode.
    pub fn get_counting_mode(&self) -> CountingMode {
        let cr1 = self.regs_gp16().cr1().read();
        (cr1.cms(), cr1.dir()).into()
    }

    /// Set input capture filter.
    pub fn set_input_capture_filter(&self, channel: Channel, icf: vals::FilterValue) {
        let raw_channel = channel.index();
        self.regs_gp16()
            .ccmr_input(raw_channel / 2)
            .modify(|r| r.set_icf(raw_channel % 2, icf));
    }

    /// Clear input interrupt.
    pub fn clear_input_interrupt(&self, channel: Channel) {
        self.regs_gp16().sr().modify(|r| r.set_ccif(channel.index(), false));
    }

    /// Get input interrupt.
    pub fn get_input_interrupt(&self, channel: Channel) -> bool {
        self.regs_gp16().sr().read().ccif(channel.index())
    }

    /// Enable input interrupt.
    pub fn enable_input_interrupt(&self, channel: Channel, enable: bool) {
        self.regs_gp16().dier().modify(|r| r.set_ccie(channel.index(), enable));
    }

    /// Set input capture prescaler.
    pub fn set_input_capture_prescaler(&self, channel: Channel, factor: u8) {
        let raw_channel = channel.index();
        self.regs_gp16()
            .ccmr_input(raw_channel / 2)
            .modify(|r| r.set_icpsc(raw_channel % 2, factor));
    }

    /// Set input TI selection.
    pub fn set_input_ti_selection(&self, channel: Channel, tisel: InputTISelection) {
        let raw_channel = channel.index();
        self.regs_gp16()
            .ccmr_input(raw_channel / 2)
            .modify(|r| r.set_ccs(raw_channel % 2, tisel.into()));
    }

    /// Set input capture mode.
    pub fn set_input_capture_mode(&self, channel: Channel, mode: InputCaptureMode) {
        self.regs_gp16().ccer().modify(|r| match mode {
            InputCaptureMode::Rising => {
                r.set_ccnp(channel.index(), false);
                r.set_ccp(channel.index(), false);
            }
            InputCaptureMode::Falling => {
                r.set_ccnp(channel.index(), false);
                r.set_ccp(channel.index(), true);
            }
            InputCaptureMode::BothEdges => {
                r.set_ccnp(channel.index(), true);
                r.set_ccp(channel.index(), true);
            }
        });
    }

    /// Set output compare mode.
    pub fn set_output_compare_mode(&self, channel: Channel, mode: OutputCompareMode) {
        let raw_channel: usize = channel.index();
        self.regs_gp16()
            .ccmr_output(raw_channel / 2)
            .modify(|w| w.set_ocm(raw_channel % 2, mode.into()));
    }

    /// Set output polarity.
    pub fn set_output_polarity(&self, channel: Channel, polarity: OutputPolarity) {
        self.regs_gp16()
            .ccer()
            .modify(|w| w.set_ccp(channel.index(), polarity.into()));
    }

    /// Enable/disable a channel.
    pub fn enable_channel(&self, channel: Channel, enable: bool) {
        self.regs_gp16().ccer().modify(|w| w.set_cce(channel.index(), enable));
    }

    /// Get enable/disable state of a channel
    pub fn get_channel_enable_state(&self, channel: Channel) -> bool {
        self.regs_gp16().ccer().read().cce(channel.index())
    }

    /// Set compare value for a channel.
    pub fn set_compare_value(&self, channel: Channel, value: T::Word) {
        #[cfg(not(stm32l0))]
        self.regs_gp32_unchecked()
            .ccr(channel.index())
            .write_value(value.into());
        #[cfg(stm32l0)]
        self.regs_gp16()
            .ccr(channel.index())
            .modify(|w| w.set_ccr(unwrap!(value.try_into())));
    }

    /// Get compare value for a channel.
    pub fn get_compare_value(&self, channel: Channel) -> T::Word {
        #[cfg(not(stm32l0))]
        return unwrap!(self.regs_gp32_unchecked().ccr(channel.index()).read().try_into());
        #[cfg(stm32l0)]
        return unwrap!(self.regs_gp32_unchecked().ccr(channel.index()).read().ccr().try_into());
    }

    pub(crate) fn clamp_compare_value<W: Word>(&mut self, channel: Channel) {
        self.set_compare_value(
            channel,
            unwrap!(
                self.get_compare_value(channel)
                    .into()
                    .clamp(0, W::max() as u32)
                    .try_into()
            ),
        );
    }

    /// Setup a ring buffer for the channel
    pub fn setup_ring_buffer<'a, W: Word + Into<T::Word>>(
        &mut self,
        dma: Peri<'a, impl super::UpDma<T>>,
        channel: Channel,
        dma_buf: &'a mut [W],
    ) -> WritableRingBuffer<'a, W> {
        #[allow(clippy::let_unit_value)] // eg. stm32f334
        let req = dma.request();

        unsafe {
            use crate::dma::TransferOptions;
            #[cfg(not(any(bdma, gpdma)))]
            use crate::dma::{Burst, FifoThreshold};

            let dma_transfer_option = TransferOptions {
                #[cfg(not(any(bdma, gpdma)))]
                fifo_threshold: Some(FifoThreshold::Full),
                #[cfg(not(any(bdma, gpdma)))]
                mburst: Burst::Incr8,
                ..Default::default()
            };

            WritableRingBuffer::new(
                dma,
                req,
                self.regs_1ch().ccr(channel.index()).as_ptr() as *mut W,
                dma_buf,
                dma_transfer_option,
            )
        }
    }

    /// Generate a sequence of PWM waveform
    ///
    /// Note:
    /// you will need to provide corresponding TIMx_UP DMA channel to use this method.
    pub fn setup_update_dma<'a, W: Word + Into<T::Word>>(
        &mut self,
        dma: Peri<'a, impl super::UpDma<T>>,
        channel: Channel,
        duty: &'a [W],
    ) -> Transfer<'a> {
        self.setup_update_dma_inner(dma.request(), dma, channel, duty)
    }

    /// Generate a sequence of PWM waveform
    ///
    /// Note:
    /// The DMA channel provided does not need to correspond to the requested channel.
    pub fn setup_channel_update_dma<'a, C: TimerChannel, W: Word + Into<T::Word>>(
        &mut self,
        dma: Peri<'a, impl super::Dma<T, C>>,
        channel: Channel,
        duty: &'a [W],
    ) -> Transfer<'a> {
        self.setup_update_dma_inner(dma.request(), dma, channel, duty)
    }

    fn setup_update_dma_inner<'a, W: Word + Into<T::Word>>(
        &mut self,
        request: dma::Request,
        dma: Peri<'a, impl dma::Channel>,
        channel: Channel,
        duty: &'a [W],
    ) -> Transfer<'a> {
        unsafe {
            #[cfg(not(any(bdma, gpdma)))]
            use crate::dma::{Burst, FifoThreshold};
            use crate::dma::{Transfer, TransferOptions};

            let dma_transfer_option = TransferOptions {
                #[cfg(not(any(bdma, gpdma)))]
                fifo_threshold: Some(FifoThreshold::Full),
                #[cfg(not(any(bdma, gpdma)))]
                mburst: Burst::Incr8,
                ..Default::default()
            };

            Transfer::new_write(
                dma,
                request,
                duty,
                self.regs_gp16().ccr(channel.index()).as_ptr() as *mut W,
                dma_transfer_option,
            )
        }
    }

    /// Generate a multichannel sequence of PWM waveforms using DMA triggered by timer update events.
    ///
    /// This method utilizes the timer's DMA burst transfer capability to update multiple CCRx registers
    /// in sequence on each update event (UEV). The data is written via the DMAR register using the
    /// DMA base address (DBA) and burst length (DBL) configured in the DCR register.
    ///
    /// The `duty` buffer must be structured as a flattened 2D array in row-major order, where each row
    /// represents a single update event and each column corresponds to a specific timer channel (starting
    /// from `starting_channel` up to and including `ending_channel`).
    ///
    /// For example, if using channels 1 through 4, a buffer of 4 update steps might look like:
    ///
    /// ```rust,ignore
    /// let dma_buf: [u16; 16] = [
    ///     ch1_duty_1, ch2_duty_1, ch3_duty_1, ch4_duty_1, // update 1
    ///     ch1_duty_2, ch2_duty_2, ch3_duty_2, ch4_duty_2, // update 2
    ///     ch1_duty_3, ch2_duty_3, ch3_duty_3, ch4_duty_3, // update 3
    ///     ch1_duty_4, ch2_duty_4, ch3_duty_4, ch4_duty_4, // update 4
    /// ];
    /// ```
    ///
    /// Each group of `N` values (where `N` is number of channels) is transferred on one update event,
    /// updating the duty cycles of all selected channels simultaneously.
    ///
    /// Note:
    /// You will need to provide corresponding `TIMx_UP` DMA channel to use this method.
    /// Also be aware that embassy timers use one of timers internally. It is possible to
    /// switch this timer by using `time-driver-timX` feature.
    ///
    pub fn setup_update_dma_burst<'a, W: Word + Into<T::Word>>(
        &mut self,
        dma: Peri<'a, impl super::UpDma<T>>,
        starting_channel: Channel,
        ending_channel: Channel,
        duty: &'a [W],
    ) -> Transfer<'a> {
        let cr1_addr = self.regs_gp16().cr1().as_ptr() as u32;
        let start_ch_index = starting_channel.index();
        let end_ch_index = ending_channel.index();

        assert!(start_ch_index <= end_ch_index);

        let ccrx_addr = self.regs_gp16().ccr(start_ch_index).as_ptr() as u32;
        self.regs_gp16()
            .dcr()
            .modify(|w| w.set_dba(((ccrx_addr - cr1_addr) / 4) as u8));
        self.regs_gp16()
            .dcr()
            .modify(|w| w.set_dbl((end_ch_index - start_ch_index) as u8));

        #[allow(clippy::let_unit_value)] // eg. stm32f334
        let req = dma.request();

        unsafe {
            #[cfg(not(any(bdma, gpdma)))]
            use crate::dma::{Burst, FifoThreshold};
            use crate::dma::{Transfer, TransferOptions};

            let dma_transfer_option = TransferOptions {
                #[cfg(not(any(bdma, gpdma)))]
                fifo_threshold: Some(FifoThreshold::Full),
                #[cfg(not(any(bdma, gpdma)))]
                mburst: Burst::Incr4,
                ..Default::default()
            };

            Transfer::new_write(
                dma,
                req,
                duty,
                self.regs_gp16().dmar().as_ptr() as *mut W,
                dma_transfer_option,
            )
        }
    }

    /// Get capture value for a channel.
    pub fn get_capture_value(&self, channel: Channel) -> T::Word {
        self.get_compare_value(channel)
    }

    /// Set output compare preload.
    pub fn set_output_compare_preload(&self, channel: Channel, preload: bool) {
        let channel_index = channel.index();
        self.regs_gp16()
            .ccmr_output(channel_index / 2)
            .modify(|w| w.set_ocpe(channel_index % 2, preload));
    }

    /// Get capture compare DMA selection
    pub fn get_cc_dma_selection(&self) -> vals::Ccds {
        self.regs_gp16().cr2().read().ccds()
    }

    /// Set capture compare DMA selection
    pub fn set_cc_dma_selection(&self, ccds: vals::Ccds) {
        self.regs_gp16().cr2().modify(|w| w.set_ccds(ccds))
    }

    /// Get capture compare DMA enable state
    pub fn get_cc_dma_enable_state(&self, channel: Channel) -> bool {
        self.regs_gp16().dier().read().ccde(channel.index())
    }

    /// Set capture compare DMA enable state
    pub fn set_cc_dma_enable_state(&self, channel: Channel, ccde: bool) {
        self.regs_gp16().dier().modify(|w| w.set_ccde(channel.index(), ccde))
    }

    /// Set Timer Master Mode
    pub fn set_master_mode(&self, mms: MasterMode) {
        self.regs_gp16().cr2().modify(|w| w.set_mms(mms));
    }

    /// Set Timer Slave Mode
    pub fn set_slave_mode(&self, sms: SlaveMode) {
        self.regs_gp16().smcr().modify(|r| r.set_sms(sms));
    }

    /// Set Timer Trigger Source
    pub fn set_trigger_source(&self, ts: TriggerSource) {
        self.regs_gp16().smcr().modify(|r| r.set_ts(ts));
    }
}

#[cfg(not(stm32l0))]
impl<'d, T: GeneralInstance32bit4Channel> Timer<'d, T> {
    /// Get access to the general purpose 32bit timer registers.
    ///
    /// Note: This works even if the timer is more capable, because registers
    /// for the less capable timers are a subset. This allows writing a driver
    /// for a given set of capabilities, and having it transparently work with
    /// more capable timers.
    pub fn regs_gp32(&self) -> crate::pac::timer::TimGp32 {
        unsafe { crate::pac::timer::TimGp32::from_ptr(T::regs()) }
    }
}

#[cfg(not(stm32l0))]
impl<'d, T: AdvancedInstance1Channel> Timer<'d, T> {
    /// Get access to the general purpose 1 channel with one complementary 16bit timer registers.
    ///
    /// Note: This works even if the timer is more capable, because registers
    /// for the less capable timers are a subset. This allows writing a driver
    /// for a given set of capabilities, and having it transparently work with
    /// more capable timers.
    pub fn regs_1ch_cmp(&self) -> crate::pac::timer::Tim1chCmp {
        unsafe { crate::pac::timer::Tim1chCmp::from_ptr(T::regs()) }
    }

    /// Set clock divider for the dead time.
    pub fn set_dead_time_clock_division(&self, value: vals::Ckd) {
        self.regs_1ch_cmp().cr1().modify(|w| w.set_ckd(value));
    }

    /// Set dead time, as a fraction of the max duty value.
    pub fn set_dead_time_value(&self, value: u8) {
        self.regs_1ch_cmp().bdtr().modify(|w| w.set_dtg(value));
    }

    /// Set state of OSSI-bit in BDTR register
    pub fn set_ossi(&self, val: vals::Ossi) {
        self.regs_1ch_cmp().bdtr().modify(|w| w.set_ossi(val));
    }

    /// Get state of OSSI-bit in BDTR register
    pub fn get_ossi(&self) -> vals::Ossi {
        self.regs_1ch_cmp().bdtr().read().ossi()
    }

    /// Set state of OSSR-bit in BDTR register
    pub fn set_ossr(&self, val: vals::Ossr) {
        self.regs_1ch_cmp().bdtr().modify(|w| w.set_ossr(val));
    }

    /// Get state of OSSR-bit in BDTR register
    pub fn get_ossr(&self) -> vals::Ossr {
        self.regs_1ch_cmp().bdtr().read().ossr()
    }

    /// Set state of MOE-bit in BDTR register to en-/disable output
    pub fn set_moe(&self, enable: bool) {
        self.regs_1ch_cmp().bdtr().modify(|w| w.set_moe(enable));
    }

    /// Get state of MOE-bit in BDTR register
    pub fn get_moe(&self) -> bool {
        self.regs_1ch_cmp().bdtr().read().moe()
    }
}

#[cfg(not(stm32l0))]
impl<'d, T: AdvancedInstance2Channel> Timer<'d, T> {
    /// Get access to the general purpose 2 channel with one complementary 16bit timer registers.
    ///
    /// Note: This works even if the timer is more capable, because registers
    /// for the less capable timers are a subset. This allows writing a driver
    /// for a given set of capabilities, and having it transparently work with
    /// more capable timers.
    pub fn regs_2ch_cmp(&self) -> crate::pac::timer::Tim2chCmp {
        unsafe { crate::pac::timer::Tim2chCmp::from_ptr(T::regs()) }
    }
}

#[cfg(not(stm32l0))]
impl<'d, T: AdvancedInstance4Channel> Timer<'d, T> {
    /// Get access to the advanced timer registers.
    pub fn regs_advanced(&self) -> crate::pac::timer::TimAdv {
        unsafe { crate::pac::timer::TimAdv::from_ptr(T::regs()) }
    }

    /// Set complementary output polarity.
    pub fn set_complementary_output_polarity(&self, channel: Channel, polarity: OutputPolarity) {
        self.regs_advanced()
            .ccer()
            .modify(|w| w.set_ccnp(channel.index(), polarity.into()));
    }

    /// Enable/disable a complementary channel.
    pub fn enable_complementary_channel(&self, channel: Channel, enable: bool) {
        self.regs_advanced()
            .ccer()
            .modify(|w| w.set_ccne(channel.index(), enable));
    }

    /// Set Output Idle State
    pub fn set_ois(&self, channel: Channel, val: bool) {
        self.regs_advanced().cr2().modify(|w| w.set_ois(channel.index(), val));
    }
    /// Set Output Idle State Complementary Channel
    pub fn set_oisn(&self, channel: Channel, val: bool) {
        self.regs_advanced().cr2().modify(|w| w.set_oisn(channel.index(), val));
    }

    /// Set master mode selection 2
    pub fn set_mms2_selection(&self, mms2: vals::Mms2) {
        self.regs_advanced().cr2().modify(|w| w.set_mms2(mms2));
    }

    /// Set repetition counter
    pub fn set_repetition_counter(&self, val: u16) {
        self.regs_advanced().rcr().modify(|w| w.set_rep(val));
    }

    /// Trigger software break 1 or 2
    /// Setting this bit generates a break event. This bit is automatically cleared by the hardware.
    pub fn trigger_software_break(&self, n: usize) {
        self.regs_advanced().egr().write(|r| r.set_bg(n, true));
    }
}
