From dc6bf5d44675f6f2013ddfab6b14df25a996a965 Mon Sep 17 00:00:00 2001 From: James Munns Date: Thu, 4 Dec 2025 18:37:01 +0100 Subject: Move to subfolder --- embassy-mcxa/src/adc.rs | 409 +++++++++ embassy-mcxa/src/clkout.rs | 169 ++++ embassy-mcxa/src/clocks/config.rs | 204 +++++ embassy-mcxa/src/clocks/mod.rs | 943 +++++++++++++++++++++ embassy-mcxa/src/clocks/periph_helpers.rs | 502 +++++++++++ embassy-mcxa/src/config.rs | 27 + embassy-mcxa/src/gpio.rs | 1062 ++++++++++++++++++++++++ embassy-mcxa/src/i2c/controller.rs | 742 +++++++++++++++++ embassy-mcxa/src/i2c/mod.rs | 194 +++++ embassy-mcxa/src/interrupt.rs | 563 +++++++++++++ embassy-mcxa/src/lib.rs | 471 +++++++++++ embassy-mcxa/src/lpuart/buffered.rs | 780 +++++++++++++++++ embassy-mcxa/src/lpuart/mod.rs | 1292 +++++++++++++++++++++++++++++ embassy-mcxa/src/ostimer.rs | 745 +++++++++++++++++ embassy-mcxa/src/pins.rs | 28 + embassy-mcxa/src/rtc.rs | 453 ++++++++++ 16 files changed, 8584 insertions(+) create mode 100644 embassy-mcxa/src/adc.rs create mode 100644 embassy-mcxa/src/clkout.rs create mode 100644 embassy-mcxa/src/clocks/config.rs create mode 100644 embassy-mcxa/src/clocks/mod.rs create mode 100644 embassy-mcxa/src/clocks/periph_helpers.rs create mode 100644 embassy-mcxa/src/config.rs create mode 100644 embassy-mcxa/src/gpio.rs create mode 100644 embassy-mcxa/src/i2c/controller.rs create mode 100644 embassy-mcxa/src/i2c/mod.rs create mode 100644 embassy-mcxa/src/interrupt.rs create mode 100644 embassy-mcxa/src/lib.rs create mode 100644 embassy-mcxa/src/lpuart/buffered.rs create mode 100644 embassy-mcxa/src/lpuart/mod.rs create mode 100644 embassy-mcxa/src/ostimer.rs create mode 100644 embassy-mcxa/src/pins.rs create mode 100644 embassy-mcxa/src/rtc.rs (limited to 'embassy-mcxa/src') diff --git a/embassy-mcxa/src/adc.rs b/embassy-mcxa/src/adc.rs new file mode 100644 index 000000000..b5ec5983f --- /dev/null +++ b/embassy-mcxa/src/adc.rs @@ -0,0 +1,409 @@ +//! ADC driver +use core::sync::atomic::{AtomicBool, Ordering}; + +use embassy_hal_internal::{Peri, PeripheralType}; + +use crate::clocks::periph_helpers::{AdcClockSel, AdcConfig, Div4}; +use crate::clocks::{enable_and_reset, Gate, PoweredClock}; +use crate::pac; +use crate::pac::adc1::cfg::{HptExdi, Pwrsel, Refsel, Tcmdres, Tprictrl, Tres}; +use crate::pac::adc1::cmdh1::{Avgs, Cmpen, Next, Sts}; +use crate::pac::adc1::cmdl1::{Adch, Ctype, Mode}; +use crate::pac::adc1::ctrl::CalAvgs; +use crate::pac::adc1::tctrl::{Tcmd, Tpri}; + +type Regs = pac::adc1::RegisterBlock; + +static INTERRUPT_TRIGGERED: AtomicBool = AtomicBool::new(false); +// Token-based instance pattern like embassy-imxrt +pub trait Instance: Gate + PeripheralType { + fn ptr() -> *const Regs; +} + +/// Token for ADC1 +pub type Adc1 = crate::peripherals::ADC1; +impl Instance for crate::peripherals::ADC1 { + #[inline(always)] + fn ptr() -> *const Regs { + pac::Adc1::ptr() + } +} + +// Also implement Instance for the Peri wrapper type +// impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::ADC1> { +// #[inline(always)] +// fn ptr() -> *const Regs { +// pac::Adc1::ptr() +// } +// } + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum TriggerPriorityPolicy { + ConvPreemptImmediatelyNotAutoResumed = 0, + ConvPreemptSoftlyNotAutoResumed = 1, + ConvPreemptImmediatelyAutoRestarted = 4, + ConvPreemptSoftlyAutoRestarted = 5, + ConvPreemptImmediatelyAutoResumed = 12, + ConvPreemptSoftlyAutoResumed = 13, + ConvPreemptSubsequentlyNotAutoResumed = 2, + ConvPreemptSubsequentlyAutoRestarted = 6, + ConvPreemptSubsequentlyAutoResumed = 14, + TriggerPriorityExceptionDisabled = 16, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct LpadcConfig { + pub enable_in_doze_mode: bool, + pub conversion_average_mode: CalAvgs, + pub enable_analog_preliminary: bool, + pub power_up_delay: u8, + pub reference_voltage_source: Refsel, + pub power_level_mode: Pwrsel, + pub trigger_priority_policy: TriggerPriorityPolicy, + pub enable_conv_pause: bool, + pub conv_pause_delay: u16, + pub fifo_watermark: u8, + pub power: PoweredClock, + pub source: AdcClockSel, + pub div: Div4, +} + +impl Default for LpadcConfig { + fn default() -> Self { + LpadcConfig { + enable_in_doze_mode: true, + conversion_average_mode: CalAvgs::NoAverage, + enable_analog_preliminary: false, + power_up_delay: 0x80, + reference_voltage_source: Refsel::Option1, + power_level_mode: Pwrsel::Lowest, + trigger_priority_policy: TriggerPriorityPolicy::ConvPreemptImmediatelyNotAutoResumed, + enable_conv_pause: false, + conv_pause_delay: 0, + fifo_watermark: 0, + power: PoweredClock::NormalEnabledDeepSleepDisabled, + source: AdcClockSel::FroLfDiv, + div: Div4::no_div(), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ConvCommandConfig { + pub sample_channel_mode: Ctype, + pub channel_number: Adch, + pub chained_next_command_number: Next, + pub enable_auto_channel_increment: bool, + pub loop_count: u8, + pub hardware_average_mode: Avgs, + pub sample_time_mode: Sts, + pub hardware_compare_mode: Cmpen, + pub hardware_compare_value_high: u32, + pub hardware_compare_value_low: u32, + pub conversion_resolution_mode: Mode, + pub enable_wait_trigger: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ConvTriggerConfig { + pub target_command_id: Tcmd, + pub delay_power: u8, + pub priority: Tpri, + pub enable_hardware_trigger: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ConvResult { + pub command_id_source: u32, + pub loop_count_index: u32, + pub trigger_id_source: u32, + pub conv_value: u16, +} + +pub struct Adc<'a, I: Instance> { + _inst: core::marker::PhantomData<&'a mut I>, +} + +impl<'a, I: Instance> Adc<'a, I> { + /// initialize ADC + pub fn new(_inst: Peri<'a, I>, config: LpadcConfig) -> Self { + let adc = unsafe { &*I::ptr() }; + + let _clock_freq = unsafe { + enable_and_reset::(&AdcConfig { + power: config.power, + source: config.source, + div: config.div, + }) + .expect("Adc Init should not fail") + }; + + /* Reset the module. */ + adc.ctrl().modify(|_, w| w.rst().held_in_reset()); + adc.ctrl().modify(|_, w| w.rst().released_from_reset()); + + adc.ctrl().modify(|_, w| w.rstfifo0().trigger_reset()); + + /* Disable the module before setting configuration. */ + adc.ctrl().modify(|_, w| w.adcen().disabled()); + + /* Configure the module generally. */ + if config.enable_in_doze_mode { + adc.ctrl().modify(|_, w| w.dozen().enabled()); + } else { + adc.ctrl().modify(|_, w| w.dozen().disabled()); + } + + /* Set calibration average mode. */ + adc.ctrl() + .modify(|_, w| w.cal_avgs().variant(config.conversion_average_mode)); + + adc.cfg().write(|w| unsafe { + let w = if config.enable_analog_preliminary { + w.pwren().pre_enabled() + } else { + w + }; + + w.pudly() + .bits(config.power_up_delay) + .refsel() + .variant(config.reference_voltage_source) + .pwrsel() + .variant(config.power_level_mode) + .tprictrl() + .variant(match config.trigger_priority_policy { + TriggerPriorityPolicy::ConvPreemptSoftlyNotAutoResumed + | TriggerPriorityPolicy::ConvPreemptSoftlyAutoRestarted + | TriggerPriorityPolicy::ConvPreemptSoftlyAutoResumed => Tprictrl::FinishCurrentOnPriority, + TriggerPriorityPolicy::ConvPreemptSubsequentlyNotAutoResumed + | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoRestarted + | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoResumed => Tprictrl::FinishSequenceOnPriority, + _ => Tprictrl::AbortCurrentOnPriority, + }) + .tres() + .variant(match config.trigger_priority_policy { + TriggerPriorityPolicy::ConvPreemptImmediatelyAutoRestarted + | TriggerPriorityPolicy::ConvPreemptSoftlyAutoRestarted + | TriggerPriorityPolicy::ConvPreemptImmediatelyAutoResumed + | TriggerPriorityPolicy::ConvPreemptSoftlyAutoResumed + | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoRestarted + | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoResumed => Tres::Enabled, + _ => Tres::Disabled, + }) + .tcmdres() + .variant(match config.trigger_priority_policy { + TriggerPriorityPolicy::ConvPreemptImmediatelyAutoResumed + | TriggerPriorityPolicy::ConvPreemptSoftlyAutoResumed + | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoResumed + | TriggerPriorityPolicy::TriggerPriorityExceptionDisabled => Tcmdres::Enabled, + _ => Tcmdres::Disabled, + }) + .hpt_exdi() + .variant(match config.trigger_priority_policy { + TriggerPriorityPolicy::TriggerPriorityExceptionDisabled => HptExdi::Disabled, + _ => HptExdi::Enabled, + }) + }); + + if config.enable_conv_pause { + adc.pause() + .modify(|_, w| unsafe { w.pauseen().enabled().pausedly().bits(config.conv_pause_delay) }); + } else { + adc.pause().write(|w| unsafe { w.bits(0) }); + } + + adc.fctrl0() + .write(|w| unsafe { w.fwmark().bits(config.fifo_watermark) }); + + // Enable ADC + adc.ctrl().modify(|_, w| w.adcen().enabled()); + + Self { + _inst: core::marker::PhantomData, + } + } + + pub fn deinit(&self) { + let adc = unsafe { &*I::ptr() }; + adc.ctrl().modify(|_, w| w.adcen().disabled()); + } + + pub fn do_offset_calibration(&self) { + let adc = unsafe { &*I::ptr() }; + // Enable calibration mode + adc.ctrl() + .modify(|_, w| w.calofs().offset_calibration_request_pending()); + + // Wait for calibration to complete (polling status register) + while adc.stat().read().cal_rdy().is_not_set() {} + } + + pub fn get_gain_conv_result(&self, mut gain_adjustment: f32) -> u32 { + let mut gcra_array = [0u32; 17]; + let mut gcalr: u32 = 0; + + for i in (1..=17).rev() { + let shift = 16 - (i - 1); + let step = 1.0 / (1u32 << shift) as f32; + let tmp = (gain_adjustment / step) as u32; + gcra_array[i - 1] = tmp; + gain_adjustment -= tmp as f32 * step; + } + + for i in (1..=17).rev() { + gcalr += gcra_array[i - 1] << (i - 1); + } + gcalr + } + + pub fn do_auto_calibration(&self) { + let adc = unsafe { &*I::ptr() }; + adc.ctrl().modify(|_, w| w.cal_req().calibration_request_pending()); + + while adc.gcc0().read().rdy().is_gain_cal_not_valid() {} + + let mut gcca = adc.gcc0().read().gain_cal().bits() as u32; + if gcca & ((0xFFFF + 1) >> 1) != 0 { + gcca |= !0xFFFF; + } + + let gcra = 131072.0 / (131072.0 - gcca as f32); + + // Write to GCR0 + adc.gcr0().write(|w| unsafe { w.bits(self.get_gain_conv_result(gcra)) }); + + adc.gcr0().modify(|_, w| w.rdy().set_bit()); + + // Wait for calibration to complete (polling status register) + while adc.stat().read().cal_rdy().is_not_set() {} + } + + pub fn do_software_trigger(&self, trigger_id_mask: u32) { + let adc = unsafe { &*I::ptr() }; + adc.swtrig().write(|w| unsafe { w.bits(trigger_id_mask) }); + } + + pub fn get_default_conv_command_config(&self) -> ConvCommandConfig { + ConvCommandConfig { + sample_channel_mode: Ctype::SingleEndedASideChannel, + channel_number: Adch::SelectCh0, + chained_next_command_number: Next::NoNextCmdTerminateOnFinish, + enable_auto_channel_increment: false, + loop_count: 0, + hardware_average_mode: Avgs::NoAverage, + sample_time_mode: Sts::Sample3p5, + hardware_compare_mode: Cmpen::DisabledAlwaysStoreResult, + hardware_compare_value_high: 0, + hardware_compare_value_low: 0, + conversion_resolution_mode: Mode::Data12Bits, + enable_wait_trigger: false, + } + } + + //TBD Need to add cmdlx and cmdhx with x {2..7} + pub fn set_conv_command_config(&self, index: u32, config: &ConvCommandConfig) { + let adc = unsafe { &*I::ptr() }; + + match index { + 1 => { + adc.cmdl1().write(|w| { + w.adch() + .variant(config.channel_number) + .mode() + .variant(config.conversion_resolution_mode) + }); + adc.cmdh1().write(|w| unsafe { + w.next() + .variant(config.chained_next_command_number) + .loop_() + .bits(config.loop_count) + .avgs() + .variant(config.hardware_average_mode) + .sts() + .variant(config.sample_time_mode) + .cmpen() + .variant(config.hardware_compare_mode); + if config.enable_wait_trigger { + w.wait_trig().enabled(); + } + if config.enable_auto_channel_increment { + w.lwi().enabled(); + } + w + }); + } + _ => panic!("Invalid command index: must be between 1 and 7"), + } + } + + pub fn get_default_conv_trigger_config(&self) -> ConvTriggerConfig { + ConvTriggerConfig { + target_command_id: Tcmd::NotValid, + delay_power: 0, + priority: Tpri::HighestPriority, + enable_hardware_trigger: false, + } + } + + pub fn set_conv_trigger_config(&self, trigger_id: usize, config: &ConvTriggerConfig) { + let adc = unsafe { &*I::ptr() }; + let tctrl = &adc.tctrl(trigger_id); + + tctrl.write(|w| unsafe { + let w = w.tcmd().variant(config.target_command_id); + let w = w.tdly().bits(config.delay_power); + w.tpri().variant(config.priority); + if config.enable_hardware_trigger { + w.hten().enabled() + } else { + w + } + }); + } + + pub fn do_reset_fifo(&self) { + let adc = unsafe { &*I::ptr() }; + adc.ctrl().modify(|_, w| w.rstfifo0().trigger_reset()); + } + + pub fn enable_interrupt(&self, mask: u32) { + let adc = unsafe { &*I::ptr() }; + adc.ie().modify(|r, w| unsafe { w.bits(r.bits() | mask) }); + INTERRUPT_TRIGGERED.store(false, Ordering::SeqCst); + } + + pub fn is_interrupt_triggered(&self) -> bool { + INTERRUPT_TRIGGERED.load(Ordering::Relaxed) + } +} + +pub fn get_conv_result() -> Option { + let adc = unsafe { &*pac::Adc1::ptr() }; + let fifo = adc.resfifo0().read().bits(); + const VALID_MASK: u32 = 1 << 31; + if fifo & VALID_MASK == 0 { + return None; + } + + Some(ConvResult { + command_id_source: (fifo >> 24) & 0x0F, + loop_count_index: (fifo >> 20) & 0x0F, + trigger_id_source: (fifo >> 16) & 0x0F, + conv_value: (fifo & 0xFFFF) as u16, + }) +} + +pub fn on_interrupt() { + if get_conv_result().is_some() { + INTERRUPT_TRIGGERED.store(true, Ordering::SeqCst); + } +} + +pub struct AdcHandler; +impl crate::interrupt::typelevel::Handler for AdcHandler { + unsafe fn on_interrupt() { + on_interrupt(); + } +} diff --git a/embassy-mcxa/src/clkout.rs b/embassy-mcxa/src/clkout.rs new file mode 100644 index 000000000..88c731df1 --- /dev/null +++ b/embassy-mcxa/src/clkout.rs @@ -0,0 +1,169 @@ +//! CLKOUT pseudo-peripheral +//! +//! CLKOUT is a part of the clock generation subsystem, and can be used +//! either to generate arbitrary waveforms, or to debug the state of +//! internal oscillators. + +use core::marker::PhantomData; + +use embassy_hal_internal::Peri; + +pub use crate::clocks::periph_helpers::Div4; +use crate::clocks::{with_clocks, ClockError, PoweredClock}; +use crate::pac::mrcc0::mrcc_clkout_clksel::Mux; +use crate::peripherals::CLKOUT; + +/// A peripheral representing the CLKOUT pseudo-peripheral +pub struct ClockOut<'a> { + _p: PhantomData<&'a mut CLKOUT>, + freq: u32, +} + +/// Selected clock source to output +pub enum ClockOutSel { + /// 12MHz Internal Oscillator + Fro12M, + /// FRO180M Internal Oscillator, via divisor + FroHfDiv, + /// External Oscillator + ClkIn, + /// 16KHz oscillator + Clk16K, + /// Output of PLL1 + Pll1Clk, + /// Main System CPU clock, divided by 6 + SlowClk, +} + +/// Configuration for the ClockOut +pub struct Config { + /// Selected Source Clock + pub sel: ClockOutSel, + /// Selected division level + pub div: Div4, + /// Selected power level + pub level: PoweredClock, +} + +impl<'a> ClockOut<'a> { + /// Create a new ClockOut pin. On success, the clock signal will begin immediately + /// on the given pin. + pub fn new( + _peri: Peri<'a, CLKOUT>, + pin: Peri<'a, impl sealed::ClockOutPin>, + cfg: Config, + ) -> Result { + // There's no MRCC enable bit, so we check the validity of the clocks here + // + // TODO: Should we check that the frequency is suitably low? + let (freq, mux) = check_sel(cfg.sel, cfg.level)?; + + // All good! Apply requested config, starting with the pin. + pin.mux(); + + setup_clkout(mux, cfg.div); + + Ok(Self { + _p: PhantomData, + freq: freq / cfg.div.into_divisor(), + }) + } + + /// Frequency of the clkout pin + #[inline] + pub fn frequency(&self) -> u32 { + self.freq + } +} + +impl Drop for ClockOut<'_> { + fn drop(&mut self) { + disable_clkout(); + } +} + +/// Check whether the given clock selection is valid +fn check_sel(sel: ClockOutSel, level: PoweredClock) -> Result<(u32, Mux), ClockError> { + let res = with_clocks(|c| { + Ok(match sel { + ClockOutSel::Fro12M => (c.ensure_fro_hf_active(&level)?, Mux::Clkroot12m), + ClockOutSel::FroHfDiv => (c.ensure_fro_hf_div_active(&level)?, Mux::ClkrootFircDiv), + ClockOutSel::ClkIn => (c.ensure_clk_in_active(&level)?, Mux::ClkrootSosc), + ClockOutSel::Clk16K => (c.ensure_clk_16k_vdd_core_active(&level)?, Mux::Clkroot16k), + ClockOutSel::Pll1Clk => (c.ensure_pll1_clk_active(&level)?, Mux::ClkrootSpll), + ClockOutSel::SlowClk => (c.ensure_slow_clk_active(&level)?, Mux::ClkrootSlow), + }) + }); + let Some(res) = res else { + return Err(ClockError::NeverInitialized); + }; + res +} + +/// Set up the clkout pin using the given mux and div settings +fn setup_clkout(mux: Mux, div: Div4) { + let mrcc = unsafe { crate::pac::Mrcc0::steal() }; + + mrcc.mrcc_clkout_clksel().write(|w| w.mux().variant(mux)); + + // Set up clkdiv + mrcc.mrcc_clkout_clkdiv().write(|w| { + w.halt().set_bit(); + w.reset().set_bit(); + unsafe { w.div().bits(div.into_bits()) }; + w + }); + mrcc.mrcc_clkout_clkdiv().write(|w| { + w.halt().clear_bit(); + w.reset().clear_bit(); + unsafe { w.div().bits(div.into_bits()) }; + w + }); + + while mrcc.mrcc_clkout_clkdiv().read().unstab().bit_is_set() {} +} + +/// Stop the clkout +fn disable_clkout() { + // Stop the output by selecting the "none" clock + // + // TODO: restore the pin to hi-z or something? + let mrcc = unsafe { crate::pac::Mrcc0::steal() }; + mrcc.mrcc_clkout_clkdiv().write(|w| { + w.reset().set_bit(); + w.halt().set_bit(); + unsafe { + w.div().bits(0); + } + w + }); + mrcc.mrcc_clkout_clksel().write(|w| unsafe { w.bits(0b111) }); +} + +mod sealed { + use embassy_hal_internal::PeripheralType; + + use crate::gpio::{Pull, SealedPin}; + + /// Sealed marker trait for clockout pins + pub trait ClockOutPin: PeripheralType { + /// Set the given pin to the correct muxing state + fn mux(&self); + } + + macro_rules! impl_pin { + ($pin:ident, $func:ident) => { + impl ClockOutPin for crate::peripherals::$pin { + fn mux(&self) { + self.set_function(crate::pac::port0::pcr0::Mux::$func); + self.set_pull(Pull::Disabled); + } + } + }; + } + + impl_pin!(P0_6, Mux12); + impl_pin!(P3_6, Mux1); + impl_pin!(P3_8, Mux12); + impl_pin!(P4_2, Mux1); +} diff --git a/embassy-mcxa/src/clocks/config.rs b/embassy-mcxa/src/clocks/config.rs new file mode 100644 index 000000000..0563b8917 --- /dev/null +++ b/embassy-mcxa/src/clocks/config.rs @@ -0,0 +1,204 @@ +//! Clock Configuration +//! +//! This module holds configuration types used for the system clocks. For +//! configuration of individual peripherals, see [`super::periph_helpers`]. + +use super::PoweredClock; + +/// This type represents a divider in the range 1..=256. +/// +/// At a hardware level, this is an 8-bit register from 0..=255, +/// which adds one. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Div8(pub(super) u8); + +impl Div8 { + /// Store a "raw" divisor value that will divide the source by + /// `(n + 1)`, e.g. `Div8::from_raw(0)` will divide the source + /// by 1, and `Div8::from_raw(255)` will divide the source by + /// 256. + pub const fn from_raw(n: u8) -> Self { + Self(n) + } + + /// Divide by one, or no division + pub const fn no_div() -> Self { + Self(0) + } + + /// Store a specific divisor value that will divide the source + /// by `n`. e.g. `Div8::from_divisor(1)` will divide the source + /// by 1, and `Div8::from_divisor(256)` will divide the source + /// by 256. + /// + /// Will return `None` if `n` is not in the range `1..=256`. + /// Consider [`Self::from_raw`] for an infallible version. + pub const fn from_divisor(n: u16) -> Option { + let Some(n) = n.checked_sub(1) else { + return None; + }; + if n > (u8::MAX as u16) { + return None; + } + Some(Self(n as u8)) + } + + /// Convert into "raw" bits form + #[inline(always)] + pub const fn into_bits(self) -> u8 { + self.0 + } + + /// Convert into "divisor" form, as a u32 for convenient frequency math + #[inline(always)] + pub const fn into_divisor(self) -> u32 { + self.0 as u32 + 1 + } +} + +/// ```text +/// ┌─────────────────────────────────────────────────────────┐ +/// │ │ +/// │ ┌───────────┐ clk_out ┌─────────┐ │ +/// XTAL ──────┼──▷│ System │───────────▷│ │ clk_in │ +/// │ │ OSC │ clkout_byp │ MUX │──────────────────┼──────▷ +/// EXTAL ──────┼──▷│ │───────────▷│ │ │ +/// │ └───────────┘ └─────────┘ │ +/// │ │ +/// │ ┌───────────┐ fro_hf_root ┌────┐ fro_hf │ +/// │ │ FRO180 ├───────┬─────▷│ CG │─────────────────────┼──────▷ +/// │ │ │ │ ├────┤ clk_45m │ +/// │ │ │ └─────▷│ CG │─────────────────────┼──────▷ +/// │ └───────────┘ └────┘ │ +/// │ ┌───────────┐ fro_12m_root ┌────┐ fro_12m │ +/// │ │ FRO12M │────────┬─────▷│ CG │────────────────────┼──────▷ +/// │ │ │ │ ├────┤ clk_1m │ +/// │ │ │ └─────▷│1/12│────────────────────┼──────▷ +/// │ └───────────┘ └────┘ │ +/// │ │ +/// │ ┌──────────┐ │ +/// │ │000 │ │ +/// │ clk_in │ │ │ +/// │ ───────────────▷│001 │ │ +/// │ fro_12m │ │ │ +/// │ ───────────────▷│010 │ │ +/// │ fro_hf_root │ │ │ +/// │ ───────────────▷│011 │ main_clk │ +/// │ │ │───────────────────────────┼──────▷ +/// clk_16k ──────┼─────────────────▷│100 │ │ +/// │ none │ │ │ +/// │ ───────────────▷│101 │ │ +/// │ pll1_clk │ │ │ +/// │ ───────────────▷│110 │ │ +/// │ none │ │ │ +/// │ ───────────────▷│111 │ │ +/// │ └──────────┘ │ +/// │ ▲ │ +/// │ │ │ +/// │ SCG SCS │ +/// │ SCG-Lite │ +/// └─────────────────────────────────────────────────────────┘ +/// +/// +/// clk_in ┌─────┐ +/// ───────────────▷│00 │ +/// clk_45m │ │ +/// ───────────────▷│01 │ ┌───────────┐ pll1_clk +/// none │ │─────▷│ SPLL │───────────────▷ +/// ───────────────▷│10 │ └───────────┘ +/// fro_12m │ │ +/// ───────────────▷│11 │ +/// └─────┘ +/// ``` +#[non_exhaustive] +pub struct ClocksConfig { + /// FIRC, FRO180, 45/60/90/180M clock source + pub firc: Option, + /// SIRC, FRO12M, clk_12m clock source + // NOTE: I don't think we *can* disable the SIRC? + pub sirc: SircConfig, + /// FRO16K clock source + pub fro16k: Option, +} + +// FIRC/FRO180M + +/// ```text +/// ┌───────────┐ fro_hf_root ┌────┐ fro_hf +/// │ FRO180M ├───────┬─────▷│GATE│──────────▷ +/// │ │ │ ├────┤ clk_45m +/// │ │ └─────▷│GATE│──────────▷ +/// └───────────┘ └────┘ +/// ``` +#[non_exhaustive] +pub struct FircConfig { + /// Selected clock frequency + pub frequency: FircFreqSel, + /// Selected power state of the clock + pub power: PoweredClock, + /// Is the "fro_hf" gated clock enabled? + pub fro_hf_enabled: bool, + /// Is the "clk_45m" gated clock enabled? + pub clk_45m_enabled: bool, + /// Is the "fro_hf_div" clock enabled? Requires `fro_hf`! + pub fro_hf_div: Option, +} + +/// Selected FIRC frequency +pub enum FircFreqSel { + /// 45MHz Output + Mhz45, + /// 60MHz Output + Mhz60, + /// 90MHz Output + Mhz90, + /// 180MHz Output + Mhz180, +} + +// SIRC/FRO12M + +/// ```text +/// ┌───────────┐ fro_12m_root ┌────┐ fro_12m +/// │ FRO12M │────────┬─────▷│ CG │──────────▷ +/// │ │ │ ├────┤ clk_1m +/// │ │ └─────▷│1/12│──────────▷ +/// └───────────┘ └────┘ +/// ``` +#[non_exhaustive] +pub struct SircConfig { + pub power: PoweredClock, + // peripheral output, aka sirc_12mhz + pub fro_12m_enabled: bool, + /// Is the "fro_lf_div" clock enabled? Requires `fro_12m`! + pub fro_lf_div: Option, +} + +#[non_exhaustive] +pub struct Fro16KConfig { + pub vsys_domain_active: bool, + pub vdd_core_domain_active: bool, +} + +impl Default for ClocksConfig { + fn default() -> Self { + Self { + firc: Some(FircConfig { + frequency: FircFreqSel::Mhz45, + power: PoweredClock::NormalEnabledDeepSleepDisabled, + fro_hf_enabled: true, + clk_45m_enabled: true, + fro_hf_div: None, + }), + sirc: SircConfig { + power: PoweredClock::AlwaysEnabled, + fro_12m_enabled: true, + fro_lf_div: None, + }, + fro16k: Some(Fro16KConfig { + vsys_domain_active: true, + vdd_core_domain_active: true, + }), + } + } +} diff --git a/embassy-mcxa/src/clocks/mod.rs b/embassy-mcxa/src/clocks/mod.rs new file mode 100644 index 000000000..9c9e6ef3d --- /dev/null +++ b/embassy-mcxa/src/clocks/mod.rs @@ -0,0 +1,943 @@ +//! # Clock Module +//! +//! For the MCX-A, we separate clock and peripheral control into two main stages: +//! +//! 1. At startup, e.g. when `embassy_mcxa::init()` is called, we configure the +//! core system clocks, including external and internal oscillators. This +//! configuration is then largely static for the duration of the program. +//! 2. When HAL drivers are created, e.g. `Lpuart::new()` is called, the driver +//! is responsible for two main things: +//! * Ensuring that any required "upstream" core system clocks necessary for +//! clocking the peripheral is active and configured to a reasonable value +//! * Enabling the clock gates for that peripheral, and resetting the peripheral +//! +//! From a user perspective, only step 1 is visible. Step 2 is automatically handled +//! by HAL drivers, using interfaces defined in this module. +//! +//! It is also possible to *view* the state of the clock configuration after [`init()`] +//! has been called, using the [`with_clocks()`] function, which provides a view of the +//! [`Clocks`] structure. +//! +//! ## For HAL driver implementors +//! +//! The majority of peripherals in the MCXA chip are fed from either a "hard-coded" or +//! configurable clock source, e.g. selecting the FROM12M or `clk_1m` as a source. This +//! selection, as well as often any pre-scaler division from that source clock, is made +//! through MRCC registers. +//! +//! Any peripheral that is controlled through the MRCC register can automatically implement +//! the necessary APIs using the `impl_cc_gate!` macro in this module. You will also need +//! to define the configuration surface and steps necessary to fully configure that peripheral +//! from a clocks perspective by: +//! +//! 1. Defining a configuration type in the [`periph_helpers`] module that contains any selects +//! or divisions available to the HAL driver +//! 2. Implementing the [`periph_helpers::SPConfHelper`] trait, which should check that the +//! necessary input clocks are reasonable + +use core::cell::RefCell; + +use config::{ClocksConfig, FircConfig, FircFreqSel, Fro16KConfig, SircConfig}; +use mcxa_pac::scg0::firccsr::{FircFclkPeriphEn, FircSclkPeriphEn, Fircsten}; +use mcxa_pac::scg0::sirccsr::{SircClkPeriphEn, Sircsten}; +use periph_helpers::SPConfHelper; + +use crate::pac; +pub mod config; +pub mod periph_helpers; + +// +// Statics/Consts +// + +/// The state of system core clocks. +/// +/// Initialized by [`init()`], and then unchanged for the remainder of the program. +static CLOCKS: critical_section::Mutex>> = critical_section::Mutex::new(RefCell::new(None)); + +// +// Free functions +// + +/// Initialize the core system clocks with the given [`ClocksConfig`]. +/// +/// This function should be called EXACTLY once at start-up, usually via a +/// call to [`embassy_mcxa::init()`](crate::init()). Subsequent calls will +/// return an error. +pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { + critical_section::with(|cs| { + if CLOCKS.borrow_ref(cs).is_some() { + Err(ClockError::AlreadyInitialized) + } else { + Ok(()) + } + })?; + + let mut clocks = Clocks::default(); + let mut operator = ClockOperator { + clocks: &mut clocks, + config: &settings, + + _mrcc0: unsafe { pac::Mrcc0::steal() }, + scg0: unsafe { pac::Scg0::steal() }, + syscon: unsafe { pac::Syscon::steal() }, + vbat0: unsafe { pac::Vbat0::steal() }, + }; + + operator.configure_firc_clocks()?; + operator.configure_sirc_clocks()?; + operator.configure_fro16k_clocks()?; + + // For now, just use FIRC as the main/cpu clock, which should already be + // the case on reset + assert!(operator.scg0.rccr().read().scs().is_firc()); + let input = operator.clocks.fro_hf_root.clone().unwrap(); + operator.clocks.main_clk = Some(input.clone()); + // We can also assume cpu/system clk == fro_hf because div is /1. + assert_eq!(operator.syscon.ahbclkdiv().read().div().bits(), 0); + operator.clocks.cpu_system_clk = Some(input); + + critical_section::with(|cs| { + let mut clks = CLOCKS.borrow_ref_mut(cs); + assert!(clks.is_none(), "Clock setup race!"); + *clks = Some(clocks); + }); + + Ok(()) +} + +/// Obtain the full clocks structure, calling the given closure in a critical section. +/// +/// The given closure will be called with read-only access to the state of the system +/// clocks. This can be used to query and return the state of a given clock. +/// +/// As the caller's closure will be called in a critical section, care must be taken +/// not to block or cause any other undue delays while accessing. +/// +/// Calls to this function will not succeed until after a successful call to `init()`, +/// and will always return None. +pub fn with_clocks R>(f: F) -> Option { + critical_section::with(|cs| { + let c = CLOCKS.borrow_ref(cs); + let c = c.as_ref()?; + Some(f(c)) + }) +} + +// +// Structs/Enums +// + +/// The `Clocks` structure contains the initialized state of the core system clocks +/// +/// These values are configured by providing [`config::ClocksConfig`] to the [`init()`] function +/// at boot time. +#[derive(Default, Debug, Clone)] +#[non_exhaustive] +pub struct Clocks { + /// The `clk_in` is a clock provided by an external oscillator + pub clk_in: Option, + + // FRO180M stuff + // + /// `fro_hf_root` is the direct output of the `FRO180M` internal oscillator + /// + /// It is used to feed downstream clocks, such as `fro_hf`, `clk_45m`, + /// and `fro_hf_div`. + pub fro_hf_root: Option, + + /// `fro_hf` is the same frequency as `fro_hf_root`, but behind a gate. + pub fro_hf: Option, + + /// `clk_45` is a 45MHz clock, sourced from `fro_hf`. + pub clk_45m: Option, + + /// `fro_hf_div` is a configurable frequency clock, sourced from `fro_hf`. + pub fro_hf_div: Option, + + // + // End FRO180M + + // FRO12M stuff + // + /// `fro_12m_root` is the direct output of the `FRO12M` internal oscillator + /// + /// It is used to feed downstream clocks, such as `fro_12m`, `clk_1m`, + /// `and `fro_lf_div`. + pub fro_12m_root: Option, + + /// `fro_12m` is the same frequency as `fro_12m_root`, but behind a gate. + pub fro_12m: Option, + + /// `clk_1m` is a 1MHz clock, sourced from `fro_12m` + pub clk_1m: Option, + + /// `fro_lf_div` is a configurable frequency clock, sourced from `fro_12m` + pub fro_lf_div: Option, + // + // End FRO12M stuff + /// `clk_16k_vsys` is one of two outputs of the `FRO16K` internal oscillator. + /// + /// Also referred to as `clk_16k[0]` in the datasheet, it feeds peripherals in + /// the system domain, such as the CMP and RTC. + pub clk_16k_vsys: Option, + + /// `clk_16k_vdd_core` is one of two outputs of the `FRO16K` internal oscillator. + /// + /// Also referred to as `clk_16k[1]` in the datasheet, it feeds peripherals in + /// the VDD Core domain, such as the OSTimer or LPUarts. + pub clk_16k_vdd_core: Option, + + /// `main_clk` is the main clock used by the CPU, AHB, APB, IPS bus, and some + /// peripherals. + pub main_clk: Option, + + /// `CPU_CLK` or `SYSTEM_CLK` is the output of `main_clk`, run through the `AHBCLKDIV` + pub cpu_system_clk: Option, + + /// `pll1_clk` is the output of the main system PLL, `pll1`. + pub pll1_clk: Option, +} + +/// `ClockError` is the main error returned when configuring or checking clock state +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum ClockError { + /// The system clocks were never initialized by calling [`init()`] + NeverInitialized, + /// The [`init()`] function was called more than once + AlreadyInitialized, + /// The requested configuration was not possible to fulfill, as the system clocks + /// were not configured in a compatible way + BadConfig { clock: &'static str, reason: &'static str }, + /// The requested configuration was not possible to fulfill, as the required system + /// clocks have not yet been implemented. + NotImplemented { clock: &'static str }, + /// The requested peripheral could not be configured, as the steps necessary to + /// enable it have not yet been implemented. + UnimplementedConfig, +} + +/// Information regarding a system clock +#[derive(Debug, Clone)] +pub struct Clock { + /// The frequency, in Hz, of the given clock + pub frequency: u32, + /// The power state of the clock, e.g. whether it is active in deep sleep mode + /// or not. + pub power: PoweredClock, +} + +/// The power state of a given clock. +/// +/// On the MCX-A, when Deep-Sleep is entered, any clock not configured for Deep Sleep +/// mode will be stopped. This means that any downstream usage, e.g. by peripherals, +/// will also stop. +/// +/// In the future, we will provide an API for entering Deep Sleep, and if there are +/// any peripherals that are NOT using an `AlwaysEnabled` clock active, entry into +/// Deep Sleep will be prevented, in order to avoid misbehaving peripherals. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PoweredClock { + /// The given clock will NOT continue running in Deep Sleep mode + NormalEnabledDeepSleepDisabled, + /// The given clock WILL continue running in Deep Sleep mode + AlwaysEnabled, +} + +/// The ClockOperator is a private helper type that contains the methods used +/// during system clock initialization. +/// +/// # SAFETY +/// +/// Concurrent access to clock-relevant peripheral registers, such as `MRCC`, `SCG`, +/// `SYSCON`, and `VBAT` should not be allowed for the duration of the [`init()`] function. +struct ClockOperator<'a> { + /// A mutable reference to the current state of system clocks + clocks: &'a mut Clocks, + /// A reference to the requested configuration provided by the caller of [`init()`] + config: &'a ClocksConfig, + + // We hold on to stolen peripherals + _mrcc0: pac::Mrcc0, + scg0: pac::Scg0, + syscon: pac::Syscon, + vbat0: pac::Vbat0, +} + +/// Trait describing an AHB clock gate that can be toggled through MRCC. +pub trait Gate { + type MrccPeriphConfig: SPConfHelper; + + /// Enable the clock gate. + /// + /// # SAFETY + /// + /// The current peripheral must be disabled prior to calling this method + unsafe fn enable_clock(); + + /// Disable the clock gate. + /// + /// # SAFETY + /// + /// There must be no active user of this peripheral when calling this method + unsafe fn disable_clock(); + + /// Drive the peripheral into reset. + /// + /// # SAFETY + /// + /// There must be no active user of this peripheral when calling this method + unsafe fn assert_reset(); + + /// Drive the peripheral out of reset. + /// + /// # SAFETY + /// + /// There must be no active user of this peripheral when calling this method + unsafe fn release_reset(); + + /// Return whether the clock gate for this peripheral is currently enabled. + fn is_clock_enabled() -> bool; + + /// Return whether the peripheral is currently held in reset. + fn is_reset_released() -> bool; +} + +/// This is the primary helper method HAL drivers are expected to call when creating +/// an instance of the peripheral. +/// +/// This method: +/// +/// 1. Enables the MRCC clock gate for this peripheral +/// 2. Calls the `G::MrccPeriphConfig::post_enable_config()` method, returning an error +/// and re-disabling the peripheral if this fails. +/// 3. Pulses the MRCC reset line, to reset the peripheral to the default state +/// 4. Returns the frequency, in Hz that is fed into the peripheral, taking into account +/// the selected upstream clock, as well as any division specified by `cfg`. +/// +/// NOTE: if a clock is disabled, sourced from an "ambient" clock source, this method +/// may return `Ok(0)`. In the future, this might be updated to return the correct +/// "ambient" clock, e.g. the AHB/APB frequency. +/// +/// # SAFETY +/// +/// This peripheral must not yet be in use prior to calling `enable_and_reset`. +#[inline] +pub unsafe fn enable_and_reset(cfg: &G::MrccPeriphConfig) -> Result { + let freq = enable::(cfg).inspect_err(|_| disable::())?; + pulse_reset::(); + Ok(freq) +} + +/// Enable the clock gate for the given peripheral. +/// +/// Prefer [`enable_and_reset`] unless you are specifically avoiding a pulse of the reset, or need +/// to control the duration of the pulse more directly. +/// +/// # SAFETY +/// +/// This peripheral must not yet be in use prior to calling `enable`. +#[inline] +pub unsafe fn enable(cfg: &G::MrccPeriphConfig) -> Result { + G::enable_clock(); + while !G::is_clock_enabled() {} + core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags)); + + let freq = critical_section::with(|cs| { + let clocks = CLOCKS.borrow_ref(cs); + let clocks = clocks.as_ref().ok_or(ClockError::NeverInitialized)?; + cfg.post_enable_config(clocks) + }); + + freq.inspect_err(|_e| { + G::disable_clock(); + }) +} + +/// Disable the clock gate for the given peripheral. +/// +/// # SAFETY +/// +/// This peripheral must no longer be in use prior to calling `enable`. +#[allow(dead_code)] +#[inline] +pub unsafe fn disable() { + G::disable_clock(); +} + +/// Check whether a gate is currently enabled. +#[allow(dead_code)] +#[inline] +pub fn is_clock_enabled() -> bool { + G::is_clock_enabled() +} + +/// Release a reset line for the given peripheral set. +/// +/// Prefer [`enable_and_reset`]. +/// +/// # SAFETY +/// +/// This peripheral must not yet be in use prior to calling `release_reset`. +#[inline] +pub unsafe fn release_reset() { + G::release_reset(); +} + +/// Assert a reset line for the given peripheral set. +/// +/// Prefer [`enable_and_reset`]. +/// +/// # SAFETY +/// +/// This peripheral must not yet be in use prior to calling `assert_reset`. +#[inline] +pub unsafe fn assert_reset() { + G::assert_reset(); +} + +/// Check whether the peripheral is held in reset. +#[inline] +pub unsafe fn is_reset_released() -> bool { + G::is_reset_released() +} + +/// Pulse a reset line (assert then release) with a short delay. +/// +/// Prefer [`enable_and_reset`]. +/// +/// # SAFETY +/// +/// This peripheral must not yet be in use prior to calling `release_reset`. +#[inline] +pub unsafe fn pulse_reset() { + G::assert_reset(); + cortex_m::asm::nop(); + cortex_m::asm::nop(); + G::release_reset(); +} + +// +// `impl`s for structs/enums +// + +/// The [`Clocks`] type's methods generally take the form of "ensure X clock is active". +/// +/// These methods are intended to be used by HAL peripheral implementors to ensure that their +/// selected clocks are active at a suitable level at time of construction. These methods +/// return the frequency of the requested clock, in Hertz, or a [`ClockError`]. +impl Clocks { + /// Ensure the `fro_lf_div` clock is active and valid at the given power state. + pub fn ensure_fro_lf_div_active(&self, at_level: &PoweredClock) -> Result { + let Some(clk) = self.fro_lf_div.as_ref() else { + return Err(ClockError::BadConfig { + clock: "fro_lf_div", + reason: "required but not active", + }); + }; + if !clk.power.meets_requirement_of(at_level) { + return Err(ClockError::BadConfig { + clock: "fro_lf_div", + reason: "not low power active", + }); + } + Ok(clk.frequency) + } + + /// Ensure the `fro_hf` clock is active and valid at the given power state. + pub fn ensure_fro_hf_active(&self, at_level: &PoweredClock) -> Result { + let Some(clk) = self.fro_hf.as_ref() else { + return Err(ClockError::BadConfig { + clock: "fro_hf", + reason: "required but not active", + }); + }; + if !clk.power.meets_requirement_of(at_level) { + return Err(ClockError::BadConfig { + clock: "fro_hf", + reason: "not low power active", + }); + } + Ok(clk.frequency) + } + + /// Ensure the `fro_hf_div` clock is active and valid at the given power state. + pub fn ensure_fro_hf_div_active(&self, at_level: &PoweredClock) -> Result { + let Some(clk) = self.fro_hf_div.as_ref() else { + return Err(ClockError::BadConfig { + clock: "fro_hf_div", + reason: "required but not active", + }); + }; + if !clk.power.meets_requirement_of(at_level) { + return Err(ClockError::BadConfig { + clock: "fro_hf_div", + reason: "not low power active", + }); + } + Ok(clk.frequency) + } + + /// Ensure the `clk_in` clock is active and valid at the given power state. + pub fn ensure_clk_in_active(&self, _at_level: &PoweredClock) -> Result { + Err(ClockError::NotImplemented { clock: "clk_in" }) + } + + /// Ensure the `clk_16k_vsys` clock is active and valid at the given power state. + pub fn ensure_clk_16k_vsys_active(&self, _at_level: &PoweredClock) -> Result { + // NOTE: clk_16k is always active in low power mode + Ok(self + .clk_16k_vsys + .as_ref() + .ok_or(ClockError::BadConfig { + clock: "clk_16k_vsys", + reason: "required but not active", + })? + .frequency) + } + + /// Ensure the `clk_16k_vdd_core` clock is active and valid at the given power state. + pub fn ensure_clk_16k_vdd_core_active(&self, _at_level: &PoweredClock) -> Result { + // NOTE: clk_16k is always active in low power mode + Ok(self + .clk_16k_vdd_core + .as_ref() + .ok_or(ClockError::BadConfig { + clock: "clk_16k_vdd_core", + reason: "required but not active", + })? + .frequency) + } + + /// Ensure the `clk_1m` clock is active and valid at the given power state. + pub fn ensure_clk_1m_active(&self, at_level: &PoweredClock) -> Result { + let Some(clk) = self.clk_1m.as_ref() else { + return Err(ClockError::BadConfig { + clock: "clk_1m", + reason: "required but not active", + }); + }; + if !clk.power.meets_requirement_of(at_level) { + return Err(ClockError::BadConfig { + clock: "clk_1m", + reason: "not low power active", + }); + } + Ok(clk.frequency) + } + + /// Ensure the `pll1_clk` clock is active and valid at the given power state. + pub fn ensure_pll1_clk_active(&self, _at_level: &PoweredClock) -> Result { + Err(ClockError::NotImplemented { clock: "pll1_clk" }) + } + + /// Ensure the `pll1_clk_div` clock is active and valid at the given power state. + pub fn ensure_pll1_clk_div_active(&self, _at_level: &PoweredClock) -> Result { + Err(ClockError::NotImplemented { clock: "pll1_clk_div" }) + } + + /// Ensure the `CPU_CLK` or `SYSTEM_CLK` is active + pub fn ensure_cpu_system_clk_active(&self, at_level: &PoweredClock) -> Result { + let Some(clk) = self.cpu_system_clk.as_ref() else { + return Err(ClockError::BadConfig { + clock: "cpu_system_clk", + reason: "required but not active", + }); + }; + // Can the main_clk ever be active in deep sleep? I think it is gated? + match at_level { + PoweredClock::NormalEnabledDeepSleepDisabled => {} + PoweredClock::AlwaysEnabled => { + return Err(ClockError::BadConfig { + clock: "main_clk", + reason: "not low power active", + }); + } + } + + Ok(clk.frequency) + } + + pub fn ensure_slow_clk_active(&self, at_level: &PoweredClock) -> Result { + let freq = self.ensure_cpu_system_clk_active(at_level)?; + + Ok(freq / 6) + } +} + +impl PoweredClock { + /// Does THIS clock meet the power requirements of the OTHER clock? + pub fn meets_requirement_of(&self, other: &Self) -> bool { + match (self, other) { + (PoweredClock::NormalEnabledDeepSleepDisabled, PoweredClock::AlwaysEnabled) => false, + (PoweredClock::NormalEnabledDeepSleepDisabled, PoweredClock::NormalEnabledDeepSleepDisabled) => true, + (PoweredClock::AlwaysEnabled, PoweredClock::NormalEnabledDeepSleepDisabled) => true, + (PoweredClock::AlwaysEnabled, PoweredClock::AlwaysEnabled) => true, + } + } +} + +impl ClockOperator<'_> { + /// Configure the FIRC/FRO180M clock family + /// + /// NOTE: Currently we require this to be a fairly hardcoded value, as this clock is used + /// as the main clock used for the CPU, AHB, APB, etc. + fn configure_firc_clocks(&mut self) -> Result<(), ClockError> { + const HARDCODED_ERR: Result<(), ClockError> = Err(ClockError::BadConfig { + clock: "firc", + reason: "For now, FIRC must be enabled and in default state!", + }); + + // Did the user give us a FIRC config? + let Some(firc) = self.config.firc.as_ref() else { + return HARDCODED_ERR; + }; + // Is the FIRC set to 45MHz (should be reset default) + if !matches!(firc.frequency, FircFreqSel::Mhz45) { + return HARDCODED_ERR; + } + let base_freq = 45_000_000; + + // Now, check if the FIRC as expected for our hardcoded value + let mut firc_ok = true; + + // Is the hardware currently set to the default 45MHz? + // + // NOTE: the SVD currently has the wrong(?) values for these: + // 45 -> 48 + // 60 -> 64 + // 90 -> 96 + // 180 -> 192 + // Probably correct-ish, but for a different trim value? + firc_ok &= self.scg0.firccfg().read().freq_sel().is_firc_48mhz_192s(); + + // Check some values in the CSR + let csr = self.scg0.firccsr().read(); + // Is it enabled? + firc_ok &= csr.fircen().is_enabled(); + // Is it accurate? + firc_ok &= csr.fircacc().is_enabled_and_valid(); + // Is there no error? + firc_ok &= csr.fircerr().is_error_not_detected(); + // Is the FIRC the system clock? + firc_ok &= csr.fircsel().is_firc(); + // Is it valid? + firc_ok &= csr.fircvld().is_enabled_and_valid(); + + // Are we happy with the current (hardcoded) state? + if !firc_ok { + return HARDCODED_ERR; + } + + // Note that the fro_hf_root is active + self.clocks.fro_hf_root = Some(Clock { + frequency: base_freq, + power: firc.power, + }); + + // Okay! Now we're past that, let's enable all the downstream clocks. + let FircConfig { + frequency: _, + power, + fro_hf_enabled, + clk_45m_enabled, + fro_hf_div, + } = firc; + + // When is the FRO enabled? + let pow_set = match power { + PoweredClock::NormalEnabledDeepSleepDisabled => Fircsten::DisabledInStopModes, + PoweredClock::AlwaysEnabled => Fircsten::EnabledInStopModes, + }; + + // Do we enable the `fro_hf` output? + let fro_hf_set = if *fro_hf_enabled { + self.clocks.fro_hf = Some(Clock { + frequency: base_freq, + power: *power, + }); + FircFclkPeriphEn::Enabled + } else { + FircFclkPeriphEn::Disabled + }; + + // Do we enable the `clk_45m` output? + let clk_45m_set = if *clk_45m_enabled { + self.clocks.clk_45m = Some(Clock { + frequency: 45_000_000, + power: *power, + }); + FircSclkPeriphEn::Enabled + } else { + FircSclkPeriphEn::Disabled + }; + + self.scg0.firccsr().modify(|_r, w| { + w.fircsten().variant(pow_set); + w.firc_fclk_periph_en().variant(fro_hf_set); + w.firc_sclk_periph_en().variant(clk_45m_set); + w + }); + + // Do we enable the `fro_hf_div` output? + if let Some(d) = fro_hf_div.as_ref() { + // We need `fro_hf` to be enabled + if !*fro_hf_enabled { + return Err(ClockError::BadConfig { + clock: "fro_hf_div", + reason: "fro_hf not enabled", + }); + } + + // Halt and reset the div; then set our desired div. + self.syscon.frohfdiv().write(|w| { + w.halt().halt(); + w.reset().asserted(); + unsafe { w.div().bits(d.into_bits()) }; + w + }); + // Then unhalt it, and reset it + self.syscon.frohfdiv().write(|w| { + w.halt().run(); + w.reset().released(); + w + }); + + // Wait for clock to stabilize + while self.syscon.frohfdiv().read().unstab().is_ongoing() {} + + // Store off the clock info + self.clocks.fro_hf_div = Some(Clock { + frequency: base_freq / d.into_divisor(), + power: *power, + }); + } + + Ok(()) + } + + /// Configure the SIRC/FRO12M clock family + fn configure_sirc_clocks(&mut self) -> Result<(), ClockError> { + let SircConfig { + power, + fro_12m_enabled, + fro_lf_div, + } = &self.config.sirc; + let base_freq = 12_000_000; + + // Allow writes + self.scg0.sirccsr().modify(|_r, w| w.lk().write_enabled()); + self.clocks.fro_12m_root = Some(Clock { + frequency: base_freq, + power: *power, + }); + + let deep = match power { + PoweredClock::NormalEnabledDeepSleepDisabled => Sircsten::Disabled, + PoweredClock::AlwaysEnabled => Sircsten::Enabled, + }; + let pclk = if *fro_12m_enabled { + self.clocks.fro_12m = Some(Clock { + frequency: base_freq, + power: *power, + }); + self.clocks.clk_1m = Some(Clock { + frequency: base_freq / 12, + power: *power, + }); + SircClkPeriphEn::Enabled + } else { + SircClkPeriphEn::Disabled + }; + + // Set sleep/peripheral usage + self.scg0.sirccsr().modify(|_r, w| { + w.sircsten().variant(deep); + w.sirc_clk_periph_en().variant(pclk); + w + }); + + while self.scg0.sirccsr().read().sircvld().is_disabled_or_not_valid() {} + if self.scg0.sirccsr().read().sircerr().is_error_detected() { + return Err(ClockError::BadConfig { + clock: "sirc", + reason: "error set", + }); + } + + // reset lock + self.scg0.sirccsr().modify(|_r, w| w.lk().write_disabled()); + + // Do we enable the `fro_lf_div` output? + if let Some(d) = fro_lf_div.as_ref() { + // We need `fro_lf` to be enabled + if !*fro_12m_enabled { + return Err(ClockError::BadConfig { + clock: "fro_lf_div", + reason: "fro_12m not enabled", + }); + } + + // Halt and reset the div; then set our desired div. + self.syscon.frolfdiv().write(|w| { + w.halt().halt(); + w.reset().asserted(); + unsafe { w.div().bits(d.into_bits()) }; + w + }); + // Then unhalt it, and reset it + self.syscon.frolfdiv().modify(|_r, w| { + w.halt().run(); + w.reset().released(); + w + }); + + // Wait for clock to stabilize + while self.syscon.frolfdiv().read().unstab().is_ongoing() {} + + // Store off the clock info + self.clocks.fro_lf_div = Some(Clock { + frequency: base_freq / d.into_divisor(), + power: *power, + }); + } + + Ok(()) + } + + /// Configure the FRO16K/clk_16k clock family + fn configure_fro16k_clocks(&mut self) -> Result<(), ClockError> { + let Some(fro16k) = self.config.fro16k.as_ref() else { + return Ok(()); + }; + // Enable FRO16K oscillator + self.vbat0.froctla().modify(|_, w| w.fro_en().set_bit()); + + // Lock the control register + self.vbat0.frolcka().modify(|_, w| w.lock().set_bit()); + + let Fro16KConfig { + vsys_domain_active, + vdd_core_domain_active, + } = fro16k; + + // Enable clock outputs to both VSYS and VDD_CORE domains + // Bit 0: clk_16k0 to VSYS domain + // Bit 1: clk_16k1 to VDD_CORE domain + // + // TODO: Define sub-fields for this register with a PAC patch? + let mut bits = 0; + if *vsys_domain_active { + bits |= 0b01; + self.clocks.clk_16k_vsys = Some(Clock { + frequency: 16_384, + power: PoweredClock::AlwaysEnabled, + }); + } + if *vdd_core_domain_active { + bits |= 0b10; + self.clocks.clk_16k_vdd_core = Some(Clock { + frequency: 16_384, + power: PoweredClock::AlwaysEnabled, + }); + } + self.vbat0.froclke().modify(|_r, w| unsafe { w.clke().bits(bits) }); + + Ok(()) + } +} + +// +// Macros/macro impls +// + +/// This macro is used to implement the [`Gate`] trait for a given peripheral +/// that is controlled by the MRCC peripheral. +macro_rules! impl_cc_gate { + ($name:ident, $clk_reg:ident, $rst_reg:ident, $field:ident, $config:ty) => { + impl Gate for crate::peripherals::$name { + type MrccPeriphConfig = $config; + + #[inline] + unsafe fn enable_clock() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$clk_reg().modify(|_, w| w.$field().enabled()); + } + + #[inline] + unsafe fn disable_clock() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$clk_reg().modify(|_r, w| w.$field().disabled()); + } + + #[inline] + fn is_clock_enabled() -> bool { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$clk_reg().read().$field().is_enabled() + } + + #[inline] + unsafe fn release_reset() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$rst_reg().modify(|_, w| w.$field().enabled()); + } + + #[inline] + unsafe fn assert_reset() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$rst_reg().modify(|_, w| w.$field().disabled()); + } + + #[inline] + fn is_reset_released() -> bool { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$rst_reg().read().$field().is_enabled() + } + } + }; +} + +/// This module contains implementations of MRCC APIs, specifically of the [`Gate`] trait, +/// for various low level peripherals. +pub(crate) mod gate { + #[cfg(not(feature = "time"))] + use super::periph_helpers::OsTimerConfig; + use super::periph_helpers::{AdcConfig, Lpi2cConfig, LpuartConfig, NoConfig}; + use super::*; + + // These peripherals have no additional upstream clocks or configuration required + // other than enabling through the MRCC gate. Currently, these peripherals will + // ALWAYS return `Ok(0)` when calling [`enable_and_reset()`] and/or + // [`SPConfHelper::post_enable_config()`]. + impl_cc_gate!(PORT0, mrcc_glb_cc1, mrcc_glb_rst1, port0, NoConfig); + impl_cc_gate!(PORT1, mrcc_glb_cc1, mrcc_glb_rst1, port1, NoConfig); + impl_cc_gate!(PORT2, mrcc_glb_cc1, mrcc_glb_rst1, port2, NoConfig); + impl_cc_gate!(PORT3, mrcc_glb_cc1, mrcc_glb_rst1, port3, NoConfig); + impl_cc_gate!(PORT4, mrcc_glb_cc1, mrcc_glb_rst1, port4, NoConfig); + + impl_cc_gate!(GPIO0, mrcc_glb_cc2, mrcc_glb_rst2, gpio0, NoConfig); + impl_cc_gate!(GPIO1, mrcc_glb_cc2, mrcc_glb_rst2, gpio1, NoConfig); + impl_cc_gate!(GPIO2, mrcc_glb_cc2, mrcc_glb_rst2, gpio2, NoConfig); + impl_cc_gate!(GPIO3, mrcc_glb_cc2, mrcc_glb_rst2, gpio3, NoConfig); + impl_cc_gate!(GPIO4, mrcc_glb_cc2, mrcc_glb_rst2, gpio4, NoConfig); + + // These peripherals DO have meaningful configuration, and could fail if the system + // clocks do not match their needs. + #[cfg(not(feature = "time"))] + impl_cc_gate!(OSTIMER0, mrcc_glb_cc1, mrcc_glb_rst1, ostimer0, OsTimerConfig); + + impl_cc_gate!(LPI2C0, mrcc_glb_cc0, mrcc_glb_rst0, lpi2c0, Lpi2cConfig); + impl_cc_gate!(LPI2C1, mrcc_glb_cc0, mrcc_glb_rst0, lpi2c1, Lpi2cConfig); + impl_cc_gate!(LPI2C2, mrcc_glb_cc1, mrcc_glb_rst1, lpi2c2, Lpi2cConfig); + impl_cc_gate!(LPI2C3, mrcc_glb_cc1, mrcc_glb_rst1, lpi2c3, Lpi2cConfig); + + impl_cc_gate!(LPUART0, mrcc_glb_cc0, mrcc_glb_rst0, lpuart0, LpuartConfig); + impl_cc_gate!(LPUART1, mrcc_glb_cc0, mrcc_glb_rst0, lpuart1, LpuartConfig); + impl_cc_gate!(LPUART2, mrcc_glb_cc0, mrcc_glb_rst0, lpuart2, LpuartConfig); + impl_cc_gate!(LPUART3, mrcc_glb_cc0, mrcc_glb_rst0, lpuart3, LpuartConfig); + impl_cc_gate!(LPUART4, mrcc_glb_cc0, mrcc_glb_rst0, lpuart4, LpuartConfig); + impl_cc_gate!(LPUART5, mrcc_glb_cc1, mrcc_glb_rst1, lpuart5, LpuartConfig); + impl_cc_gate!(ADC1, mrcc_glb_cc1, mrcc_glb_rst1, adc1, AdcConfig); +} diff --git a/embassy-mcxa/src/clocks/periph_helpers.rs b/embassy-mcxa/src/clocks/periph_helpers.rs new file mode 100644 index 000000000..1ea7a99ed --- /dev/null +++ b/embassy-mcxa/src/clocks/periph_helpers.rs @@ -0,0 +1,502 @@ +//! Peripheral Helpers +//! +//! The purpose of this module is to define the per-peripheral special handling +//! required from a clocking perspective. Different peripherals have different +//! selectable source clocks, and some peripherals have additional pre-dividers +//! that can be used. +//! +//! See the docs of [`SPConfHelper`] for more details. + +use super::{ClockError, Clocks, PoweredClock}; +use crate::pac; + +/// Sealed Peripheral Configuration Helper +/// +/// NOTE: the name "sealed" doesn't *totally* make sense because its not sealed yet in the +/// embassy-mcxa project, but it derives from embassy-imxrt where it is. We should +/// fix the name, or actually do the sealing of peripherals. +/// +/// This trait serves to act as a per-peripheral customization for clocking behavior. +/// +/// This trait should be implemented on a configuration type for a given peripheral, and +/// provide the methods that will be called by the higher level operations like +/// `embassy_mcxa::clocks::enable_and_reset()`. +pub trait SPConfHelper { + /// This method is called AFTER a given MRCC peripheral has been enabled (e.g. un-gated), + /// but BEFORE the peripheral reset line is reset. + /// + /// This function should check that any relevant upstream clocks are enabled, are in a + /// reasonable power state, and that the requested configuration can be made. If any of + /// these checks fail, an `Err(ClockError)` should be returned, likely `ClockError::BadConfig`. + /// + /// This function SHOULD NOT make any changes to the system clock configuration, even + /// unsafely, as this should remain static for the duration of the program. + /// + /// This function WILL be called in a critical section, care should be taken not to delay + /// for an unreasonable amount of time. + /// + /// On success, this function MUST return an `Ok(freq)`, where `freq` is the frequency + /// fed into the peripheral, taking into account the selected source clock, as well as + /// any pre-divisors. + fn post_enable_config(&self, clocks: &Clocks) -> Result; +} + +/// Copy and paste macro that: +/// +/// * Sets the clocksel mux to `$selvar` +/// * Resets and halts the div, and applies the calculated div4 bits +/// * Releases reset + halt +/// * Waits for the div to stabilize +/// * Returns `Ok($freq / $conf.div.into_divisor())` +/// +/// Assumes: +/// +/// * self is a configuration struct that has a field called `div`, which +/// is a `Div4` +/// +/// usage: +/// +/// ```rust +/// apply_div4!(self, clksel, clkdiv, variant, freq) +/// ``` +/// +/// In the future if we make all the clksel+clkdiv pairs into commonly derivedFrom +/// registers, or if we put some kind of simple trait around those regs, we could +/// do this with something other than a macro, but for now, this is harm-reduction +/// to avoid incorrect copy + paste +macro_rules! apply_div4 { + ($conf:ident, $selreg:ident, $divreg:ident, $selvar:ident, $freq:ident) => {{ + // set clksel + $selreg.modify(|_r, w| w.mux().variant($selvar)); + + // Set up clkdiv + $divreg.modify(|_r, w| { + unsafe { w.div().bits($conf.div.into_bits()) } + .halt() + .asserted() + .reset() + .asserted() + }); + $divreg.modify(|_r, w| w.halt().deasserted().reset().deasserted()); + + while $divreg.read().unstab().is_unstable() {} + + Ok($freq / $conf.div.into_divisor()) + }}; +} + +// config types + +/// This type represents a divider in the range 1..=16. +/// +/// At a hardware level, this is an 8-bit register from 0..=15, +/// which adds one. +/// +/// While the *clock* domain seems to use 8-bit dividers, the *peripheral* domain +/// seems to use 4 bit dividers! +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Div4(pub(super) u8); + +impl Div4 { + /// Divide by one, or no division + pub const fn no_div() -> Self { + Self(0) + } + + /// Store a "raw" divisor value that will divide the source by + /// `(n + 1)`, e.g. `Div4::from_raw(0)` will divide the source + /// by 1, and `Div4::from_raw(15)` will divide the source by + /// 16. + pub const fn from_raw(n: u8) -> Option { + if n > 0b1111 { + None + } else { + Some(Self(n)) + } + } + + /// Store a specific divisor value that will divide the source + /// by `n`. e.g. `Div4::from_divisor(1)` will divide the source + /// by 1, and `Div4::from_divisor(16)` will divide the source + /// by 16. + /// + /// Will return `None` if `n` is not in the range `1..=16`. + /// Consider [`Self::from_raw`] for an infallible version. + pub const fn from_divisor(n: u8) -> Option { + let Some(n) = n.checked_sub(1) else { + return None; + }; + if n > 0b1111 { + return None; + } + Some(Self(n)) + } + + /// Convert into "raw" bits form + #[inline(always)] + pub const fn into_bits(self) -> u8 { + self.0 + } + + /// Convert into "divisor" form, as a u32 for convenient frequency math + #[inline(always)] + pub const fn into_divisor(self) -> u32 { + self.0 as u32 + 1 + } +} + +/// A basic type that always returns an error when `post_enable_config` is called. +/// +/// Should only be used as a placeholder. +pub struct UnimplementedConfig; + +impl SPConfHelper for UnimplementedConfig { + fn post_enable_config(&self, _clocks: &Clocks) -> Result { + Err(ClockError::UnimplementedConfig) + } +} + +/// A basic type that always returns `Ok(0)` when `post_enable_config` is called. +/// +/// This should only be used for peripherals that are "ambiently" clocked, like `PORTn` +/// peripherals, which have no selectable/configurable source clock. +pub struct NoConfig; +impl SPConfHelper for NoConfig { + fn post_enable_config(&self, _clocks: &Clocks) -> Result { + Ok(0) + } +} + +// +// LPI2c +// + +/// Selectable clocks for `Lpi2c` peripherals +#[derive(Debug, Clone, Copy)] +pub enum Lpi2cClockSel { + /// FRO12M/FRO_LF/SIRC clock source, passed through divider + /// "fro_lf_div" + FroLfDiv, + /// FRO180M/FRO_HF/FIRC clock source, passed through divider + /// "fro_hf_div" + FroHfDiv, + /// SOSC/XTAL/EXTAL clock source + ClkIn, + /// clk_1m/FRO_LF divided by 12 + Clk1M, + /// Output of PLL1, passed through clock divider, + /// "pll1_clk_div", maybe "pll1_lf_div"? + Pll1ClkDiv, + /// Disabled + None, +} + +/// Which instance of the `Lpi2c` is this? +/// +/// Should not be directly selectable by end-users. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Lpi2cInstance { + /// Instance 0 + Lpi2c0, + /// Instance 1 + Lpi2c1, + /// Instance 2 + Lpi2c2, + /// Instance 3 + Lpi2c3, +} + +/// Top level configuration for `Lpi2c` instances. +pub struct Lpi2cConfig { + /// Power state required for this peripheral + pub power: PoweredClock, + /// Clock source + pub source: Lpi2cClockSel, + /// Clock divisor + pub div: Div4, + /// Which instance is this? + // NOTE: should not be user settable + pub(crate) instance: Lpi2cInstance, +} + +impl SPConfHelper for Lpi2cConfig { + fn post_enable_config(&self, clocks: &Clocks) -> Result { + // check that source is suitable + let mrcc0 = unsafe { pac::Mrcc0::steal() }; + use mcxa_pac::mrcc0::mrcc_lpi2c0_clksel::Mux; + + let (clkdiv, clksel) = match self.instance { + Lpi2cInstance::Lpi2c0 => (mrcc0.mrcc_lpi2c0_clkdiv(), mrcc0.mrcc_lpi2c0_clksel()), + Lpi2cInstance::Lpi2c1 => (mrcc0.mrcc_lpi2c1_clkdiv(), mrcc0.mrcc_lpi2c1_clksel()), + Lpi2cInstance::Lpi2c2 => (mrcc0.mrcc_lpi2c2_clkdiv(), mrcc0.mrcc_lpi2c2_clksel()), + Lpi2cInstance::Lpi2c3 => (mrcc0.mrcc_lpi2c3_clkdiv(), mrcc0.mrcc_lpi2c3_clksel()), + }; + + let (freq, variant) = match self.source { + Lpi2cClockSel::FroLfDiv => { + let freq = clocks.ensure_fro_lf_div_active(&self.power)?; + (freq, Mux::ClkrootFunc0) + } + Lpi2cClockSel::FroHfDiv => { + let freq = clocks.ensure_fro_hf_div_active(&self.power)?; + (freq, Mux::ClkrootFunc2) + } + Lpi2cClockSel::ClkIn => { + let freq = clocks.ensure_clk_in_active(&self.power)?; + (freq, Mux::ClkrootFunc3) + } + Lpi2cClockSel::Clk1M => { + let freq = clocks.ensure_clk_1m_active(&self.power)?; + (freq, Mux::ClkrootFunc5) + } + Lpi2cClockSel::Pll1ClkDiv => { + let freq = clocks.ensure_pll1_clk_div_active(&self.power)?; + (freq, Mux::ClkrootFunc6) + } + Lpi2cClockSel::None => unsafe { + // no ClkrootFunc7, just write manually for now + clksel.write(|w| w.bits(0b111)); + clkdiv.modify(|_r, w| w.reset().asserted().halt().asserted()); + return Ok(0); + }, + }; + + apply_div4!(self, clksel, clkdiv, variant, freq) + } +} + +// +// LPUart +// + +/// Selectable clocks for Lpuart peripherals +#[derive(Debug, Clone, Copy)] +pub enum LpuartClockSel { + /// FRO12M/FRO_LF/SIRC clock source, passed through divider + /// "fro_lf_div" + FroLfDiv, + /// FRO180M/FRO_HF/FIRC clock source, passed through divider + /// "fro_hf_div" + FroHfDiv, + /// SOSC/XTAL/EXTAL clock source + ClkIn, + /// FRO16K/clk_16k source + Clk16K, + /// clk_1m/FRO_LF divided by 12 + Clk1M, + /// Output of PLL1, passed through clock divider, + /// "pll1_clk_div", maybe "pll1_lf_div"? + Pll1ClkDiv, + /// Disabled + None, +} + +/// Which instance of the Lpuart is this? +/// +/// Should not be directly selectable by end-users. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum LpuartInstance { + /// Instance 0 + Lpuart0, + /// Instance 1 + Lpuart1, + /// Instance 2 + Lpuart2, + /// Instance 3 + Lpuart3, + /// Instance 4 + Lpuart4, + /// Instance 5 + Lpuart5, +} + +/// Top level configuration for `Lpuart` instances. +pub struct LpuartConfig { + /// Power state required for this peripheral + pub power: PoweredClock, + /// Clock source + pub source: LpuartClockSel, + /// Clock divisor + pub div: Div4, + /// Which instance is this? + // NOTE: should not be user settable + pub(crate) instance: LpuartInstance, +} + +impl SPConfHelper for LpuartConfig { + fn post_enable_config(&self, clocks: &Clocks) -> Result { + // check that source is suitable + let mrcc0 = unsafe { pac::Mrcc0::steal() }; + use mcxa_pac::mrcc0::mrcc_lpuart0_clksel::Mux; + + let (clkdiv, clksel) = match self.instance { + LpuartInstance::Lpuart0 => (mrcc0.mrcc_lpuart0_clkdiv(), mrcc0.mrcc_lpuart0_clksel()), + LpuartInstance::Lpuart1 => (mrcc0.mrcc_lpuart1_clkdiv(), mrcc0.mrcc_lpuart1_clksel()), + LpuartInstance::Lpuart2 => (mrcc0.mrcc_lpuart2_clkdiv(), mrcc0.mrcc_lpuart2_clksel()), + LpuartInstance::Lpuart3 => (mrcc0.mrcc_lpuart3_clkdiv(), mrcc0.mrcc_lpuart3_clksel()), + LpuartInstance::Lpuart4 => (mrcc0.mrcc_lpuart4_clkdiv(), mrcc0.mrcc_lpuart4_clksel()), + LpuartInstance::Lpuart5 => (mrcc0.mrcc_lpuart5_clkdiv(), mrcc0.mrcc_lpuart5_clksel()), + }; + + let (freq, variant) = match self.source { + LpuartClockSel::FroLfDiv => { + let freq = clocks.ensure_fro_lf_div_active(&self.power)?; + (freq, Mux::ClkrootFunc0) + } + LpuartClockSel::FroHfDiv => { + let freq = clocks.ensure_fro_hf_div_active(&self.power)?; + (freq, Mux::ClkrootFunc2) + } + LpuartClockSel::ClkIn => { + let freq = clocks.ensure_clk_in_active(&self.power)?; + (freq, Mux::ClkrootFunc3) + } + LpuartClockSel::Clk16K => { + let freq = clocks.ensure_clk_16k_vdd_core_active(&self.power)?; + (freq, Mux::ClkrootFunc4) + } + LpuartClockSel::Clk1M => { + let freq = clocks.ensure_clk_1m_active(&self.power)?; + (freq, Mux::ClkrootFunc5) + } + LpuartClockSel::Pll1ClkDiv => { + let freq = clocks.ensure_pll1_clk_div_active(&self.power)?; + (freq, Mux::ClkrootFunc6) + } + LpuartClockSel::None => unsafe { + // no ClkrootFunc7, just write manually for now + clksel.write(|w| w.bits(0b111)); + clkdiv.modify(|_r, w| { + w.reset().asserted(); + w.halt().asserted(); + w + }); + return Ok(0); + }, + }; + + // set clksel + apply_div4!(self, clksel, clkdiv, variant, freq) + } +} + +// +// OSTimer +// + +/// Selectable clocks for the OSTimer peripheral +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum OstimerClockSel { + /// 16k clock, sourced from FRO16K (Vdd Core) + Clk16kVddCore, + /// 1 MHz Clock sourced from FRO12M + Clk1M, + /// Disabled + None, +} + +/// Top level configuration for the `OSTimer` peripheral +pub struct OsTimerConfig { + /// Power state required for this peripheral + pub power: PoweredClock, + /// Selected clock source for this peripheral + pub source: OstimerClockSel, +} + +impl SPConfHelper for OsTimerConfig { + fn post_enable_config(&self, clocks: &Clocks) -> Result { + let mrcc0 = unsafe { pac::Mrcc0::steal() }; + Ok(match self.source { + OstimerClockSel::Clk16kVddCore => { + let freq = clocks.ensure_clk_16k_vdd_core_active(&self.power)?; + mrcc0.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_16k()); + freq + } + OstimerClockSel::Clk1M => { + let freq = clocks.ensure_clk_1m_active(&self.power)?; + mrcc0.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_1m()); + freq + } + OstimerClockSel::None => { + mrcc0.mrcc_ostimer0_clksel().write(|w| unsafe { w.mux().bits(0b11) }); + 0 + } + }) + } +} + +// +// Adc +// + +/// Selectable clocks for the ADC peripheral +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum AdcClockSel { + /// Divided `fro_lf`/`clk_12m`/FRO12M source + FroLfDiv, + /// Gated `fro_hf`/`FRO180M` source + FroHf, + /// External Clock Source + ClkIn, + /// 1MHz clock sourced by a divided `fro_lf`/`clk_12m` + Clk1M, + /// Internal PLL output, with configurable divisor + Pll1ClkDiv, + /// No clock/disabled + None, +} + +/// Top level configuration for the ADC peripheral +pub struct AdcConfig { + /// Power state required for this peripheral + pub power: PoweredClock, + /// Selected clock-source for this peripheral + pub source: AdcClockSel, + /// Pre-divisor, applied to the upstream clock output + pub div: Div4, +} + +impl SPConfHelper for AdcConfig { + fn post_enable_config(&self, clocks: &Clocks) -> Result { + use mcxa_pac::mrcc0::mrcc_adc_clksel::Mux; + let mrcc0 = unsafe { pac::Mrcc0::steal() }; + let (freq, variant) = match self.source { + AdcClockSel::FroLfDiv => { + let freq = clocks.ensure_fro_lf_div_active(&self.power)?; + (freq, Mux::ClkrootFunc0) + } + AdcClockSel::FroHf => { + let freq = clocks.ensure_fro_hf_active(&self.power)?; + (freq, Mux::ClkrootFunc1) + } + AdcClockSel::ClkIn => { + let freq = clocks.ensure_clk_in_active(&self.power)?; + (freq, Mux::ClkrootFunc3) + } + AdcClockSel::Clk1M => { + let freq = clocks.ensure_clk_1m_active(&self.power)?; + (freq, Mux::ClkrootFunc5) + } + AdcClockSel::Pll1ClkDiv => { + let freq = clocks.ensure_pll1_clk_div_active(&self.power)?; + (freq, Mux::ClkrootFunc6) + } + AdcClockSel::None => { + mrcc0.mrcc_adc_clksel().write(|w| unsafe { + // no ClkrootFunc7, just write manually for now + w.mux().bits(0b111) + }); + mrcc0.mrcc_adc_clkdiv().modify(|_r, w| { + w.reset().asserted(); + w.halt().asserted(); + w + }); + return Ok(0); + } + }; + let clksel = mrcc0.mrcc_adc_clksel(); + let clkdiv = mrcc0.mrcc_adc_clkdiv(); + + apply_div4!(self, clksel, clkdiv, variant, freq) + } +} diff --git a/embassy-mcxa/src/config.rs b/embassy-mcxa/src/config.rs new file mode 100644 index 000000000..8d52a1d44 --- /dev/null +++ b/embassy-mcxa/src/config.rs @@ -0,0 +1,27 @@ +// HAL configuration (minimal), mirroring embassy-imxrt style + +use crate::clocks::config::ClocksConfig; +use crate::interrupt::Priority; + +#[non_exhaustive] +pub struct Config { + #[cfg(feature = "time")] + pub time_interrupt_priority: Priority, + pub rtc_interrupt_priority: Priority, + pub adc_interrupt_priority: Priority, + pub gpio_interrupt_priority: Priority, + pub clock_cfg: ClocksConfig, +} + +impl Default for Config { + fn default() -> Self { + Self { + #[cfg(feature = "time")] + time_interrupt_priority: Priority::from(0), + rtc_interrupt_priority: Priority::from(0), + adc_interrupt_priority: Priority::from(0), + gpio_interrupt_priority: Priority::from(0), + clock_cfg: ClocksConfig::default(), + } + } +} diff --git a/embassy-mcxa/src/gpio.rs b/embassy-mcxa/src/gpio.rs new file mode 100644 index 000000000..65f8df985 --- /dev/null +++ b/embassy-mcxa/src/gpio.rs @@ -0,0 +1,1062 @@ +//! GPIO driver built around a type-erased `Flex` pin, similar to other Embassy HALs. +//! The exported `Output`/`Input` drivers own a `Flex` so they no longer depend on the +//! concrete pin type. + +use core::convert::Infallible; +use core::future::Future; +use core::marker::PhantomData; +use core::pin::pin; + +use embassy_hal_internal::{Peri, PeripheralType}; +use maitake_sync::WaitMap; +use paste::paste; + +use crate::pac::interrupt; +use crate::pac::port0::pcr0::{Dse, Inv, Mux, Pe, Ps, Sre}; + +struct BitIter(u32); + +impl Iterator for BitIter { + type Item = usize; + + fn next(&mut self) -> Option { + match self.0.trailing_zeros() { + 32 => None, + b => { + self.0 &= !(1 << b); + Some(b as usize) + } + } + } +} + +const PORT_COUNT: usize = 5; + +static PORT_WAIT_MAPS: [WaitMap; PORT_COUNT] = [ + WaitMap::new(), + WaitMap::new(), + WaitMap::new(), + WaitMap::new(), + WaitMap::new(), +]; + +fn irq_handler(port_index: usize, gpio_base: *const crate::pac::gpio0::RegisterBlock) { + let gpio = unsafe { &*gpio_base }; + let isfr = gpio.isfr0().read().bits(); + + for pin in BitIter(isfr) { + // Clear all pending interrupts + gpio.isfr0().write(|w| unsafe { w.bits(1 << pin) }); + gpio.icr(pin).modify(|_, w| w.irqc().irqc0()); // Disable interrupt + + // Wake the corresponding port waker + if let Some(w) = PORT_WAIT_MAPS.get(port_index) { + w.wake(&pin, ()); + } + } +} + +#[interrupt] +fn GPIO0() { + irq_handler(0, crate::pac::Gpio0::ptr()); +} + +#[interrupt] +fn GPIO1() { + irq_handler(1, crate::pac::Gpio1::ptr()); +} + +#[interrupt] +fn GPIO2() { + irq_handler(2, crate::pac::Gpio2::ptr()); +} + +#[interrupt] +fn GPIO3() { + irq_handler(3, crate::pac::Gpio3::ptr()); +} + +#[interrupt] +fn GPIO4() { + irq_handler(4, crate::pac::Gpio4::ptr()); +} + +pub(crate) unsafe fn init() { + use embassy_hal_internal::interrupt::InterruptExt; + + crate::pac::interrupt::GPIO0.enable(); + crate::pac::interrupt::GPIO1.enable(); + crate::pac::interrupt::GPIO2.enable(); + crate::pac::interrupt::GPIO3.enable(); + crate::pac::interrupt::GPIO4.enable(); + + cortex_m::interrupt::enable(); +} + +/// Logical level for GPIO pins. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Level { + Low, + High, +} + +impl From for Level { + fn from(val: bool) -> Self { + match val { + true => Self::High, + false => Self::Low, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum Pull { + Disabled, + Up, + Down, +} + +impl From for (Pe, Ps) { + fn from(pull: Pull) -> Self { + match pull { + Pull::Disabled => (Pe::Pe0, Ps::Ps0), + Pull::Up => (Pe::Pe1, Ps::Ps1), + Pull::Down => (Pe::Pe1, Ps::Ps0), + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum SlewRate { + Fast, + Slow, +} + +impl From for Sre { + fn from(slew_rate: SlewRate) -> Self { + match slew_rate { + SlewRate::Fast => Sre::Sre0, + SlewRate::Slow => Sre::Sre1, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum DriveStrength { + Normal, + Double, +} + +impl From for Dse { + fn from(strength: DriveStrength) -> Self { + match strength { + DriveStrength::Normal => Dse::Dse0, + DriveStrength::Double => Dse::Dse1, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum Inverter { + Disabled, + Enabled, +} + +impl From for Inv { + fn from(strength: Inverter) -> Self { + match strength { + Inverter::Disabled => Inv::Inv0, + Inverter::Enabled => Inv::Inv1, + } + } +} + +pub type Gpio = crate::peripherals::GPIO0; + +/// Type-erased representation of a GPIO pin. +pub struct AnyPin { + port: usize, + pin: usize, + gpio: &'static crate::pac::gpio0::RegisterBlock, + port_reg: &'static crate::pac::port0::RegisterBlock, + pcr_reg: &'static crate::pac::port0::Pcr0, +} + +impl AnyPin { + /// Create an `AnyPin` from raw components. + fn new( + port: usize, + pin: usize, + gpio: &'static crate::pac::gpio0::RegisterBlock, + port_reg: &'static crate::pac::port0::RegisterBlock, + pcr_reg: &'static crate::pac::port0::Pcr0, + ) -> Self { + Self { + port, + pin, + gpio, + port_reg, + pcr_reg, + } + } + + #[inline(always)] + fn mask(&self) -> u32 { + 1 << self.pin + } + + #[inline(always)] + fn gpio(&self) -> &'static crate::pac::gpio0::RegisterBlock { + self.gpio + } + + #[inline(always)] + pub fn port_index(&self) -> usize { + self.port + } + + #[inline(always)] + pub fn pin_index(&self) -> usize { + self.pin + } + + #[inline(always)] + fn port_reg(&self) -> &'static crate::pac::port0::RegisterBlock { + self.port_reg + } + + #[inline(always)] + fn pcr_reg(&self) -> &'static crate::pac::port0::Pcr0 { + self.pcr_reg + } +} + +embassy_hal_internal::impl_peripheral!(AnyPin); + +pub(crate) trait SealedPin { + fn pin_port(&self) -> usize; + + fn port(&self) -> usize { + self.pin_port() / 32 + } + + fn pin(&self) -> usize { + self.pin_port() % 32 + } + + fn gpio(&self) -> &'static crate::pac::gpio0::RegisterBlock; + + fn port_reg(&self) -> &'static crate::pac::port0::RegisterBlock; + + fn pcr_reg(&self) -> &'static crate::pac::port0::Pcr0; + + fn set_function(&self, function: Mux); + + fn set_pull(&self, pull: Pull); + + fn set_drive_strength(&self, strength: Dse); + + fn set_slew_rate(&self, slew_rate: Sre); + + fn set_enable_input_buffer(&self); +} + +/// GPIO pin trait. +#[allow(private_bounds)] +pub trait GpioPin: SealedPin + Sized + PeripheralType + Into + 'static { + /// Type-erase the pin. + fn degrade(self) -> AnyPin { + // SAFETY: This is only called within the GpioPin trait, which is only + // implemented within this module on valid pin peripherals and thus + // has been verified to be correct. + AnyPin::new(self.port(), self.pin(), self.gpio(), self.port_reg(), self.pcr_reg()) + } +} + +impl SealedPin for AnyPin { + fn pin_port(&self) -> usize { + self.port * 32 + self.pin + } + + fn gpio(&self) -> &'static crate::pac::gpio0::RegisterBlock { + self.gpio() + } + + fn port_reg(&self) -> &'static crate::pac::port0::RegisterBlock { + self.port_reg() + } + + fn pcr_reg(&self) -> &'static crate::pac::port0::Pcr0 { + self.pcr_reg() + } + + fn set_function(&self, function: Mux) { + self.pcr_reg().modify(|_, w| w.mux().variant(function)); + } + + fn set_pull(&self, pull: Pull) { + let (pull_enable, pull_select) = pull.into(); + self.pcr_reg().modify(|_, w| { + w.pe().variant(pull_enable); + w.ps().variant(pull_select) + }); + } + + fn set_drive_strength(&self, strength: Dse) { + self.pcr_reg().modify(|_, w| w.dse().variant(strength)); + } + + fn set_slew_rate(&self, slew_rate: Sre) { + self.pcr_reg().modify(|_, w| w.sre().variant(slew_rate)); + } + + fn set_enable_input_buffer(&self) { + self.pcr_reg().modify(|_, w| w.ibe().ibe1()); + } +} + +impl GpioPin for AnyPin {} + +macro_rules! impl_pin { + ($peri:ident, $port:expr, $pin:expr, $block:ident) => { + paste! { + impl SealedPin for crate::peripherals::$peri { + fn pin_port(&self) -> usize { + $port * 32 + $pin + } + + fn gpio(&self) -> &'static crate::pac::gpio0::RegisterBlock { + unsafe { &*crate::pac::$block::ptr() } + } + + fn port_reg(&self) -> &'static crate::pac::port0::RegisterBlock { + unsafe { &*crate::pac::[]::ptr() } + } + + fn pcr_reg(&self) -> &'static crate::pac::port0::Pcr0 { + self.port_reg().[]() + } + + fn set_function(&self, function: Mux) { + unsafe { + let port_reg = &*crate::pac::[]::ptr(); + port_reg.[]().modify(|_, w| { + w.mux().variant(function) + }); + } + } + + fn set_pull(&self, pull: Pull) { + let port_reg = unsafe {&*crate::pac::[]::ptr()}; + let (pull_enable, pull_select) = pull.into(); + port_reg.[]().modify(|_, w| { + w.pe().variant(pull_enable); + w.ps().variant(pull_select) + }); + } + + fn set_drive_strength(&self, strength: Dse) { + let port_reg = unsafe {&*crate::pac::[]::ptr()}; + port_reg.[]().modify(|_, w| w.dse().variant(strength)); + } + + fn set_slew_rate(&self, slew_rate: Sre) { + let port_reg = unsafe {&*crate::pac::[]::ptr()}; + port_reg.[]().modify(|_, w| w.sre().variant(slew_rate)); + } + + fn set_enable_input_buffer(&self) { + let port_reg = unsafe {&*crate::pac::[]::ptr()}; + port_reg.[]().modify(|_, w| w.ibe().ibe1()); + } + } + + impl GpioPin for crate::peripherals::$peri {} + + impl From for AnyPin { + fn from(value: crate::peripherals::$peri) -> Self { + value.degrade() + } + } + + impl crate::peripherals::$peri { + /// Convenience helper to obtain a type-erased handle to this pin. + pub fn degrade(&self) -> AnyPin { + AnyPin::new(self.port(), self.pin(), self.gpio(), self.port_reg(), self.pcr_reg()) + } + } + } + }; +} + +impl_pin!(P0_0, 0, 0, Gpio0); +impl_pin!(P0_1, 0, 1, Gpio0); +impl_pin!(P0_2, 0, 2, Gpio0); +impl_pin!(P0_3, 0, 3, Gpio0); +impl_pin!(P0_4, 0, 4, Gpio0); +impl_pin!(P0_5, 0, 5, Gpio0); +impl_pin!(P0_6, 0, 6, Gpio0); +impl_pin!(P0_7, 0, 7, Gpio0); +impl_pin!(P0_8, 0, 8, Gpio0); +impl_pin!(P0_9, 0, 9, Gpio0); +impl_pin!(P0_10, 0, 10, Gpio0); +impl_pin!(P0_11, 0, 11, Gpio0); +impl_pin!(P0_12, 0, 12, Gpio0); +impl_pin!(P0_13, 0, 13, Gpio0); +impl_pin!(P0_14, 0, 14, Gpio0); +impl_pin!(P0_15, 0, 15, Gpio0); +impl_pin!(P0_16, 0, 16, Gpio0); +impl_pin!(P0_17, 0, 17, Gpio0); +impl_pin!(P0_18, 0, 18, Gpio0); +impl_pin!(P0_19, 0, 19, Gpio0); +impl_pin!(P0_20, 0, 20, Gpio0); +impl_pin!(P0_21, 0, 21, Gpio0); +impl_pin!(P0_22, 0, 22, Gpio0); +impl_pin!(P0_23, 0, 23, Gpio0); +impl_pin!(P0_24, 0, 24, Gpio0); +impl_pin!(P0_25, 0, 25, Gpio0); +impl_pin!(P0_26, 0, 26, Gpio0); +impl_pin!(P0_27, 0, 27, Gpio0); +impl_pin!(P0_28, 0, 28, Gpio0); +impl_pin!(P0_29, 0, 29, Gpio0); +impl_pin!(P0_30, 0, 30, Gpio0); +impl_pin!(P0_31, 0, 31, Gpio0); + +impl_pin!(P1_0, 1, 0, Gpio1); +impl_pin!(P1_1, 1, 1, Gpio1); +impl_pin!(P1_2, 1, 2, Gpio1); +impl_pin!(P1_3, 1, 3, Gpio1); +impl_pin!(P1_4, 1, 4, Gpio1); +impl_pin!(P1_5, 1, 5, Gpio1); +impl_pin!(P1_6, 1, 6, Gpio1); +impl_pin!(P1_7, 1, 7, Gpio1); +impl_pin!(P1_8, 1, 8, Gpio1); +impl_pin!(P1_9, 1, 9, Gpio1); +impl_pin!(P1_10, 1, 10, Gpio1); +impl_pin!(P1_11, 1, 11, Gpio1); +impl_pin!(P1_12, 1, 12, Gpio1); +impl_pin!(P1_13, 1, 13, Gpio1); +impl_pin!(P1_14, 1, 14, Gpio1); +impl_pin!(P1_15, 1, 15, Gpio1); +impl_pin!(P1_16, 1, 16, Gpio1); +impl_pin!(P1_17, 1, 17, Gpio1); +impl_pin!(P1_18, 1, 18, Gpio1); +impl_pin!(P1_19, 1, 19, Gpio1); +impl_pin!(P1_20, 1, 20, Gpio1); +impl_pin!(P1_21, 1, 21, Gpio1); +impl_pin!(P1_22, 1, 22, Gpio1); +impl_pin!(P1_23, 1, 23, Gpio1); +impl_pin!(P1_24, 1, 24, Gpio1); +impl_pin!(P1_25, 1, 25, Gpio1); +impl_pin!(P1_26, 1, 26, Gpio1); +impl_pin!(P1_27, 1, 27, Gpio1); +impl_pin!(P1_28, 1, 28, Gpio1); +impl_pin!(P1_29, 1, 29, Gpio1); +impl_pin!(P1_30, 1, 30, Gpio1); +impl_pin!(P1_31, 1, 31, Gpio1); + +impl_pin!(P2_0, 2, 0, Gpio2); +impl_pin!(P2_1, 2, 1, Gpio2); +impl_pin!(P2_2, 2, 2, Gpio2); +impl_pin!(P2_3, 2, 3, Gpio2); +impl_pin!(P2_4, 2, 4, Gpio2); +impl_pin!(P2_5, 2, 5, Gpio2); +impl_pin!(P2_6, 2, 6, Gpio2); +impl_pin!(P2_7, 2, 7, Gpio2); +impl_pin!(P2_8, 2, 8, Gpio2); +impl_pin!(P2_9, 2, 9, Gpio2); +impl_pin!(P2_10, 2, 10, Gpio2); +impl_pin!(P2_11, 2, 11, Gpio2); +impl_pin!(P2_12, 2, 12, Gpio2); +impl_pin!(P2_13, 2, 13, Gpio2); +impl_pin!(P2_14, 2, 14, Gpio2); +impl_pin!(P2_15, 2, 15, Gpio2); +impl_pin!(P2_16, 2, 16, Gpio2); +impl_pin!(P2_17, 2, 17, Gpio2); +impl_pin!(P2_18, 2, 18, Gpio2); +impl_pin!(P2_19, 2, 19, Gpio2); +impl_pin!(P2_20, 2, 20, Gpio2); +impl_pin!(P2_21, 2, 21, Gpio2); +impl_pin!(P2_22, 2, 22, Gpio2); +impl_pin!(P2_23, 2, 23, Gpio2); +impl_pin!(P2_24, 2, 24, Gpio2); +impl_pin!(P2_25, 2, 25, Gpio2); +impl_pin!(P2_26, 2, 26, Gpio2); +impl_pin!(P2_27, 2, 27, Gpio2); +impl_pin!(P2_28, 2, 28, Gpio2); +impl_pin!(P2_29, 2, 29, Gpio2); +impl_pin!(P2_30, 2, 30, Gpio2); +impl_pin!(P2_31, 2, 31, Gpio2); + +impl_pin!(P3_0, 3, 0, Gpio3); +impl_pin!(P3_1, 3, 1, Gpio3); +impl_pin!(P3_2, 3, 2, Gpio3); +impl_pin!(P3_3, 3, 3, Gpio3); +impl_pin!(P3_4, 3, 4, Gpio3); +impl_pin!(P3_5, 3, 5, Gpio3); +impl_pin!(P3_6, 3, 6, Gpio3); +impl_pin!(P3_7, 3, 7, Gpio3); +impl_pin!(P3_8, 3, 8, Gpio3); +impl_pin!(P3_9, 3, 9, Gpio3); +impl_pin!(P3_10, 3, 10, Gpio3); +impl_pin!(P3_11, 3, 11, Gpio3); +impl_pin!(P3_12, 3, 12, Gpio3); +impl_pin!(P3_13, 3, 13, Gpio3); +impl_pin!(P3_14, 3, 14, Gpio3); +impl_pin!(P3_15, 3, 15, Gpio3); +impl_pin!(P3_16, 3, 16, Gpio3); +impl_pin!(P3_17, 3, 17, Gpio3); +impl_pin!(P3_18, 3, 18, Gpio3); +impl_pin!(P3_19, 3, 19, Gpio3); +impl_pin!(P3_20, 3, 20, Gpio3); +impl_pin!(P3_21, 3, 21, Gpio3); +impl_pin!(P3_22, 3, 22, Gpio3); +impl_pin!(P3_23, 3, 23, Gpio3); +impl_pin!(P3_24, 3, 24, Gpio3); +impl_pin!(P3_25, 3, 25, Gpio3); +impl_pin!(P3_26, 3, 26, Gpio3); +impl_pin!(P3_27, 3, 27, Gpio3); +impl_pin!(P3_28, 3, 28, Gpio3); +impl_pin!(P3_29, 3, 29, Gpio3); +impl_pin!(P3_30, 3, 30, Gpio3); +impl_pin!(P3_31, 3, 31, Gpio3); + +impl_pin!(P4_0, 4, 0, Gpio4); +impl_pin!(P4_1, 4, 1, Gpio4); +impl_pin!(P4_2, 4, 2, Gpio4); +impl_pin!(P4_3, 4, 3, Gpio4); +impl_pin!(P4_4, 4, 4, Gpio4); +impl_pin!(P4_5, 4, 5, Gpio4); +impl_pin!(P4_6, 4, 6, Gpio4); +impl_pin!(P4_7, 4, 7, Gpio4); +impl_pin!(P4_8, 4, 8, Gpio4); +impl_pin!(P4_9, 4, 9, Gpio4); +impl_pin!(P4_10, 4, 10, Gpio4); +impl_pin!(P4_11, 4, 11, Gpio4); +impl_pin!(P4_12, 4, 12, Gpio4); +impl_pin!(P4_13, 4, 13, Gpio4); +impl_pin!(P4_14, 4, 14, Gpio4); +impl_pin!(P4_15, 4, 15, Gpio4); +impl_pin!(P4_16, 4, 16, Gpio4); +impl_pin!(P4_17, 4, 17, Gpio4); +impl_pin!(P4_18, 4, 18, Gpio4); +impl_pin!(P4_19, 4, 19, Gpio4); +impl_pin!(P4_20, 4, 20, Gpio4); +impl_pin!(P4_21, 4, 21, Gpio4); +impl_pin!(P4_22, 4, 22, Gpio4); +impl_pin!(P4_23, 4, 23, Gpio4); +impl_pin!(P4_24, 4, 24, Gpio4); +impl_pin!(P4_25, 4, 25, Gpio4); +impl_pin!(P4_26, 4, 26, Gpio4); +impl_pin!(P4_27, 4, 27, Gpio4); +impl_pin!(P4_28, 4, 28, Gpio4); +impl_pin!(P4_29, 4, 29, Gpio4); +impl_pin!(P4_30, 4, 30, Gpio4); +impl_pin!(P4_31, 4, 31, Gpio4); + +/// A flexible pin that can be configured as input or output. +pub struct Flex<'d> { + pin: Peri<'d, AnyPin>, + _marker: PhantomData<&'d mut ()>, +} + +impl<'d> Flex<'d> { + /// Wrap the pin in a `Flex`. + /// + /// The pin remains unmodified. The initial output level is unspecified, but + /// can be changed before the pin is put into output mode. + pub fn new(pin: Peri<'d, impl GpioPin>) -> Self { + pin.set_function(Mux::Mux0); + Self { + pin: pin.into(), + _marker: PhantomData, + } + } + + #[inline] + fn gpio(&self) -> &'static crate::pac::gpio0::RegisterBlock { + self.pin.gpio() + } + + #[inline] + fn mask(&self) -> u32 { + self.pin.mask() + } + + /// Put the pin into input mode. + pub fn set_as_input(&mut self) { + let mask = self.mask(); + let gpio = self.gpio(); + + self.set_enable_input_buffer(); + + gpio.pddr().modify(|r, w| unsafe { w.bits(r.bits() & !mask) }); + } + + /// Put the pin into output mode. + pub fn set_as_output(&mut self) { + let mask = self.mask(); + let gpio = self.gpio(); + + self.set_pull(Pull::Disabled); + + gpio.pddr().modify(|r, w| unsafe { w.bits(r.bits() | mask) }); + } + + /// Set output level to High. + #[inline] + pub fn set_high(&mut self) { + self.gpio().psor().write(|w| unsafe { w.bits(self.mask()) }); + } + + /// Set output level to Low. + #[inline] + pub fn set_low(&mut self) { + self.gpio().pcor().write(|w| unsafe { w.bits(self.mask()) }); + } + + /// Set output level to the given `Level`. + #[inline] + pub fn set_level(&mut self, level: Level) { + match level { + Level::High => self.set_high(), + Level::Low => self.set_low(), + } + } + + /// Toggle output level. + #[inline] + pub fn toggle(&mut self) { + self.gpio().ptor().write(|w| unsafe { w.bits(self.mask()) }); + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + (self.gpio().pdir().read().bits() & self.mask()) != 0 + } + + /// Get whether the pin input level is low. + #[inline] + pub fn is_low(&self) -> bool { + !self.is_high() + } + + /// Is the output pin set as high? + #[inline] + pub fn is_set_high(&self) -> bool { + self.is_high() + } + + /// Is the output pin set as low? + #[inline] + pub fn is_set_low(&self) -> bool { + !self.is_set_high() + } + + /// Configure the pin pull up/down level. + pub fn set_pull(&mut self, pull_select: Pull) { + self.pin.set_pull(pull_select); + } + + /// Configure the pin drive strength. + pub fn set_drive_strength(&mut self, strength: DriveStrength) { + self.pin.set_drive_strength(strength.into()); + } + + /// Configure the pin slew rate. + pub fn set_slew_rate(&mut self, slew_rate: SlewRate) { + self.pin.set_slew_rate(slew_rate.into()); + } + + /// Enable input buffer for the pin. + pub fn set_enable_input_buffer(&mut self) { + self.pin.set_enable_input_buffer(); + } + + /// Get pin level. + pub fn get_level(&self) -> Level { + self.is_high().into() + } +} + +/// Async methods +impl<'d> Flex<'d> { + /// Helper function that waits for a given interrupt trigger + async fn wait_for_inner(&mut self, level: crate::pac::gpio0::icr::Irqc) { + // First, ensure that we have a waker that is ready for this port+pin + let w = PORT_WAIT_MAPS[self.pin.port].wait(self.pin.pin); + let mut w = pin!(w); + // Wait for the subscription to occur, which requires polling at least once + // + // This function returns a result, but can only be an Err if: + // + // * We call `.close()` on a WaitMap, which we never do + // * We have a duplicate key, which can't happen because `wait_for_*` methods + // take an &mut ref of their unique port+pin combo + // + // So we wait for it to complete, but ignore the result. + _ = w.as_mut().subscribe().await; + + // Now that our waker is in the map, we can enable the appropriate interrupt + // + // Clear any existing pending interrupt on this pin + self.pin + .gpio() + .isfr0() + .write(|w| unsafe { w.bits(1 << self.pin.pin()) }); + self.pin.gpio().icr(self.pin.pin()).write(|w| w.isf().isf1()); + + // Pin interrupt configuration + self.pin + .gpio() + .icr(self.pin.pin()) + .modify(|_, w| w.irqc().variant(level)); + + // Finally, we can await the matching call to `.wake()` from the interrupt. + // + // Again, technically, this could return a result, but for the same reasons + // as above, this can't be an error in our case, so just wait for it to complete + _ = w.await; + } + + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub fn wait_for_high(&mut self) -> impl Future + use<'_, 'd> { + self.wait_for_inner(crate::pac::gpio0::icr::Irqc::Irqc12) + } + + /// Wait until the pin is low. If it is already low, return immediately. + #[inline] + pub fn wait_for_low(&mut self) -> impl Future + use<'_, 'd> { + self.wait_for_inner(crate::pac::gpio0::icr::Irqc::Irqc8) + } + + /// Wait for the pin to undergo a transition from low to high. + #[inline] + pub fn wait_for_rising_edge(&mut self) -> impl Future + use<'_, 'd> { + self.wait_for_inner(crate::pac::gpio0::icr::Irqc::Irqc9) + } + + /// Wait for the pin to undergo a transition from high to low. + #[inline] + pub fn wait_for_falling_edge(&mut self) -> impl Future + use<'_, 'd> { + self.wait_for_inner(crate::pac::gpio0::icr::Irqc::Irqc10) + } + + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. + #[inline] + pub fn wait_for_any_edge(&mut self) -> impl Future + use<'_, 'd> { + self.wait_for_inner(crate::pac::gpio0::icr::Irqc::Irqc11) + } +} + +/// GPIO output driver that owns a `Flex` pin. +pub struct Output<'d> { + flex: Flex<'d>, +} + +impl<'d> Output<'d> { + /// Create a GPIO output driver for a [GpioPin] with the provided [Level]. + pub fn new(pin: Peri<'d, impl GpioPin>, initial: Level, strength: DriveStrength, slew_rate: SlewRate) -> Self { + let mut flex = Flex::new(pin); + flex.set_level(initial); + flex.set_as_output(); + flex.set_drive_strength(strength); + flex.set_slew_rate(slew_rate); + Self { flex } + } + + /// Set the output as high. + #[inline] + pub fn set_high(&mut self) { + self.flex.set_high(); + } + + /// Set the output as low. + #[inline] + pub fn set_low(&mut self) { + self.flex.set_low(); + } + + /// Set the output level. + #[inline] + pub fn set_level(&mut self, level: Level) { + self.flex.set_level(level); + } + + /// Toggle the output level. + #[inline] + pub fn toggle(&mut self) { + self.flex.toggle(); + } + + /// Is the output pin set as high? + #[inline] + pub fn is_set_high(&self) -> bool { + self.flex.is_high() + } + + /// Is the output pin set as low? + #[inline] + pub fn is_set_low(&self) -> bool { + !self.is_set_high() + } + + /// Expose the inner `Flex` if callers need to reconfigure the pin. + #[inline] + pub fn into_flex(self) -> Flex<'d> { + self.flex + } +} + +/// GPIO input driver that owns a `Flex` pin. +pub struct Input<'d> { + flex: Flex<'d>, +} + +impl<'d> Input<'d> { + /// Create a GPIO input driver for a [GpioPin]. + /// + pub fn new(pin: Peri<'d, impl GpioPin>, pull_select: Pull) -> Self { + let mut flex = Flex::new(pin); + flex.set_as_input(); + flex.set_pull(pull_select); + Self { flex } + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + self.flex.is_high() + } + + /// Get whether the pin input level is low. + #[inline] + pub fn is_low(&self) -> bool { + self.flex.is_low() + } + + /// Expose the inner `Flex` if callers need to reconfigure the pin. + /// + /// Since Drive Strength and Slew Rate are not set when creating the Input + /// pin, they need to be set when converting + #[inline] + pub fn into_flex(mut self, strength: DriveStrength, slew_rate: SlewRate) -> Flex<'d> { + self.flex.set_drive_strength(strength); + self.flex.set_slew_rate(slew_rate); + self.flex + } + + /// Get the pin level. + pub fn get_level(&self) -> Level { + self.flex.get_level() + } +} + +/// Async methods +impl<'d> Input<'d> { + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub fn wait_for_high(&mut self) -> impl Future + use<'_, 'd> { + self.flex.wait_for_high() + } + + /// Wait until the pin is low. If it is already low, return immediately. + #[inline] + pub fn wait_for_low(&mut self) -> impl Future + use<'_, 'd> { + self.flex.wait_for_low() + } + + /// Wait for the pin to undergo a transition from low to high. + #[inline] + pub fn wait_for_rising_edge(&mut self) -> impl Future + use<'_, 'd> { + self.flex.wait_for_rising_edge() + } + + /// Wait for the pin to undergo a transition from high to low. + #[inline] + pub fn wait_for_falling_edge(&mut self) -> impl Future + use<'_, 'd> { + self.flex.wait_for_falling_edge() + } + + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. + #[inline] + pub fn wait_for_any_edge(&mut self) -> impl Future + use<'_, 'd> { + self.flex.wait_for_any_edge() + } +} + +impl embedded_hal_async::digital::Wait for Input<'_> { + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + self.wait_for_high().await; + Ok(()) + } + + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + self.wait_for_low().await; + Ok(()) + } + + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_rising_edge().await; + Ok(()) + } + + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_falling_edge().await; + Ok(()) + } + + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_any_edge().await; + Ok(()) + } +} + +impl embedded_hal_async::digital::Wait for Flex<'_> { + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + self.wait_for_high().await; + Ok(()) + } + + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + self.wait_for_low().await; + Ok(()) + } + + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_rising_edge().await; + Ok(()) + } + + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_falling_edge().await; + Ok(()) + } + + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_any_edge().await; + Ok(()) + } +} + +// Both embedded_hal 0.2 and 1.0 must be supported by embassy HALs. +impl embedded_hal_02::digital::v2::InputPin for Flex<'_> { + // GPIO operations on this block cannot fail, therefor we set the error type + // to Infallible to guarantee that we can only produce Ok variants. + type Error = Infallible; + + #[inline] + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + #[inline] + fn is_low(&self) -> Result { + Ok(self.is_low()) + } +} + +impl embedded_hal_02::digital::v2::InputPin for Input<'_> { + type Error = Infallible; + + #[inline] + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + #[inline] + fn is_low(&self) -> Result { + Ok(self.is_low()) + } +} + +impl embedded_hal_02::digital::v2::OutputPin for Flex<'_> { + type Error = Infallible; + + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_high(); + Ok(()) + } + + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_low(); + Ok(()) + } +} + +impl embedded_hal_02::digital::v2::StatefulOutputPin for Flex<'_> { + #[inline] + fn is_set_high(&self) -> Result { + Ok(self.is_set_high()) + } + + #[inline] + fn is_set_low(&self) -> Result { + Ok(self.is_set_low()) + } +} + +impl embedded_hal_02::digital::v2::ToggleableOutputPin for Flex<'_> { + type Error = Infallible; + + #[inline] + fn toggle(&mut self) -> Result<(), Self::Error> { + self.toggle(); + Ok(()) + } +} + +impl embedded_hal_1::digital::ErrorType for Flex<'_> { + type Error = Infallible; +} + +impl embedded_hal_1::digital::ErrorType for Input<'_> { + type Error = Infallible; +} + +impl embedded_hal_1::digital::ErrorType for Output<'_> { + type Error = Infallible; +} + +impl embedded_hal_1::digital::InputPin for Input<'_> { + #[inline] + fn is_high(&mut self) -> Result { + Ok((*self).is_high()) + } + + #[inline] + fn is_low(&mut self) -> Result { + Ok((*self).is_low()) + } +} + +impl embedded_hal_1::digital::OutputPin for Flex<'_> { + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_high(); + Ok(()) + } + + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_low(); + Ok(()) + } +} + +impl embedded_hal_1::digital::StatefulOutputPin for Flex<'_> { + #[inline] + fn is_set_high(&mut self) -> Result { + Ok((*self).is_set_high()) + } + + #[inline] + fn is_set_low(&mut self) -> Result { + Ok((*self).is_set_low()) + } +} diff --git a/embassy-mcxa/src/i2c/controller.rs b/embassy-mcxa/src/i2c/controller.rs new file mode 100644 index 000000000..182a53aea --- /dev/null +++ b/embassy-mcxa/src/i2c/controller.rs @@ -0,0 +1,742 @@ +//! LPI2C controller driver + +use core::future::Future; +use core::marker::PhantomData; + +use embassy_hal_internal::drop::OnDrop; +use embassy_hal_internal::Peri; +use mcxa_pac::lpi2c0::mtdr::Cmd; + +use super::{Async, Blocking, Error, Instance, InterruptHandler, Mode, Result, SclPin, SdaPin}; +use crate::clocks::periph_helpers::{Div4, Lpi2cClockSel, Lpi2cConfig}; +use crate::clocks::{enable_and_reset, PoweredClock}; +use crate::interrupt::typelevel::Interrupt; +use crate::AnyPin; + +/// Bus speed (nominal SCL, no clock stretching) +#[derive(Clone, Copy, Default, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Speed { + #[default] + /// 100 kbit/sec + Standard, + /// 400 kbit/sec + Fast, + /// 1 Mbit/sec + FastPlus, + /// 3.4 Mbit/sec + UltraFast, +} + +impl From for (u8, u8, u8, u8) { + fn from(value: Speed) -> (u8, u8, u8, u8) { + match value { + Speed::Standard => (0x3d, 0x37, 0x3b, 0x1d), + Speed::Fast => (0x0e, 0x0c, 0x0d, 0x06), + Speed::FastPlus => (0x04, 0x03, 0x03, 0x02), + + // UltraFast is "special". Leaving it unimplemented until + // the driver and the clock API is further stabilized. + Speed::UltraFast => todo!(), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum SendStop { + No, + Yes, +} + +/// I2C controller configuration +#[derive(Clone, Copy, Default)] +#[non_exhaustive] +pub struct Config { + /// Bus speed + pub speed: Speed, +} + +/// I2C Controller Driver. +pub struct I2c<'d, T: Instance, M: Mode> { + _peri: Peri<'d, T>, + _scl: Peri<'d, AnyPin>, + _sda: Peri<'d, AnyPin>, + _phantom: PhantomData, + is_hs: bool, +} + +impl<'d, T: Instance> I2c<'d, T, Blocking> { + /// Create a new blocking instance of the I2C Controller bus driver. + pub fn new_blocking( + peri: Peri<'d, T>, + scl: Peri<'d, impl SclPin>, + sda: Peri<'d, impl SdaPin>, + config: Config, + ) -> Result { + Self::new_inner(peri, scl, sda, config) + } +} + +impl<'d, T: Instance, M: Mode> I2c<'d, T, M> { + fn new_inner( + _peri: Peri<'d, T>, + scl: Peri<'d, impl SclPin>, + sda: Peri<'d, impl SdaPin>, + config: Config, + ) -> Result { + let (power, source, div) = Self::clock_config(config.speed); + + // Enable clocks + let conf = Lpi2cConfig { + power, + source, + div, + instance: T::CLOCK_INSTANCE, + }; + + _ = unsafe { enable_and_reset::(&conf).map_err(Error::ClockSetup)? }; + + scl.mux(); + sda.mux(); + + let _scl = scl.into(); + let _sda = sda.into(); + + Self::set_config(&config)?; + + Ok(Self { + _peri, + _scl, + _sda, + _phantom: PhantomData, + is_hs: config.speed == Speed::UltraFast, + }) + } + + fn set_config(config: &Config) -> Result<()> { + // Disable the controller. + critical_section::with(|_| T::regs().mcr().modify(|_, w| w.men().disabled())); + + // Soft-reset the controller, read and write FIFOs. + Self::reset_fifos(); + critical_section::with(|_| { + T::regs().mcr().modify(|_, w| w.rst().reset()); + // According to Reference Manual section 40.7.1.4, "There + // is no minimum delay required before clearing the + // software reset", therefore we clear it immediately. + T::regs().mcr().modify(|_, w| w.rst().not_reset()); + + T::regs().mcr().modify(|_, w| w.dozen().clear_bit().dbgen().clear_bit()); + }); + + let (clklo, clkhi, sethold, datavd) = config.speed.into(); + + critical_section::with(|_| { + T::regs().mccr0().modify(|_, w| unsafe { + w.clklo() + .bits(clklo) + .clkhi() + .bits(clkhi) + .sethold() + .bits(sethold) + .datavd() + .bits(datavd) + }) + }); + + // Enable the controller. + critical_section::with(|_| T::regs().mcr().modify(|_, w| w.men().enabled())); + + // Clear all flags + T::regs().msr().write(|w| { + w.epf() + .clear_bit_by_one() + .sdf() + .clear_bit_by_one() + .ndf() + .clear_bit_by_one() + .alf() + .clear_bit_by_one() + .fef() + .clear_bit_by_one() + .pltf() + .clear_bit_by_one() + .dmf() + .clear_bit_by_one() + .stf() + .clear_bit_by_one() + }); + + Ok(()) + } + + // REVISIT: turn this into a function of the speed parameter + fn clock_config(speed: Speed) -> (PoweredClock, Lpi2cClockSel, Div4) { + match speed { + Speed::Standard | Speed::Fast | Speed::FastPlus => ( + PoweredClock::NormalEnabledDeepSleepDisabled, + Lpi2cClockSel::FroLfDiv, + const { Div4::no_div() }, + ), + Speed::UltraFast => ( + PoweredClock::NormalEnabledDeepSleepDisabled, + Lpi2cClockSel::FroHfDiv, + const { Div4::no_div() }, + ), + } + } + + /// Resets both TX and RX FIFOs dropping their contents. + fn reset_fifos() { + critical_section::with(|_| { + T::regs().mcr().modify(|_, w| w.rtf().reset().rrf().reset()); + }); + } + + /// Checks whether the TX FIFO is full + fn is_tx_fifo_full() -> bool { + let txfifo_size = 1 << T::regs().param().read().mtxfifo().bits(); + T::regs().mfsr().read().txcount().bits() == txfifo_size + } + + /// Checks whether the TX FIFO is empty + fn is_tx_fifo_empty() -> bool { + T::regs().mfsr().read().txcount() == 0 + } + + /// Checks whether the RX FIFO is empty. + fn is_rx_fifo_empty() -> bool { + T::regs().mfsr().read().rxcount() == 0 + } + + /// Reads and parses the controller status producing an + /// appropriate `Result<(), Error>` variant. + fn status() -> Result<()> { + let msr = T::regs().msr().read(); + T::regs().msr().write(|w| { + w.epf() + .clear_bit_by_one() + .sdf() + .clear_bit_by_one() + .ndf() + .clear_bit_by_one() + .alf() + .clear_bit_by_one() + .fef() + .clear_bit_by_one() + .fef() + .clear_bit_by_one() + .pltf() + .clear_bit_by_one() + .dmf() + .clear_bit_by_one() + .stf() + .clear_bit_by_one() + }); + + if msr.ndf().bit_is_set() { + Err(Error::AddressNack) + } else if msr.alf().bit_is_set() { + Err(Error::ArbitrationLoss) + } else if msr.fef().bit_is_set() { + Err(Error::FifoError) + } else { + Ok(()) + } + } + + /// Inserts the given command into the outgoing FIFO. + /// + /// Caller must ensure there is space in the FIFO for the new + /// command. + fn send_cmd(cmd: Cmd, data: u8) { + #[cfg(feature = "defmt")] + defmt::trace!( + "Sending cmd '{}' ({}) with data '{:08x}' MSR: {:08x}", + cmd, + cmd as u8, + data, + T::regs().msr().read().bits() + ); + + T::regs() + .mtdr() + .write(|w| unsafe { w.data().bits(data) }.cmd().variant(cmd)); + } + + /// Prepares an appropriate Start condition on bus by issuing a + /// `Start` command together with the device address and R/w bit. + /// + /// Blocks waiting for space in the FIFO to become available, then + /// sends the command and blocks waiting for the FIFO to become + /// empty ensuring the command was sent. + fn start(&mut self, address: u8, read: bool) -> Result<()> { + if address >= 0x80 { + return Err(Error::AddressOutOfRange(address)); + } + + // Wait until we have space in the TxFIFO + while Self::is_tx_fifo_full() {} + + let addr_rw = address << 1 | if read { 1 } else { 0 }; + Self::send_cmd(if self.is_hs { Cmd::StartHs } else { Cmd::Start }, addr_rw); + + // Wait for TxFIFO to be drained + while !Self::is_tx_fifo_empty() {} + + // Check controller status + Self::status() + } + + /// Prepares a Stop condition on the bus. + /// + /// Analogous to `start`, this blocks waiting for space in the + /// FIFO to become available, then sends the command and blocks + /// waiting for the FIFO to become empty ensuring the command was + /// sent. + fn stop() -> Result<()> { + // Wait until we have space in the TxFIFO + while Self::is_tx_fifo_full() {} + + Self::send_cmd(Cmd::Stop, 0); + + // Wait for TxFIFO to be drained + while !Self::is_tx_fifo_empty() {} + + Self::status() + } + + fn blocking_read_internal(&mut self, address: u8, read: &mut [u8], send_stop: SendStop) -> Result<()> { + if read.is_empty() { + return Err(Error::InvalidReadBufferLength); + } + + for chunk in read.chunks_mut(256) { + self.start(address, true)?; + + // Wait until we have space in the TxFIFO + while Self::is_tx_fifo_full() {} + + Self::send_cmd(Cmd::Receive, (chunk.len() - 1) as u8); + + for byte in chunk.iter_mut() { + // Wait until there's data in the RxFIFO + while Self::is_rx_fifo_empty() {} + + *byte = T::regs().mrdr().read().data().bits(); + } + } + + if send_stop == SendStop::Yes { + Self::stop()?; + } + + Ok(()) + } + + fn blocking_write_internal(&mut self, address: u8, write: &[u8], send_stop: SendStop) -> Result<()> { + self.start(address, false)?; + + // Usually, embassy HALs error out with an empty write, + // however empty writes are useful for writing I2C scanning + // logic through write probing. That is, we send a start with + // R/w bit cleared, but instead of writing any data, just send + // the stop onto the bus. This has the effect of checking if + // the resulting address got an ACK but causing no + // side-effects to the device on the other end. + // + // Because of this, we are not going to error out in case of + // empty writes. + #[cfg(feature = "defmt")] + if write.is_empty() { + defmt::trace!("Empty write, write probing?"); + } + + for byte in write { + // Wait until we have space in the TxFIFO + while Self::is_tx_fifo_full() {} + + Self::send_cmd(Cmd::Transmit, *byte); + } + + if send_stop == SendStop::Yes { + Self::stop()?; + } + + Ok(()) + } + + // Public API: Blocking + + /// Read from address into buffer blocking caller until done. + pub fn blocking_read(&mut self, address: u8, read: &mut [u8]) -> Result<()> { + self.blocking_read_internal(address, read, SendStop::Yes) + } + + /// Write to address from buffer blocking caller until done. + pub fn blocking_write(&mut self, address: u8, write: &[u8]) -> Result<()> { + self.blocking_write_internal(address, write, SendStop::Yes) + } + + /// Write to address from bytes and read from address into buffer blocking caller until done. + pub fn blocking_write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<()> { + self.blocking_write_internal(address, write, SendStop::No)?; + self.blocking_read_internal(address, read, SendStop::Yes) + } +} + +impl<'d, T: Instance> I2c<'d, T, Async> { + /// Create a new async instance of the I2C Controller bus driver. + pub fn new_async( + peri: Peri<'d, T>, + scl: Peri<'d, impl SclPin>, + sda: Peri<'d, impl SdaPin>, + _irq: impl crate::interrupt::typelevel::Binding> + 'd, + config: Config, + ) -> Result { + T::Interrupt::unpend(); + + // Safety: `_irq` ensures an Interrupt Handler exists. + unsafe { T::Interrupt::enable() }; + + Self::new_inner(peri, scl, sda, config) + } + + fn remediation() { + #[cfg(feature = "defmt")] + defmt::trace!("Future dropped, issuing stop",); + + // if the FIFO is not empty, drop its contents. + if !Self::is_tx_fifo_empty() { + Self::reset_fifos(); + } + + // send a stop command + let _ = Self::stop(); + } + + fn enable_rx_ints(&mut self) { + T::regs().mier().write(|w| { + w.rdie() + .enabled() + .ndie() + .enabled() + .alie() + .enabled() + .feie() + .enabled() + .pltie() + .enabled() + }); + } + + fn enable_tx_ints(&mut self) { + T::regs().mier().write(|w| { + w.tdie() + .enabled() + .ndie() + .enabled() + .alie() + .enabled() + .feie() + .enabled() + .pltie() + .enabled() + }); + } + + async fn async_start(&mut self, address: u8, read: bool) -> Result<()> { + if address >= 0x80 { + return Err(Error::AddressOutOfRange(address)); + } + + // send the start command + let addr_rw = address << 1 | if read { 1 } else { 0 }; + Self::send_cmd(if self.is_hs { Cmd::StartHs } else { Cmd::Start }, addr_rw); + + T::wait_cell() + .wait_for(|| { + // enable interrupts + self.enable_tx_ints(); + // if the command FIFO is empty, we're done sending start + Self::is_tx_fifo_empty() + }) + .await + .map_err(|_| Error::Other)?; + + Self::status() + } + + async fn async_stop(&mut self) -> Result<()> { + // send the stop command + Self::send_cmd(Cmd::Stop, 0); + + T::wait_cell() + .wait_for(|| { + // enable interrupts + self.enable_tx_ints(); + // if the command FIFO is empty, we're done sending stop + Self::is_tx_fifo_empty() + }) + .await + .map_err(|_| Error::Other)?; + + Self::status() + } + + async fn async_read_internal(&mut self, address: u8, read: &mut [u8], send_stop: SendStop) -> Result<()> { + if read.is_empty() { + return Err(Error::InvalidReadBufferLength); + } + + // perform corrective action if the future is dropped + let on_drop = OnDrop::new(|| Self::remediation()); + + for chunk in read.chunks_mut(256) { + self.async_start(address, true).await?; + + // send receive command + Self::send_cmd(Cmd::Receive, (chunk.len() - 1) as u8); + + T::wait_cell() + .wait_for(|| { + // enable interrupts + self.enable_tx_ints(); + // if the command FIFO is empty, we're done sending start + Self::is_tx_fifo_empty() + }) + .await + .map_err(|_| Error::Other)?; + + for byte in chunk.iter_mut() { + T::wait_cell() + .wait_for(|| { + // enable interrupts + self.enable_rx_ints(); + // if the rx FIFO is not empty, we need to read a byte + !Self::is_rx_fifo_empty() + }) + .await + .map_err(|_| Error::ReadFail)?; + + *byte = T::regs().mrdr().read().data().bits(); + } + } + + if send_stop == SendStop::Yes { + self.async_stop().await?; + } + + // defuse it if the future is not dropped + on_drop.defuse(); + + Ok(()) + } + + async fn async_write_internal(&mut self, address: u8, write: &[u8], send_stop: SendStop) -> Result<()> { + self.async_start(address, false).await?; + + // perform corrective action if the future is dropped + let on_drop = OnDrop::new(|| Self::remediation()); + + // Usually, embassy HALs error out with an empty write, + // however empty writes are useful for writing I2C scanning + // logic through write probing. That is, we send a start with + // R/w bit cleared, but instead of writing any data, just send + // the stop onto the bus. This has the effect of checking if + // the resulting address got an ACK but causing no + // side-effects to the device on the other end. + // + // Because of this, we are not going to error out in case of + // empty writes. + #[cfg(feature = "defmt")] + if write.is_empty() { + defmt::trace!("Empty write, write probing?"); + } + + for byte in write { + T::wait_cell() + .wait_for(|| { + // enable interrupts + self.enable_tx_ints(); + // initiate transmit + Self::send_cmd(Cmd::Transmit, *byte); + // if the tx FIFO is empty, we're done transmiting + Self::is_tx_fifo_empty() + }) + .await + .map_err(|_| Error::WriteFail)?; + } + + if send_stop == SendStop::Yes { + self.async_stop().await?; + } + + // defuse it if the future is not dropped + on_drop.defuse(); + + Ok(()) + } + + // Public API: Async + + /// Read from address into buffer asynchronously. + pub fn async_read<'a>( + &mut self, + address: u8, + read: &'a mut [u8], + ) -> impl Future> + use<'_, 'a, 'd, T> { + self.async_read_internal(address, read, SendStop::Yes) + } + + /// Write to address from buffer asynchronously. + pub fn async_write<'a>( + &mut self, + address: u8, + write: &'a [u8], + ) -> impl Future> + use<'_, 'a, 'd, T> { + self.async_write_internal(address, write, SendStop::Yes) + } + + /// Write to address from bytes and read from address into buffer asynchronously. + pub async fn async_write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<()> { + self.async_write_internal(address, write, SendStop::No).await?; + self.async_read_internal(address, read, SendStop::Yes).await + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Read for I2c<'d, T, M> { + type Error = Error; + + fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<()> { + self.blocking_read(address, buffer) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Write for I2c<'d, T, M> { + type Error = Error; + + fn write(&mut self, address: u8, bytes: &[u8]) -> Result<()> { + self.blocking_write(address, bytes) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, T, M> { + type Error = Error; + + fn write_read(&mut self, address: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<()> { + self.blocking_write_read(address, bytes, buffer) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Transactional for I2c<'d, T, M> { + type Error = Error; + + fn exec(&mut self, address: u8, operations: &mut [embedded_hal_02::blocking::i2c::Operation<'_>]) -> Result<()> { + if let Some((last, rest)) = operations.split_last_mut() { + for op in rest { + match op { + embedded_hal_02::blocking::i2c::Operation::Read(buf) => { + self.blocking_read_internal(address, buf, SendStop::No)? + } + embedded_hal_02::blocking::i2c::Operation::Write(buf) => { + self.blocking_write_internal(address, buf, SendStop::No)? + } + } + } + + match last { + embedded_hal_02::blocking::i2c::Operation::Read(buf) => { + self.blocking_read_internal(address, buf, SendStop::Yes) + } + embedded_hal_02::blocking::i2c::Operation::Write(buf) => { + self.blocking_write_internal(address, buf, SendStop::Yes) + } + } + } else { + Ok(()) + } + } +} + +impl embedded_hal_1::i2c::Error for Error { + fn kind(&self) -> embedded_hal_1::i2c::ErrorKind { + match *self { + Self::ArbitrationLoss => embedded_hal_1::i2c::ErrorKind::ArbitrationLoss, + Self::AddressNack => { + embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Address) + } + _ => embedded_hal_1::i2c::ErrorKind::Other, + } + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_1::i2c::ErrorType for I2c<'d, T, M> { + type Error = Error; +} + +impl<'d, T: Instance, M: Mode> embedded_hal_1::i2c::I2c for I2c<'d, T, M> { + fn transaction(&mut self, address: u8, operations: &mut [embedded_hal_1::i2c::Operation<'_>]) -> Result<()> { + if let Some((last, rest)) = operations.split_last_mut() { + for op in rest { + match op { + embedded_hal_1::i2c::Operation::Read(buf) => { + self.blocking_read_internal(address, buf, SendStop::No)? + } + embedded_hal_1::i2c::Operation::Write(buf) => { + self.blocking_write_internal(address, buf, SendStop::No)? + } + } + } + + match last { + embedded_hal_1::i2c::Operation::Read(buf) => self.blocking_read_internal(address, buf, SendStop::Yes), + embedded_hal_1::i2c::Operation::Write(buf) => self.blocking_write_internal(address, buf, SendStop::Yes), + } + } else { + Ok(()) + } + } +} + +impl<'d, T: Instance> embedded_hal_async::i2c::I2c for I2c<'d, T, Async> { + async fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal_async::i2c::Operation<'_>], + ) -> Result<()> { + if let Some((last, rest)) = operations.split_last_mut() { + for op in rest { + match op { + embedded_hal_async::i2c::Operation::Read(buf) => { + self.async_read_internal(address, buf, SendStop::No).await? + } + embedded_hal_async::i2c::Operation::Write(buf) => { + self.async_write_internal(address, buf, SendStop::No).await? + } + } + } + + match last { + embedded_hal_async::i2c::Operation::Read(buf) => { + self.async_read_internal(address, buf, SendStop::Yes).await + } + embedded_hal_async::i2c::Operation::Write(buf) => { + self.async_write_internal(address, buf, SendStop::Yes).await + } + } + } else { + Ok(()) + } + } +} + +impl<'d, T: Instance, M: Mode> embassy_embedded_hal::SetConfig for I2c<'d, T, M> { + type Config = Config; + type ConfigError = Error; + + fn set_config(&mut self, config: &Self::Config) -> Result<()> { + Self::set_config(config) + } +} diff --git a/embassy-mcxa/src/i2c/mod.rs b/embassy-mcxa/src/i2c/mod.rs new file mode 100644 index 000000000..9a014224a --- /dev/null +++ b/embassy-mcxa/src/i2c/mod.rs @@ -0,0 +1,194 @@ +//! I2C Support + +use core::marker::PhantomData; + +use embassy_hal_internal::PeripheralType; +use maitake_sync::WaitCell; +use paste::paste; + +use crate::clocks::periph_helpers::Lpi2cConfig; +use crate::clocks::{ClockError, Gate}; +use crate::gpio::{GpioPin, SealedPin}; +use crate::{interrupt, pac}; + +/// Shorthand for `Result`. +pub type Result = core::result::Result; + +pub mod controller; + +/// Error information type +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Clock configuration error. + ClockSetup(ClockError), + /// FIFO Error + FifoError, + /// Reading for I2C failed. + ReadFail, + /// Writing to I2C failed. + WriteFail, + /// I2C address NAK condition. + AddressNack, + /// Bus level arbitration loss. + ArbitrationLoss, + /// Address out of range. + AddressOutOfRange(u8), + /// Invalid write buffer length. + InvalidWriteBufferLength, + /// Invalid read buffer length. + InvalidReadBufferLength, + /// Other internal errors or unexpected state. + Other, +} + +/// I2C interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + if T::regs().mier().read().bits() != 0 { + T::regs().mier().write(|w| { + w.tdie() + .disabled() + .rdie() + .disabled() + .epie() + .disabled() + .sdie() + .disabled() + .ndie() + .disabled() + .alie() + .disabled() + .feie() + .disabled() + .pltie() + .disabled() + .dmie() + .disabled() + .stie() + .disabled() + }); + + T::wait_cell().wake(); + } + } +} + +mod sealed { + /// Seal a trait + pub trait Sealed {} +} + +impl sealed::Sealed for T {} + +trait SealedInstance { + fn regs() -> &'static pac::lpi2c0::RegisterBlock; + fn wait_cell() -> &'static WaitCell; +} + +/// I2C Instance +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType + 'static + Send + Gate { + /// Interrupt for this I2C instance. + type Interrupt: interrupt::typelevel::Interrupt; + /// Clock instance + const CLOCK_INSTANCE: crate::clocks::periph_helpers::Lpi2cInstance; +} + +macro_rules! impl_instance { + ($($n:expr),*) => { + $( + paste!{ + impl SealedInstance for crate::peripherals::[] { + fn regs() -> &'static pac::lpi2c0::RegisterBlock { + unsafe { &*pac::[]::ptr() } + } + + fn wait_cell() -> &'static WaitCell { + static WAIT_CELL: WaitCell = WaitCell::new(); + &WAIT_CELL + } + } + + impl Instance for crate::peripherals::[] { + type Interrupt = crate::interrupt::typelevel::[]; + const CLOCK_INSTANCE: crate::clocks::periph_helpers::Lpi2cInstance + = crate::clocks::periph_helpers::Lpi2cInstance::[]; + } + } + )* + }; +} + +impl_instance!(0, 1, 2, 3); + +/// SCL pin trait. +pub trait SclPin: GpioPin + sealed::Sealed + PeripheralType { + fn mux(&self); +} + +/// SDA pin trait. +pub trait SdaPin: GpioPin + sealed::Sealed + PeripheralType { + fn mux(&self); +} + +/// Driver mode. +#[allow(private_bounds)] +pub trait Mode: sealed::Sealed {} + +/// Blocking mode. +pub struct Blocking; +impl sealed::Sealed for Blocking {} +impl Mode for Blocking {} + +/// Async mode. +pub struct Async; +impl sealed::Sealed for Async {} +impl Mode for Async {} + +macro_rules! impl_pin { + ($pin:ident, $peri:ident, $fn:ident, $trait:ident) => { + impl $trait for crate::peripherals::$pin { + fn mux(&self) { + self.set_pull(crate::gpio::Pull::Disabled); + self.set_slew_rate(crate::gpio::SlewRate::Fast.into()); + self.set_drive_strength(crate::gpio::DriveStrength::Double.into()); + self.set_function(crate::pac::port0::pcr0::Mux::$fn); + self.set_enable_input_buffer(); + } + } + }; +} + +impl_pin!(P0_16, LPI2C0, Mux2, SdaPin); +impl_pin!(P0_17, LPI2C0, Mux2, SclPin); +impl_pin!(P0_18, LPI2C0, Mux2, SclPin); +impl_pin!(P0_19, LPI2C0, Mux2, SdaPin); +impl_pin!(P1_0, LPI2C1, Mux3, SdaPin); +impl_pin!(P1_1, LPI2C1, Mux3, SclPin); +impl_pin!(P1_2, LPI2C1, Mux3, SdaPin); +impl_pin!(P1_3, LPI2C1, Mux3, SclPin); +impl_pin!(P1_8, LPI2C2, Mux3, SdaPin); +impl_pin!(P1_9, LPI2C2, Mux3, SclPin); +impl_pin!(P1_10, LPI2C2, Mux3, SdaPin); +impl_pin!(P1_11, LPI2C2, Mux3, SclPin); +impl_pin!(P1_12, LPI2C1, Mux2, SdaPin); +impl_pin!(P1_13, LPI2C1, Mux2, SclPin); +impl_pin!(P1_14, LPI2C1, Mux2, SclPin); +impl_pin!(P1_15, LPI2C1, Mux2, SdaPin); +impl_pin!(P1_30, LPI2C0, Mux3, SdaPin); +impl_pin!(P1_31, LPI2C0, Mux3, SclPin); +impl_pin!(P3_27, LPI2C3, Mux2, SclPin); +impl_pin!(P3_28, LPI2C3, Mux2, SdaPin); +// impl_pin!(P3_29, LPI2C3, Mux2, HreqPin); What is this HREQ pin? +impl_pin!(P3_30, LPI2C3, Mux2, SclPin); +impl_pin!(P3_31, LPI2C3, Mux2, SdaPin); +impl_pin!(P4_2, LPI2C2, Mux2, SdaPin); +impl_pin!(P4_3, LPI2C0, Mux2, SclPin); +impl_pin!(P4_4, LPI2C2, Mux2, SdaPin); +impl_pin!(P4_5, LPI2C0, Mux2, SclPin); +// impl_pin!(P4_6, LPI2C0, Mux2, HreqPin); What is this HREQ pin? diff --git a/embassy-mcxa/src/interrupt.rs b/embassy-mcxa/src/interrupt.rs new file mode 100644 index 000000000..1d10e3b2d --- /dev/null +++ b/embassy-mcxa/src/interrupt.rs @@ -0,0 +1,563 @@ +//! Minimal interrupt helpers mirroring embassy-imxrt style for OS_EVENT and LPUART2. +//! Type-level interrupt traits and bindings are provided by the +//! `embassy_hal_internal::interrupt_mod!` macro via the generated module below. + +// TODO(AJM): As of 2025-11-13, we need to do a pass to ensure safety docs +// are complete prior to release. +#![allow(clippy::missing_safety_doc)] + +mod generated { + #[rustfmt::skip] + embassy_hal_internal::interrupt_mod!( + ADC1, + GPIO0, + GPIO1, + GPIO2, + GPIO3, + GPIO4, + LPI2C0, + LPI2C1, + LPI2C2, + LPI2C3, + LPUART0, + LPUART1, + LPUART2, + LPUART3, + LPUART4, + LPUART5, + OS_EVENT, + RTC, + ); +} + +use core::sync::atomic::{AtomicU16, AtomicU32, Ordering}; + +pub use generated::interrupt::{typelevel, Priority}; + +use crate::pac::Interrupt; + +/// Trait for configuring and controlling interrupts. +/// +/// This trait provides a consistent interface for interrupt management across +/// different interrupt sources, similar to embassy-imxrt's InterruptExt. +pub trait InterruptExt { + /// Clear any pending interrupt in NVIC. + fn unpend(&self); + + /// Set NVIC priority for this interrupt. + fn set_priority(&self, priority: Priority); + + /// Enable this interrupt in NVIC. + /// + /// # Safety + /// This function is unsafe because it can enable interrupts that may not be + /// properly configured, potentially leading to undefined behavior. + unsafe fn enable(&self); + + /// Disable this interrupt in NVIC. + /// + /// # Safety + /// This function is unsafe because disabling interrupts may leave the system + /// in an inconsistent state if the interrupt was expected to fire. + unsafe fn disable(&self); + + /// Check if the interrupt is pending in NVIC. + fn is_pending(&self) -> bool; +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct DefaultHandlerSnapshot { + pub vector: u16, + pub count: u32, + pub cfsr: u32, + pub hfsr: u32, + pub stacked_pc: u32, + pub stacked_lr: u32, + pub stacked_sp: u32, +} + +static LAST_DEFAULT_VECTOR: AtomicU16 = AtomicU16::new(0); +static LAST_DEFAULT_COUNT: AtomicU32 = AtomicU32::new(0); +static LAST_DEFAULT_CFSR: AtomicU32 = AtomicU32::new(0); +static LAST_DEFAULT_HFSR: AtomicU32 = AtomicU32::new(0); +static LAST_DEFAULT_PC: AtomicU32 = AtomicU32::new(0); +static LAST_DEFAULT_LR: AtomicU32 = AtomicU32::new(0); +static LAST_DEFAULT_SP: AtomicU32 = AtomicU32::new(0); + +#[inline] +pub fn default_handler_snapshot() -> DefaultHandlerSnapshot { + DefaultHandlerSnapshot { + vector: LAST_DEFAULT_VECTOR.load(Ordering::Relaxed), + count: LAST_DEFAULT_COUNT.load(Ordering::Relaxed), + cfsr: LAST_DEFAULT_CFSR.load(Ordering::Relaxed), + hfsr: LAST_DEFAULT_HFSR.load(Ordering::Relaxed), + stacked_pc: LAST_DEFAULT_PC.load(Ordering::Relaxed), + stacked_lr: LAST_DEFAULT_LR.load(Ordering::Relaxed), + stacked_sp: LAST_DEFAULT_SP.load(Ordering::Relaxed), + } +} + +#[inline] +pub fn clear_default_handler_snapshot() { + LAST_DEFAULT_VECTOR.store(0, Ordering::Relaxed); + LAST_DEFAULT_COUNT.store(0, Ordering::Relaxed); + LAST_DEFAULT_CFSR.store(0, Ordering::Relaxed); + LAST_DEFAULT_HFSR.store(0, Ordering::Relaxed); + LAST_DEFAULT_PC.store(0, Ordering::Relaxed); + LAST_DEFAULT_LR.store(0, Ordering::Relaxed); + LAST_DEFAULT_SP.store(0, Ordering::Relaxed); +} + +/// OS_EVENT interrupt helper with methods similar to embassy-imxrt's InterruptExt. +pub struct OsEvent; +pub const OS_EVENT: OsEvent = OsEvent; + +impl InterruptExt for OsEvent { + /// Clear any pending OS_EVENT in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::OS_EVENT); + } + + /// Set NVIC priority for OS_EVENT. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::OS_EVENT, u8::from(priority)); + } + } + + /// Enable OS_EVENT in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::OS_EVENT); + } + + /// Disable OS_EVENT in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::OS_EVENT); + } + + /// Check if OS_EVENT is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::OS_EVENT) + } +} + +impl OsEvent { + /// Configure OS_EVENT interrupt for timer operation. + /// This sets up the NVIC priority, enables the interrupt, and ensures global interrupts are enabled. + /// Also performs a software event to wake any pending WFE. + pub fn configure_for_timer(&self, priority: Priority) { + // Configure NVIC + self.unpend(); + self.set_priority(priority); + unsafe { + self.enable(); + } + + // Ensure global interrupts are enabled in no-reset scenarios (e.g., cargo run) + // Debuggers typically perform a reset which leaves PRIMASK=0; cargo run may not. + unsafe { + cortex_m::interrupt::enable(); + } + + // Wake any executor WFE that might be sleeping when we armed the first deadline + cortex_m::asm::sev(); + } +} + +/// LPUART2 interrupt helper with methods similar to embassy-imxrt's InterruptExt. +pub struct Lpuart2; +pub const LPUART2: Lpuart2 = Lpuart2; + +impl InterruptExt for Lpuart2 { + /// Clear any pending LPUART2 in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::LPUART2); + } + + /// Set NVIC priority for LPUART2. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::LPUART2, u8::from(priority)); + } + } + + /// Enable LPUART2 in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::LPUART2); + } + + /// Disable LPUART2 in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::LPUART2); + } + + /// Check if LPUART2 is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::LPUART2) + } +} + +impl Lpuart2 { + /// Configure LPUART2 interrupt for UART operation. + /// This sets up the NVIC priority, enables the interrupt, and ensures global interrupts are enabled. + pub fn configure_for_uart(&self, priority: Priority) { + // Configure NVIC + self.unpend(); + self.set_priority(priority); + unsafe { + self.enable(); + } + + // Ensure global interrupts are enabled in no-reset scenarios (e.g., cargo run) + // Debuggers typically perform a reset which leaves PRIMASK=0; cargo run may not. + unsafe { + cortex_m::interrupt::enable(); + } + } + + /// Install LPUART2 handler into the RAM vector table. + /// Safety: See `install_irq_handler`. + pub unsafe fn install_handler(&self, handler: unsafe extern "C" fn()) { + install_irq_handler(Interrupt::LPUART2, handler); + } +} + +pub struct Rtc; +pub const RTC: Rtc = Rtc; + +impl InterruptExt for Rtc { + /// Clear any pending RTC in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::RTC); + } + + /// Set NVIC priority for RTC. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::RTC, u8::from(priority)); + } + } + + /// Enable RTC in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::RTC); + } + + /// Disable RTC in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::RTC); + } + + /// Check if RTC is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::RTC) + } +} + +pub struct Adc; +pub const ADC1: Adc = Adc; + +impl InterruptExt for Adc { + /// Clear any pending ADC1 in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::ADC1); + } + + /// Set NVIC priority for ADC1. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::ADC1, u8::from(priority)); + } + } + + /// Enable ADC1 in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::ADC1); + } + + /// Disable ADC1 in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::ADC1); + } + + /// Check if ADC1 is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::ADC1) + } +} + +pub struct Gpio0; +pub const GPIO0: Gpio0 = Gpio0; + +impl InterruptExt for Gpio0 { + /// Clear any pending GPIO0 in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO0); + } + + /// Set NVIC priority for GPIO0. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::GPIO0, u8::from(priority)); + } + } + + /// Enable GPIO0 in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO0); + } + + /// Disable GPIO0 in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::GPIO0); + } + + /// Check if GPIO0 is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO0) + } +} + +pub struct Gpio1; +pub const GPIO1: Gpio1 = Gpio1; + +impl InterruptExt for Gpio1 { + /// Clear any pending GPIO1 in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO1); + } + + /// Set NVIC priority for GPIO1. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::GPIO1, u8::from(priority)); + } + } + + /// Enable GPIO1 in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO1); + } + + /// Disable GPIO1 in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::GPIO1); + } + + /// Check if GPIO1 is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO1) + } +} + +pub struct Gpio2; +pub const GPIO2: Gpio2 = Gpio2; + +impl InterruptExt for Gpio2 { + /// Clear any pending GPIO2 in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO2); + } + + /// Set NVIC priority for GPIO2. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::GPIO2, u8::from(priority)); + } + } + + /// Enable GPIO2 in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO2); + } + + /// Disable GPIO2 in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::GPIO2); + } + + /// Check if GPIO2 is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO2) + } +} + +pub struct Gpio3; +pub const GPIO3: Gpio3 = Gpio3; + +impl InterruptExt for Gpio3 { + /// Clear any pending GPIO3 in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO3); + } + + /// Set NVIC priority for GPIO3. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::GPIO3, u8::from(priority)); + } + } + + /// Enable GPIO3 in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO3); + } + + /// Disable GPIO3 in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::GPIO3); + } + + /// Check if GPIO3 is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO3) + } +} + +pub struct Gpio4; +pub const GPIO4: Gpio4 = Gpio4; + +impl InterruptExt for Gpio4 { + /// Clear any pending GPIO4 in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO4); + } + + /// Set NVIC priority for GPIO4. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::GPIO4, u8::from(priority)); + } + } + + /// Enable GPIO4 in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO4); + } + + /// Disable GPIO4 in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::GPIO4); + } + + /// Check if GPIO4 is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO4) + } +} + +/// Set VTOR (Vector Table Offset) to a RAM-based vector table. +/// Pass a pointer to the first word in the RAM table (stack pointer slot 0). +/// Safety: Caller must ensure the RAM table is valid and aligned as required by the core. +pub unsafe fn vtor_set_ram_vector_base(base: *const u32) { + core::ptr::write_volatile(0xE000_ED08 as *mut u32, base as u32); +} + +/// Install an interrupt handler into the current VTOR-based vector table. +/// This writes the function pointer at index 16 + irq number. +/// Safety: Caller must ensure VTOR points at a writable RAM table and that `handler` +/// has the correct ABI and lifetime. +pub unsafe fn install_irq_handler(irq: Interrupt, handler: unsafe extern "C" fn()) { + let vtor_base = core::ptr::read_volatile(0xE000_ED08 as *const u32) as *mut u32; + let idx = 16 + (irq as isize as usize); + core::ptr::write_volatile(vtor_base.add(idx), handler as usize as u32); +} + +impl OsEvent { + /// Convenience to install the OS_EVENT handler into the RAM vector table. + /// Safety: See `install_irq_handler`. + pub unsafe fn install_handler(&self, handler: extern "C" fn()) { + install_irq_handler(Interrupt::OS_EVENT, handler); + } +} + +/// Install OS_EVENT handler by raw address. Useful to avoid fn pointer type mismatches. +/// Safety: Caller must ensure the address is a valid `extern "C" fn()` handler. +pub unsafe fn os_event_install_handler_raw(handler_addr: usize) { + let vtor_base = core::ptr::read_volatile(0xE000_ED08 as *const u32) as *mut u32; + let idx = 16 + (Interrupt::OS_EVENT as isize as usize); + core::ptr::write_volatile(vtor_base.add(idx), handler_addr as u32); +} + +/// Provide a conservative default IRQ handler that avoids wedging the system. +/// It clears all NVIC pending bits and returns, so spurious or reserved IRQs +/// don’t trap the core in an infinite WFI loop during bring-up. +#[no_mangle] +pub unsafe extern "C" fn DefaultHandler() { + let active = core::ptr::read_volatile(0xE000_ED04 as *const u32) & 0x1FF; + let cfsr = core::ptr::read_volatile(0xE000_ED28 as *const u32); + let hfsr = core::ptr::read_volatile(0xE000_ED2C as *const u32); + + let sp = cortex_m::register::msp::read(); + let stacked = sp as *const u32; + // Stacked registers follow ARMv8-M procedure call standard order + let stacked_pc = unsafe { stacked.add(6).read() }; + let stacked_lr = unsafe { stacked.add(5).read() }; + + LAST_DEFAULT_VECTOR.store(active as u16, Ordering::Relaxed); + LAST_DEFAULT_CFSR.store(cfsr, Ordering::Relaxed); + LAST_DEFAULT_HFSR.store(hfsr, Ordering::Relaxed); + LAST_DEFAULT_COUNT.fetch_add(1, Ordering::Relaxed); + LAST_DEFAULT_PC.store(stacked_pc, Ordering::Relaxed); + LAST_DEFAULT_LR.store(stacked_lr, Ordering::Relaxed); + LAST_DEFAULT_SP.store(sp, Ordering::Relaxed); + + // Do nothing here: on some MCUs/TrustZone setups, writing NVIC from a spurious + // handler can fault if targeting the Secure bank. Just return. + cortex_m::asm::dsb(); + cortex_m::asm::isb(); +} diff --git a/embassy-mcxa/src/lib.rs b/embassy-mcxa/src/lib.rs new file mode 100644 index 000000000..c6d8adc8f --- /dev/null +++ b/embassy-mcxa/src/lib.rs @@ -0,0 +1,471 @@ +#![no_std] +#![allow(async_fn_in_trait)] +#![doc = include_str!("../README.md")] + +// //! ## Feature flags +// #![doc = document_features::document_features!(feature_label = r#"{feature}"#)] + +pub mod clocks; // still provide clock helpers +pub mod gpio; +pub mod pins; // pin mux helpers + +pub mod adc; +pub mod clkout; +pub mod config; +pub mod i2c; +pub mod interrupt; +pub mod lpuart; +pub mod ostimer; +pub mod rtc; + +pub use crate::pac::NVIC_PRIO_BITS; + +#[rustfmt::skip] +embassy_hal_internal::peripherals!( + ADC0, + ADC1, + + AOI0, + AOI1, + + CAN0, + CAN1, + + CDOG0, + CDOG1, + + // CLKOUT is not specifically a peripheral (it's part of SYSCON), + // but we still want it to be a singleton. + CLKOUT, + + CMC, + CMP0, + CMP1, + CRC0, + + CTIMER0, + CTIMER1, + CTIMER2, + CTIMER3, + CTIMER4, + + DBGMAILBOX, + DMA0, + EDMA0_TCD0, + EIM0, + EQDC0, + EQDC1, + ERM0, + FLEXIO0, + FLEXPWM0, + FLEXPWM1, + FMC0, + FMU0, + FREQME0, + GLIKEY0, + + GPIO0, + GPIO1, + GPIO2, + GPIO3, + GPIO4, + + I3C0, + INPUTMUX0, + + LPI2C0, + LPI2C1, + LPI2C2, + LPI2C3, + + LPSPI0, + LPSPI1, + + LPTMR0, + + LPUART0, + LPUART1, + LPUART2, + LPUART3, + LPUART4, + LPUART5, + + MAU0, + MBC0, + MRCC0, + OPAMP0, + + #[cfg(not(feature = "time"))] + OSTIMER0, + + P0_0, + P0_1, + P0_2, + P0_3, + P0_4, + P0_5, + P0_6, + P0_7, + P0_8, + P0_9, + P0_10, + P0_11, + P0_12, + P0_13, + P0_14, + P0_15, + P0_16, + P0_17, + P0_18, + P0_19, + P0_20, + P0_21, + P0_22, + P0_23, + P0_24, + P0_25, + P0_26, + P0_27, + P0_28, + P0_29, + P0_30, + P0_31, + + P1_0, + P1_1, + P1_2, + P1_3, + P1_4, + P1_5, + P1_6, + P1_7, + P1_8, + P1_9, + P1_10, + P1_11, + P1_12, + P1_13, + P1_14, + P1_15, + P1_16, + P1_17, + P1_18, + P1_19, + P1_20, + P1_21, + P1_22, + P1_23, + P1_24, + P1_25, + P1_26, + P1_27, + P1_28, + P1_29, + P1_30, + P1_31, + + P2_0, + P2_1, + P2_2, + P2_3, + P2_4, + P2_5, + P2_6, + P2_7, + P2_8, + P2_9, + P2_10, + P2_11, + P2_12, + P2_13, + P2_14, + P2_15, + P2_16, + P2_17, + P2_18, + P2_19, + P2_20, + P2_21, + P2_22, + P2_23, + P2_24, + P2_25, + P2_26, + P2_27, + P2_28, + P2_29, + P2_30, + P2_31, + + P3_0, + P3_1, + P3_2, + P3_3, + P3_4, + P3_5, + P3_6, + P3_7, + P3_8, + P3_9, + P3_10, + P3_11, + P3_12, + P3_13, + P3_14, + P3_15, + P3_16, + P3_17, + P3_18, + P3_19, + P3_20, + P3_21, + P3_22, + P3_23, + P3_24, + P3_25, + P3_26, + P3_27, + P3_28, + P3_29, + P3_30, + P3_31, + + P4_0, + P4_1, + P4_2, + P4_3, + P4_4, + P4_5, + P4_6, + P4_7, + P4_8, + P4_9, + P4_10, + P4_11, + P4_12, + P4_13, + P4_14, + P4_15, + P4_16, + P4_17, + P4_18, + P4_19, + P4_20, + P4_21, + P4_22, + P4_23, + P4_24, + P4_25, + P4_26, + P4_27, + P4_28, + P4_29, + P4_30, + P4_31, + + P5_0, + P5_1, + P5_2, + P5_3, + P5_4, + P5_5, + P5_6, + P5_7, + P5_8, + P5_9, + P5_10, + P5_11, + P5_12, + P5_13, + P5_14, + P5_15, + P5_16, + P5_17, + P5_18, + P5_19, + P5_20, + P5_21, + P5_22, + P5_23, + P5_24, + P5_25, + P5_26, + P5_27, + P5_28, + P5_29, + P5_30, + P5_31, + + PKC0, + + PORT0, + PORT1, + PORT2, + PORT3, + PORT4, + + RTC0, + SAU, + SCG0, + SCN_SCB, + SGI0, + SMARTDMA0, + SPC0, + SYSCON, + TDET0, + TRNG0, + UDF0, + USB0, + UTICK0, + VBAT0, + WAKETIMER0, + WUU0, + WWDT0, +); + +// Use cortex-m-rt's #[interrupt] attribute directly; PAC does not re-export it. + +// Re-export interrupt traits and types +pub use adc::Adc1 as Adc1Token; +pub use gpio::{AnyPin, Flex, Gpio as GpioToken, Input, Level, Output}; +pub use interrupt::InterruptExt; +#[cfg(feature = "unstable-pac")] +pub use mcxa_pac as pac; +#[cfg(not(feature = "unstable-pac"))] +pub(crate) use mcxa_pac as pac; + +/// Initialize HAL with configuration (mirrors embassy-imxrt style). Minimal: just take peripherals. +/// Also applies configurable NVIC priority for the OSTIMER OS_EVENT interrupt (no enabling). +pub fn init(cfg: crate::config::Config) -> Peripherals { + let peripherals = Peripherals::take(); + // Apply user-configured priority early; enabling is left to examples/apps + #[cfg(feature = "time")] + crate::interrupt::OS_EVENT.set_priority(cfg.time_interrupt_priority); + // Apply user-configured priority early; enabling is left to examples/apps + crate::interrupt::RTC.set_priority(cfg.rtc_interrupt_priority); + // Apply user-configured priority early; enabling is left to examples/apps + crate::interrupt::ADC1.set_priority(cfg.adc_interrupt_priority); + // Apply user-configured priority early; enabling is left to examples/apps + crate::interrupt::GPIO0.set_priority(cfg.gpio_interrupt_priority); + // Apply user-configured priority early; enabling is left to examples/apps + crate::interrupt::GPIO1.set_priority(cfg.gpio_interrupt_priority); + // Apply user-configured priority early; enabling is left to examples/apps + crate::interrupt::GPIO2.set_priority(cfg.gpio_interrupt_priority); + // Apply user-configured priority early; enabling is left to examples/apps + crate::interrupt::GPIO3.set_priority(cfg.gpio_interrupt_priority); + // Apply user-configured priority early; enabling is left to examples/apps + crate::interrupt::GPIO4.set_priority(cfg.gpio_interrupt_priority); + + // Configure clocks + crate::clocks::init(cfg.clock_cfg).unwrap(); + + unsafe { + crate::gpio::init(); + } + + // Initialize embassy-time global driver backed by OSTIMER0 + #[cfg(feature = "time")] + crate::ostimer::time_driver::init(crate::config::Config::default().time_interrupt_priority, 1_000_000); + + // Enable GPIO clocks + unsafe { + _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); + _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); + + _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); + _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); + + _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); + _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); + + _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); + _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); + + _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); + _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); + } + + peripherals +} + +// /// Optional hook called by cortex-m-rt before RAM init. +// /// We proactively mask and clear all NVIC IRQs to avoid wedges from stale state +// /// left by soft resets/debug sessions. +// /// +// /// NOTE: Manual VTOR setup is required for RAM execution. The cortex-m-rt 'set-vtor' +// /// feature is incompatible with our setup because it expects __vector_table to be +// /// defined differently than how our RAM-based linker script arranges it. +// #[no_mangle] +// pub unsafe extern "C" fn __pre_init() { +// // Set the VTOR to point to the interrupt vector table in RAM +// // This is required since code runs from RAM on this MCU +// crate::interrupt::vtor_set_ram_vector_base(0x2000_0000 as *const u32); + +// // Mask and clear pending for all NVIC lines (0..127) to avoid stale state across runs. +// let nvic = &*cortex_m::peripheral::NVIC::PTR; +// for i in 0..4 { +// // 4 words x 32 = 128 IRQs +// nvic.icer[i].write(0xFFFF_FFFF); +// nvic.icpr[i].write(0xFFFF_FFFF); +// } +// // Do NOT touch peripheral registers here: clocks may be off and accesses can fault. +// crate::interrupt::clear_default_handler_snapshot(); +// } + +/// Internal helper to dispatch a type-level interrupt handler. +#[inline(always)] +#[doc(hidden)] +pub unsafe fn __handle_interrupt() +where + T: crate::interrupt::typelevel::Interrupt, + H: crate::interrupt::typelevel::Handler, +{ + H::on_interrupt(); +} + +/// Macro to bind interrupts to handlers, similar to embassy-imxrt. +/// +/// Example: +/// - Bind OS_EVENT to the OSTIMER time-driver handler +/// bind_interrupts!(struct Irqs { OS_EVENT => crate::ostimer::time_driver::OsEventHandler; }); +#[macro_export] +macro_rules! bind_interrupts { + ($(#[$attr:meta])* $vis:vis struct $name:ident { + $( + $(#[cfg($cond_irq:meta)])? + $irq:ident => $( + $(#[cfg($cond_handler:meta)])? + $handler:ty + ),*; + )* + }) => { + #[derive(Copy, Clone)] + $(#[$attr])* + $vis struct $name; + + $( + #[allow(non_snake_case)] + #[no_mangle] + $(#[cfg($cond_irq)])? + unsafe extern "C" fn $irq() { + unsafe { + $( + $(#[cfg($cond_handler)])? + <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); + )* + } + } + + $(#[cfg($cond_irq)])? + $crate::bind_interrupts!(@inner + $( + $(#[cfg($cond_handler)])? + unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} + )* + ); + )* + }; + (@inner $($t:tt)*) => { + $($t)* + } +} diff --git a/embassy-mcxa/src/lpuart/buffered.rs b/embassy-mcxa/src/lpuart/buffered.rs new file mode 100644 index 000000000..8eb443ca7 --- /dev/null +++ b/embassy-mcxa/src/lpuart/buffered.rs @@ -0,0 +1,780 @@ +use core::future::poll_fn; +use core::marker::PhantomData; +use core::sync::atomic::{AtomicBool, Ordering}; +use core::task::Poll; + +use embassy_hal_internal::atomic_ring_buffer::RingBuffer; +use embassy_hal_internal::Peri; +use embassy_sync::waitqueue::AtomicWaker; + +use super::*; +use crate::interrupt; + +// ============================================================================ +// STATIC STATE MANAGEMENT +// ============================================================================ + +/// State for buffered LPUART operations +pub struct State { + tx_waker: AtomicWaker, + tx_buf: RingBuffer, + tx_done: AtomicBool, + rx_waker: AtomicWaker, + rx_buf: RingBuffer, + initialized: AtomicBool, +} + +impl Default for State { + fn default() -> Self { + Self::new() + } +} + +impl State { + /// Create a new state instance + pub const fn new() -> Self { + Self { + tx_waker: AtomicWaker::new(), + tx_buf: RingBuffer::new(), + tx_done: AtomicBool::new(true), + rx_waker: AtomicWaker::new(), + rx_buf: RingBuffer::new(), + initialized: AtomicBool::new(false), + } + } +} +// ============================================================================ +// BUFFERED DRIVER STRUCTURES +// ============================================================================ + +/// Buffered LPUART driver +pub struct BufferedLpuart<'a> { + tx: BufferedLpuartTx<'a>, + rx: BufferedLpuartRx<'a>, +} + +/// Buffered LPUART TX driver +pub struct BufferedLpuartTx<'a> { + info: Info, + state: &'static State, + _tx_pin: Peri<'a, AnyPin>, + _cts_pin: Option>, +} + +/// Buffered LPUART RX driver +pub struct BufferedLpuartRx<'a> { + info: Info, + state: &'static State, + _rx_pin: Peri<'a, AnyPin>, + _rts_pin: Option>, +} + +// ============================================================================ +// BUFFERED LPUART IMPLEMENTATION +// ============================================================================ + +impl<'a> BufferedLpuart<'a> { + /// Common initialization logic + fn init_common( + _inner: &Peri<'a, T>, + tx_buffer: Option<&'a mut [u8]>, + rx_buffer: Option<&'a mut [u8]>, + config: &Config, + enable_tx: bool, + enable_rx: bool, + enable_rts: bool, + enable_cts: bool, + ) -> Result<&'static State> { + let state = T::buffered_state(); + + if state.initialized.load(Ordering::Relaxed) { + return Err(Error::InvalidArgument); + } + + // Initialize buffers + if let Some(tx_buffer) = tx_buffer { + if tx_buffer.is_empty() { + return Err(Error::InvalidArgument); + } + unsafe { state.tx_buf.init(tx_buffer.as_mut_ptr(), tx_buffer.len()) }; + } + + if let Some(rx_buffer) = rx_buffer { + if rx_buffer.is_empty() { + return Err(Error::InvalidArgument); + } + unsafe { state.rx_buf.init(rx_buffer.as_mut_ptr(), rx_buffer.len()) }; + } + + state.initialized.store(true, Ordering::Relaxed); + + // Enable clocks and initialize hardware + let conf = LpuartConfig { + power: config.power, + source: config.source, + div: config.div, + instance: T::CLOCK_INSTANCE, + }; + let clock_freq = unsafe { enable_and_reset::(&conf).map_err(Error::ClockSetup)? }; + + Self::init_hardware( + T::info().regs, + *config, + clock_freq, + enable_tx, + enable_rx, + enable_rts, + enable_cts, + )?; + + Ok(state) + } + + /// Helper for full-duplex initialization + fn new_inner( + inner: Peri<'a, T>, + tx_pin: Peri<'a, AnyPin>, + rx_pin: Peri<'a, AnyPin>, + rts_pin: Option>, + cts_pin: Option>, + tx_buffer: &'a mut [u8], + rx_buffer: &'a mut [u8], + config: Config, + ) -> Result<(BufferedLpuartTx<'a>, BufferedLpuartRx<'a>)> { + let state = Self::init_common::( + &inner, + Some(tx_buffer), + Some(rx_buffer), + &config, + true, + true, + rts_pin.is_some(), + cts_pin.is_some(), + )?; + + let tx = BufferedLpuartTx { + info: T::info(), + state, + _tx_pin: tx_pin, + _cts_pin: cts_pin, + }; + + let rx = BufferedLpuartRx { + info: T::info(), + state, + _rx_pin: rx_pin, + _rts_pin: rts_pin, + }; + + Ok((tx, rx)) + } + + /// Common hardware initialization logic + fn init_hardware( + regs: &'static mcxa_pac::lpuart0::RegisterBlock, + config: Config, + clock_freq: u32, + enable_tx: bool, + enable_rx: bool, + enable_rts: bool, + enable_cts: bool, + ) -> Result<()> { + // Perform standard initialization + perform_software_reset(regs); + disable_transceiver(regs); + configure_baudrate(regs, config.baudrate_bps, clock_freq)?; + configure_frame_format(regs, &config); + configure_control_settings(regs, &config); + configure_fifo(regs, &config); + clear_all_status_flags(regs); + configure_flow_control(regs, enable_rts, enable_cts, &config); + configure_bit_order(regs, config.msb_first); + + // Enable interrupts for buffered operation + cortex_m::interrupt::free(|_| { + regs.ctrl().modify(|_, w| { + w.rie() + .enabled() // RX interrupt + .orie() + .enabled() // Overrun interrupt + .peie() + .enabled() // Parity error interrupt + .feie() + .enabled() // Framing error interrupt + .neie() + .enabled() // Noise error interrupt + }); + }); + + // Enable the transceiver + enable_transceiver(regs, enable_rx, enable_tx); + + Ok(()) + } + + /// Create a new full duplex buffered LPUART + pub fn new( + inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + rx_pin: Peri<'a, impl RxPin>, + _irq: impl interrupt::typelevel::Binding> + 'a, + tx_buffer: &'a mut [u8], + rx_buffer: &'a mut [u8], + config: Config, + ) -> Result { + tx_pin.as_tx(); + rx_pin.as_rx(); + + let (tx, rx) = Self::new_inner::( + inner, + tx_pin.into(), + rx_pin.into(), + None, + None, + tx_buffer, + rx_buffer, + config, + )?; + + Ok(Self { tx, rx }) + } + + /// Create a new buffered LPUART instance with RTS/CTS flow control + pub fn new_with_rtscts( + inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + rx_pin: Peri<'a, impl RxPin>, + rts_pin: Peri<'a, impl RtsPin>, + cts_pin: Peri<'a, impl CtsPin>, + _irq: impl interrupt::typelevel::Binding> + 'a, + tx_buffer: &'a mut [u8], + rx_buffer: &'a mut [u8], + config: Config, + ) -> Result { + tx_pin.as_tx(); + rx_pin.as_rx(); + rts_pin.as_rts(); + cts_pin.as_cts(); + + let (tx, rx) = Self::new_inner::( + inner, + tx_pin.into(), + rx_pin.into(), + Some(rts_pin.into()), + Some(cts_pin.into()), + tx_buffer, + rx_buffer, + config, + )?; + + Ok(Self { tx, rx }) + } + + /// Create a new buffered LPUART with only RTS flow control (RX flow control) + pub fn new_with_rts( + inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + rx_pin: Peri<'a, impl RxPin>, + rts_pin: Peri<'a, impl RtsPin>, + _irq: impl interrupt::typelevel::Binding> + 'a, + tx_buffer: &'a mut [u8], + rx_buffer: &'a mut [u8], + config: Config, + ) -> Result { + tx_pin.as_tx(); + rx_pin.as_rx(); + rts_pin.as_rts(); + + let (tx, rx) = Self::new_inner::( + inner, + tx_pin.into(), + rx_pin.into(), + Some(rts_pin.into()), + None, + tx_buffer, + rx_buffer, + config, + )?; + + Ok(Self { tx, rx }) + } + + /// Create a new buffered LPUART with only CTS flow control (TX flow control) + pub fn new_with_cts( + inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + rx_pin: Peri<'a, impl RxPin>, + cts_pin: Peri<'a, impl CtsPin>, + _irq: impl interrupt::typelevel::Binding> + 'a, + tx_buffer: &'a mut [u8], + rx_buffer: &'a mut [u8], + config: Config, + ) -> Result { + tx_pin.as_tx(); + rx_pin.as_rx(); + cts_pin.as_cts(); + + let (tx, rx) = Self::new_inner::( + inner, + tx_pin.into(), + rx_pin.into(), + None, + Some(cts_pin.into()), + tx_buffer, + rx_buffer, + config, + )?; + + Ok(Self { tx, rx }) + } + + /// Split the buffered LPUART into separate TX and RX parts + pub fn split(self) -> (BufferedLpuartTx<'a>, BufferedLpuartRx<'a>) { + (self.tx, self.rx) + } + + /// Get mutable references to TX and RX parts + pub fn split_ref(&mut self) -> (&mut BufferedLpuartTx<'a>, &mut BufferedLpuartRx<'a>) { + (&mut self.tx, &mut self.rx) + } +} + +// ============================================================================ +// BUFFERED TX IMPLEMENTATION +// ============================================================================ + +impl<'a> BufferedLpuartTx<'a> { + /// Helper for TX-only initialization + fn new_inner( + inner: Peri<'a, T>, + tx_pin: Peri<'a, AnyPin>, + cts_pin: Option>, + tx_buffer: &'a mut [u8], + config: Config, + ) -> Result> { + let state = BufferedLpuart::init_common::( + &inner, + Some(tx_buffer), + None, + &config, + true, + false, + false, + cts_pin.is_some(), + )?; + + Ok(BufferedLpuartTx { + info: T::info(), + state, + _tx_pin: tx_pin, + _cts_pin: cts_pin, + }) + } + + pub fn new( + inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + _irq: impl interrupt::typelevel::Binding> + 'a, + tx_buffer: &'a mut [u8], + config: Config, + ) -> Result { + tx_pin.as_tx(); + + Self::new_inner::(inner, tx_pin.into(), None, tx_buffer, config) + } + + /// Create a new TX-only buffered LPUART with CTS flow control + pub fn new_with_cts( + inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + cts_pin: Peri<'a, impl CtsPin>, + _irq: impl interrupt::typelevel::Binding> + 'a, + tx_buffer: &'a mut [u8], + config: Config, + ) -> Result { + tx_pin.as_tx(); + cts_pin.as_cts(); + + Self::new_inner::(inner, tx_pin.into(), Some(cts_pin.into()), tx_buffer, config) + } +} + +impl<'a> BufferedLpuartTx<'a> { + /// Write data asynchronously + pub async fn write(&mut self, buf: &[u8]) -> Result { + let mut written = 0; + + for &byte in buf { + // Wait for space in the buffer + poll_fn(|cx| { + self.state.tx_waker.register(cx.waker()); + + let mut writer = unsafe { self.state.tx_buf.writer() }; + if writer.push_one(byte) { + // Enable TX interrupt to start transmission + cortex_m::interrupt::free(|_| { + self.info.regs.ctrl().modify(|_, w| w.tie().enabled()); + }); + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + }) + .await?; + + written += 1; + } + + Ok(written) + } + + /// Flush the TX buffer and wait for transmission to complete + pub async fn flush(&mut self) -> Result<()> { + // Wait for TX buffer to empty and transmission to complete + poll_fn(|cx| { + self.state.tx_waker.register(cx.waker()); + + let tx_empty = self.state.tx_buf.is_empty(); + let fifo_empty = self.info.regs.water().read().txcount().bits() == 0; + let tc_complete = self.info.regs.stat().read().tc().is_complete(); + + if tx_empty && fifo_empty && tc_complete { + Poll::Ready(Ok(())) + } else { + // Enable appropriate interrupt + cortex_m::interrupt::free(|_| { + if !tx_empty { + self.info.regs.ctrl().modify(|_, w| w.tie().enabled()); + } else { + self.info.regs.ctrl().modify(|_, w| w.tcie().enabled()); + } + }); + Poll::Pending + } + }) + .await + } + + /// Try to write without blocking + pub fn try_write(&mut self, buf: &[u8]) -> Result { + let mut writer = unsafe { self.state.tx_buf.writer() }; + let mut written = 0; + + for &byte in buf { + if writer.push_one(byte) { + written += 1; + } else { + break; + } + } + + if written > 0 { + // Enable TX interrupt to start transmission + cortex_m::interrupt::free(|_| { + self.info.regs.ctrl().modify(|_, w| w.tie().enabled()); + }); + } + + Ok(written) + } +} + +// ============================================================================ +// BUFFERED RX IMPLEMENTATION +// ============================================================================ + +impl<'a> BufferedLpuartRx<'a> { + /// Helper for RX-only initialization + fn new_inner( + inner: Peri<'a, T>, + rx_pin: Peri<'a, AnyPin>, + rts_pin: Option>, + rx_buffer: &'a mut [u8], + config: Config, + ) -> Result> { + let state = BufferedLpuart::init_common::( + &inner, + None, + Some(rx_buffer), + &config, + false, + true, + rts_pin.is_some(), + false, + )?; + + Ok(BufferedLpuartRx { + info: T::info(), + state, + _rx_pin: rx_pin, + _rts_pin: rts_pin, + }) + } + + /// Create a new RX-only buffered LPUART + pub fn new( + inner: Peri<'a, T>, + rx_pin: Peri<'a, impl RxPin>, + _irq: impl interrupt::typelevel::Binding> + 'a, + rx_buffer: &'a mut [u8], + config: Config, + ) -> Result { + rx_pin.as_rx(); + + Self::new_inner::(inner, rx_pin.into(), None, rx_buffer, config) + } + + /// Create a new RX-only buffered LPUART with RTS flow control + pub fn new_with_rts( + inner: Peri<'a, T>, + rx_pin: Peri<'a, impl RxPin>, + rts_pin: Peri<'a, impl RtsPin>, + _irq: impl interrupt::typelevel::Binding> + 'a, + rx_buffer: &'a mut [u8], + config: Config, + ) -> Result { + rx_pin.as_rx(); + rts_pin.as_rts(); + + Self::new_inner::(inner, rx_pin.into(), Some(rts_pin.into()), rx_buffer, config) + } +} + +impl<'a> BufferedLpuartRx<'a> { + /// Read data asynchronously + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + let mut read = 0; + + // Try to read available data + poll_fn(|cx| { + self.state.rx_waker.register(cx.waker()); + + // Disable RX interrupt while reading from buffer + cortex_m::interrupt::free(|_| { + self.info.regs.ctrl().modify(|_, w| w.rie().disabled()); + }); + + let mut reader = unsafe { self.state.rx_buf.reader() }; + let available = reader.pop(|data| { + let to_copy = core::cmp::min(data.len(), buf.len() - read); + if to_copy > 0 { + buf[read..read + to_copy].copy_from_slice(&data[..to_copy]); + read += to_copy; + } + to_copy + }); + + // Re-enable RX interrupt + cortex_m::interrupt::free(|_| { + self.info.regs.ctrl().modify(|_, w| w.rie().enabled()); + }); + + if read > 0 { + Poll::Ready(Ok(read)) + } else if available == 0 { + Poll::Pending + } else { + Poll::Ready(Ok(0)) + } + }) + .await + } + + /// Try to read without blocking + pub fn try_read(&mut self, buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + // Disable RX interrupt while reading from buffer + cortex_m::interrupt::free(|_| { + self.info.regs.ctrl().modify(|_, w| w.rie().disabled()); + }); + + let mut reader = unsafe { self.state.rx_buf.reader() }; + let read = reader.pop(|data| { + let to_copy = core::cmp::min(data.len(), buf.len()); + if to_copy > 0 { + buf[..to_copy].copy_from_slice(&data[..to_copy]); + } + to_copy + }); + + // Re-enable RX interrupt + cortex_m::interrupt::free(|_| { + self.info.regs.ctrl().modify(|_, w| w.rie().enabled()); + }); + + Ok(read) + } +} + +// ============================================================================ +// INTERRUPT HANDLER +// ============================================================================ + +/// Buffered UART interrupt handler +pub struct BufferedInterruptHandler { + _phantom: PhantomData, +} + +impl crate::interrupt::typelevel::Handler for BufferedInterruptHandler { + unsafe fn on_interrupt() { + let regs = T::info().regs; + let state = T::buffered_state(); + + // Check if this instance is initialized + if !state.initialized.load(Ordering::Relaxed) { + return; + } + + let ctrl = regs.ctrl().read(); + let stat = regs.stat().read(); + let has_fifo = regs.param().read().rxfifo().bits() > 0; + + // Handle overrun error + if stat.or().is_overrun() { + regs.stat().write(|w| w.or().clear_bit_by_one()); + state.rx_waker.wake(); + return; + } + + // Clear other error flags + if stat.pf().is_parity() { + regs.stat().write(|w| w.pf().clear_bit_by_one()); + } + if stat.fe().is_error() { + regs.stat().write(|w| w.fe().clear_bit_by_one()); + } + if stat.nf().is_noise() { + regs.stat().write(|w| w.nf().clear_bit_by_one()); + } + + // Handle RX data + if ctrl.rie().is_enabled() && (has_data(regs) || stat.idle().is_idle()) { + let mut pushed_any = false; + let mut writer = state.rx_buf.writer(); + + if has_fifo { + // Read from FIFO + while regs.water().read().rxcount().bits() > 0 { + let byte = (regs.data().read().bits() & 0xFF) as u8; + if writer.push_one(byte) { + pushed_any = true; + } else { + // Buffer full, stop reading + break; + } + } + } else { + // Read single byte + if regs.stat().read().rdrf().is_rxdata() { + let byte = (regs.data().read().bits() & 0xFF) as u8; + if writer.push_one(byte) { + pushed_any = true; + } + } + } + + if pushed_any { + state.rx_waker.wake(); + } + + // Clear idle flag if set + if stat.idle().is_idle() { + regs.stat().write(|w| w.idle().clear_bit_by_one()); + } + } + + // Handle TX data + if ctrl.tie().is_enabled() { + let mut sent_any = false; + let mut reader = state.tx_buf.reader(); + + // Send data while TX buffer is ready and we have data + while regs.stat().read().tdre().is_no_txdata() { + if let Some(byte) = reader.pop_one() { + regs.data().write(|w| w.bits(u32::from(byte))); + sent_any = true; + } else { + // No more data to send + break; + } + } + + if sent_any { + state.tx_waker.wake(); + } + + // If buffer is empty, switch to TC interrupt or disable + if state.tx_buf.is_empty() { + cortex_m::interrupt::free(|_| { + regs.ctrl().modify(|_, w| w.tie().disabled().tcie().enabled()); + }); + } + } + + // Handle transmission complete + if ctrl.tcie().is_enabled() && regs.stat().read().tc().is_complete() { + state.tx_done.store(true, Ordering::Release); + state.tx_waker.wake(); + + // Disable TC interrupt + cortex_m::interrupt::free(|_| { + regs.ctrl().modify(|_, w| w.tcie().disabled()); + }); + } + } +} + +// ============================================================================ +// EMBEDDED-IO ASYNC TRAIT IMPLEMENTATIONS +// ============================================================================ + +impl embedded_io_async::ErrorType for BufferedLpuartTx<'_> { + type Error = Error; +} + +impl embedded_io_async::ErrorType for BufferedLpuartRx<'_> { + type Error = Error; +} + +impl embedded_io_async::ErrorType for BufferedLpuart<'_> { + type Error = Error; +} + +impl embedded_io_async::Write for BufferedLpuartTx<'_> { + async fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.write(buf).await + } + + async fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.flush().await + } +} + +impl embedded_io_async::Read for BufferedLpuartRx<'_> { + async fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.read(buf).await + } +} + +impl embedded_io_async::Write for BufferedLpuart<'_> { + async fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.tx.write(buf).await + } + + async fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.tx.flush().await + } +} + +impl embedded_io_async::Read for BufferedLpuart<'_> { + async fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.rx.read(buf).await + } +} diff --git a/embassy-mcxa/src/lpuart/mod.rs b/embassy-mcxa/src/lpuart/mod.rs new file mode 100644 index 000000000..2d8308ec8 --- /dev/null +++ b/embassy-mcxa/src/lpuart/mod.rs @@ -0,0 +1,1292 @@ +use core::marker::PhantomData; + +use embassy_hal_internal::{Peri, PeripheralType}; +use paste::paste; + +use crate::clocks::periph_helpers::{Div4, LpuartClockSel, LpuartConfig}; +use crate::clocks::{enable_and_reset, ClockError, Gate, PoweredClock}; +use crate::gpio::SealedPin; +use crate::pac::lpuart0::baud::Sbns as StopBits; +use crate::pac::lpuart0::ctrl::{Idlecfg as IdleConfig, Ilt as IdleType, Pt as Parity, M as DataBits}; +use crate::pac::lpuart0::modir::{Txctsc as TxCtsConfig, Txctssrc as TxCtsSource}; +use crate::pac::lpuart0::stat::Msbf as MsbFirst; +use crate::{interrupt, pac, AnyPin}; + +pub mod buffered; + +// ============================================================================ +// STUB IMPLEMENTATION +// ============================================================================ + +// Stub implementation for LIB (Peripherals), GPIO, DMA and CLOCK until stable API +// Pin and Clock initialization is currently done at the examples level. + +// --- START DMA --- +mod dma { + pub struct Channel<'d> { + pub(super) _lifetime: core::marker::PhantomData<&'d ()>, + } +} + +use dma::Channel; + +// --- END DMA --- + +// ============================================================================ +// MISC +// ============================================================================ + +mod sealed { + /// Simply seal a trait to prevent external implementations + pub trait Sealed {} +} + +// ============================================================================ +// INSTANCE TRAIT +// ============================================================================ + +pub type Regs = &'static crate::pac::lpuart0::RegisterBlock; + +pub trait SealedInstance { + fn info() -> Info; + fn index() -> usize; + fn buffered_state() -> &'static buffered::State; +} + +pub struct Info { + pub regs: Regs, +} + +/// Trait for LPUART peripheral instances +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType + 'static + Send + Gate { + const CLOCK_INSTANCE: crate::clocks::periph_helpers::LpuartInstance; + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_instance { + ($($n:expr),*) => { + $( + paste!{ + impl SealedInstance for crate::peripherals::[] { + fn info() -> Info { + Info { + regs: unsafe { &*pac::[]::ptr() }, + } + } + + #[inline] + fn index() -> usize { + $n + } + + fn buffered_state() -> &'static buffered::State { + static BUFFERED_STATE: buffered::State = buffered::State::new(); + &BUFFERED_STATE + } + } + + impl Instance for crate::peripherals::[] { + const CLOCK_INSTANCE: crate::clocks::periph_helpers::LpuartInstance + = crate::clocks::periph_helpers::LpuartInstance::[]; + type Interrupt = crate::interrupt::typelevel::[]; + } + } + )* + }; +} + +impl_instance!(0, 1, 2, 3, 4, 5); + +// ============================================================================ +// INSTANCE HELPER FUNCTIONS +// ============================================================================ + +/// Perform software reset on the LPUART peripheral +pub fn perform_software_reset(regs: Regs) { + // Software reset - set and clear RST bit (Global register) + regs.global().write(|w| w.rst().reset()); + regs.global().write(|w| w.rst().no_effect()); +} + +/// Disable both transmitter and receiver +pub fn disable_transceiver(regs: Regs) { + regs.ctrl().modify(|_, w| w.te().disabled().re().disabled()); +} + +/// Calculate and configure baudrate settings +pub fn configure_baudrate(regs: Regs, baudrate_bps: u32, clock_freq: u32) -> Result<()> { + let (osr, sbr) = calculate_baudrate(baudrate_bps, clock_freq)?; + + // Configure BAUD register + regs.baud().modify(|_, w| unsafe { + // Clear and set OSR + w.osr().bits(osr - 1); + // Clear and set SBR + w.sbr().bits(sbr); + // Set BOTHEDGE if OSR is between 4 and 7 + if osr > 3 && osr < 8 { + w.bothedge().enabled() + } else { + w.bothedge().disabled() + } + }); + + Ok(()) +} + +/// Configure frame format (stop bits, data bits) +pub fn configure_frame_format(regs: Regs, config: &Config) { + // Configure stop bits + regs.baud().modify(|_, w| w.sbns().variant(config.stop_bits_count)); + + // Clear M10 for now (10-bit mode) + regs.baud().modify(|_, w| w.m10().disabled()); +} + +/// Configure control settings (parity, data bits, idle config, pin swap) +pub fn configure_control_settings(regs: Regs, config: &Config) { + regs.ctrl().modify(|_, w| { + // Parity configuration + let mut w = if let Some(parity) = config.parity_mode { + w.pe().enabled().pt().variant(parity) + } else { + w.pe().disabled() + }; + + // Data bits configuration + w = match config.data_bits_count { + DataBits::Data8 => { + if config.parity_mode.is_some() { + w.m().data9() // 8 data + 1 parity = 9 bits + } else { + w.m().data8() // 8 data bits only + } + } + DataBits::Data9 => w.m().data9(), + }; + + // Idle configuration + w = w.idlecfg().variant(config.rx_idle_config); + w = w.ilt().variant(config.rx_idle_type); + + // Swap TXD/RXD if configured + if config.swap_txd_rxd { + w.swap().swap() + } else { + w.swap().standard() + } + }); +} + +/// Configure FIFO settings and watermarks +pub fn configure_fifo(regs: Regs, config: &Config) { + // Configure WATER register for FIFO watermarks + regs.water().write(|w| unsafe { + w.rxwater() + .bits(config.rx_fifo_watermark) + .txwater() + .bits(config.tx_fifo_watermark) + }); + + // Enable TX/RX FIFOs + regs.fifo().modify(|_, w| w.txfe().enabled().rxfe().enabled()); + + // Flush FIFOs + regs.fifo() + .modify(|_, w| w.txflush().txfifo_rst().rxflush().rxfifo_rst()); +} + +/// Clear all status flags +pub fn clear_all_status_flags(regs: Regs) { + regs.stat().reset(); +} + +/// Configure hardware flow control if enabled +pub fn configure_flow_control(regs: Regs, enable_tx_cts: bool, enable_rx_rts: bool, config: &Config) { + if enable_rx_rts || enable_tx_cts { + regs.modir().modify(|_, w| { + let mut w = w; + + // Configure TX CTS + w = w.txctsc().variant(config.tx_cts_config); + w = w.txctssrc().variant(config.tx_cts_source); + + if enable_rx_rts { + w = w.rxrtse().enabled(); + } else { + w = w.rxrtse().disabled(); + } + + if enable_tx_cts { + w = w.txctse().enabled(); + } else { + w = w.txctse().disabled(); + } + + w + }); + } +} + +/// Configure bit order (MSB first or LSB first) +pub fn configure_bit_order(regs: Regs, msb_first: MsbFirst) { + regs.stat().modify(|_, w| w.msbf().variant(msb_first)); +} + +/// Enable transmitter and/or receiver based on configuration +pub fn enable_transceiver(regs: Regs, enable_tx: bool, enable_rx: bool) { + regs.ctrl().modify(|_, w| { + let mut w = w; + if enable_tx { + w = w.te().enabled(); + } + if enable_rx { + w = w.re().enabled(); + } + w + }); +} + +pub fn calculate_baudrate(baudrate: u32, src_clock_hz: u32) -> Result<(u8, u16)> { + let mut baud_diff = baudrate; + let mut osr = 0u8; + let mut sbr = 0u16; + + // Try OSR values from 4 to 32 + for osr_temp in 4u8..=32u8 { + // Calculate SBR: (srcClock_Hz * 2 / (baudRate * osr) + 1) / 2 + let sbr_calc = ((src_clock_hz * 2) / (baudrate * osr_temp as u32)).div_ceil(2); + + let sbr_temp = if sbr_calc == 0 { + 1 + } else if sbr_calc > 0x1FFF { + 0x1FFF + } else { + sbr_calc as u16 + }; + + // Calculate actual baud rate + let calculated_baud = src_clock_hz / (osr_temp as u32 * sbr_temp as u32); + + let temp_diff = calculated_baud.abs_diff(baudrate); + + if temp_diff <= baud_diff { + baud_diff = temp_diff; + osr = osr_temp; + sbr = sbr_temp; + } + } + + // Check if baud rate difference is within 3% + if baud_diff > (baudrate / 100) * 3 { + return Err(Error::UnsupportedBaudrate); + } + + Ok((osr, sbr)) +} + +/// Wait for all transmit operations to complete +pub fn wait_for_tx_complete(regs: Regs) { + // Wait for TX FIFO to empty + while regs.water().read().txcount().bits() != 0 { + // Wait for TX FIFO to drain + } + + // Wait for last character to shift out (TC = Transmission Complete) + while regs.stat().read().tc().is_active() { + // Wait for transmission to complete + } +} + +pub fn check_and_clear_rx_errors(regs: Regs) -> Result<()> { + let stat = regs.stat().read(); + let mut status = Ok(()); + + // Check for overrun first - other error flags are prevented when OR is set + if stat.or().is_overrun() { + regs.stat().write(|w| w.or().clear_bit_by_one()); + + return Err(Error::Overrun); + } + + if stat.pf().is_parity() { + regs.stat().write(|w| w.pf().clear_bit_by_one()); + status = Err(Error::Parity); + } + + if stat.fe().is_error() { + regs.stat().write(|w| w.fe().clear_bit_by_one()); + status = Err(Error::Framing); + } + + if stat.nf().is_noise() { + regs.stat().write(|w| w.nf().clear_bit_by_one()); + status = Err(Error::Noise); + } + + status +} + +pub fn has_data(regs: Regs) -> bool { + if regs.param().read().rxfifo().bits() > 0 { + // FIFO is available - check RXCOUNT in WATER register + regs.water().read().rxcount().bits() > 0 + } else { + // No FIFO - check RDRF flag in STAT register + regs.stat().read().rdrf().is_rxdata() + } +} + +// ============================================================================ +// PIN TRAITS FOR LPUART FUNCTIONALITY +// ============================================================================ + +impl sealed::Sealed for T {} + +/// io configuration trait for Lpuart Tx configuration +pub trait TxPin: Into + sealed::Sealed + PeripheralType { + /// convert the pin to appropriate function for Lpuart Tx usage + fn as_tx(&self); +} + +/// io configuration trait for Lpuart Rx configuration +pub trait RxPin: Into + sealed::Sealed + PeripheralType { + /// convert the pin to appropriate function for Lpuart Rx usage + fn as_rx(&self); +} + +/// io configuration trait for Lpuart Cts +pub trait CtsPin: Into + sealed::Sealed + PeripheralType { + /// convert the pin to appropriate function for Lpuart Cts usage + fn as_cts(&self); +} + +/// io configuration trait for Lpuart Rts +pub trait RtsPin: Into + sealed::Sealed + PeripheralType { + /// convert the pin to appropriate function for Lpuart Rts usage + fn as_rts(&self); +} + +macro_rules! impl_tx_pin { + ($inst:ident, $pin:ident, $alt:ident) => { + impl TxPin for crate::peripherals::$pin { + fn as_tx(&self) { + // TODO: Check these are right + self.set_pull(crate::gpio::Pull::Up); + self.set_slew_rate(crate::gpio::SlewRate::Fast.into()); + self.set_drive_strength(crate::gpio::DriveStrength::Double.into()); + self.set_function(crate::pac::port0::pcr0::Mux::$alt); + self.set_enable_input_buffer(); + } + } + }; +} + +macro_rules! impl_rx_pin { + ($inst:ident, $pin:ident, $alt:ident) => { + impl RxPin for crate::peripherals::$pin { + fn as_rx(&self) { + // TODO: Check these are right + self.set_pull(crate::gpio::Pull::Up); + self.set_slew_rate(crate::gpio::SlewRate::Fast.into()); + self.set_drive_strength(crate::gpio::DriveStrength::Double.into()); + self.set_function(crate::pac::port0::pcr0::Mux::$alt); + self.set_enable_input_buffer(); + } + } + }; +} + +// TODO: Macro and impls for CTS/RTS pins +macro_rules! impl_cts_pin { + ($inst:ident, $pin:ident, $alt:ident) => { + impl CtsPin for crate::peripherals::$pin { + fn as_cts(&self) { + todo!() + } + } + }; +} + +macro_rules! impl_rts_pin { + ($inst:ident, $pin:ident, $alt:ident) => { + impl RtsPin for crate::peripherals::$pin { + fn as_rts(&self) { + todo!() + } + } + }; +} + +// LPUART 0 +impl_tx_pin!(LPUART0, P0_3, Mux2); +impl_tx_pin!(LPUART0, P0_21, Mux3); +impl_tx_pin!(LPUART0, P2_1, Mux2); + +impl_rx_pin!(LPUART0, P0_2, Mux2); +impl_rx_pin!(LPUART0, P0_20, Mux3); +impl_rx_pin!(LPUART0, P2_0, Mux2); + +impl_cts_pin!(LPUART0, P0_1, Mux2); +impl_cts_pin!(LPUART0, P0_23, Mux3); +impl_cts_pin!(LPUART0, P2_3, Mux2); + +impl_rts_pin!(LPUART0, P0_0, Mux2); +impl_rts_pin!(LPUART0, P0_22, Mux3); +impl_rts_pin!(LPUART0, P2_2, Mux2); + +// LPUART 1 +impl_tx_pin!(LPUART1, P1_9, Mux2); +impl_tx_pin!(LPUART1, P2_13, Mux3); +impl_tx_pin!(LPUART1, P3_9, Mux3); +impl_tx_pin!(LPUART1, P3_21, Mux3); + +impl_rx_pin!(LPUART1, P1_8, Mux2); +impl_rx_pin!(LPUART1, P2_12, Mux3); +impl_rx_pin!(LPUART1, P3_8, Mux3); +impl_rx_pin!(LPUART1, P3_20, Mux3); + +impl_cts_pin!(LPUART1, P1_11, Mux2); +impl_cts_pin!(LPUART1, P2_17, Mux3); +impl_cts_pin!(LPUART1, P3_11, Mux3); +impl_cts_pin!(LPUART1, P3_23, Mux3); + +impl_rts_pin!(LPUART1, P1_10, Mux2); +impl_rts_pin!(LPUART1, P2_15, Mux3); +impl_rts_pin!(LPUART1, P2_16, Mux3); +impl_rts_pin!(LPUART1, P3_10, Mux3); + +// LPUART 2 +impl_tx_pin!(LPUART2, P1_5, Mux3); +impl_tx_pin!(LPUART2, P1_13, Mux3); +impl_tx_pin!(LPUART2, P2_2, Mux3); +impl_tx_pin!(LPUART2, P2_10, Mux3); +impl_tx_pin!(LPUART2, P3_15, Mux2); + +impl_rx_pin!(LPUART2, P1_4, Mux3); +impl_rx_pin!(LPUART2, P1_12, Mux3); +impl_rx_pin!(LPUART2, P2_3, Mux3); +impl_rx_pin!(LPUART2, P2_11, Mux3); +impl_rx_pin!(LPUART2, P3_14, Mux2); + +impl_cts_pin!(LPUART2, P1_7, Mux3); +impl_cts_pin!(LPUART2, P1_15, Mux3); +impl_cts_pin!(LPUART2, P2_4, Mux3); +impl_cts_pin!(LPUART2, P3_13, Mux2); + +impl_rts_pin!(LPUART2, P1_6, Mux3); +impl_rts_pin!(LPUART2, P1_14, Mux3); +impl_rts_pin!(LPUART2, P2_5, Mux3); +impl_rts_pin!(LPUART2, P3_12, Mux2); + +// LPUART 3 +impl_tx_pin!(LPUART3, P3_1, Mux3); +impl_tx_pin!(LPUART3, P3_12, Mux3); +impl_tx_pin!(LPUART3, P4_5, Mux3); + +impl_rx_pin!(LPUART3, P3_0, Mux3); +impl_rx_pin!(LPUART3, P3_13, Mux3); +impl_rx_pin!(LPUART3, P4_2, Mux3); + +impl_cts_pin!(LPUART3, P3_7, Mux3); +impl_cts_pin!(LPUART3, P3_14, Mux3); +impl_cts_pin!(LPUART3, P4_6, Mux3); + +impl_rts_pin!(LPUART3, P3_6, Mux3); +impl_rts_pin!(LPUART3, P3_15, Mux3); +impl_rts_pin!(LPUART3, P4_7, Mux3); + +// LPUART 4 +impl_tx_pin!(LPUART4, P2_7, Mux3); +impl_tx_pin!(LPUART4, P3_19, Mux2); +impl_tx_pin!(LPUART4, P3_27, Mux3); +impl_tx_pin!(LPUART4, P4_3, Mux3); + +impl_rx_pin!(LPUART4, P2_6, Mux3); +impl_rx_pin!(LPUART4, P3_18, Mux2); +impl_rx_pin!(LPUART4, P3_28, Mux3); +impl_rx_pin!(LPUART4, P4_4, Mux3); + +impl_cts_pin!(LPUART4, P2_0, Mux3); +impl_cts_pin!(LPUART4, P3_17, Mux2); +impl_cts_pin!(LPUART4, P3_31, Mux3); + +impl_rts_pin!(LPUART4, P2_1, Mux3); +impl_rts_pin!(LPUART4, P3_16, Mux2); +impl_rts_pin!(LPUART4, P3_30, Mux3); + +// LPUART 5 +impl_tx_pin!(LPUART5, P1_10, Mux8); +impl_tx_pin!(LPUART5, P1_17, Mux8); + +impl_rx_pin!(LPUART5, P1_11, Mux8); +impl_rx_pin!(LPUART5, P1_16, Mux8); + +impl_cts_pin!(LPUART5, P1_12, Mux8); +impl_cts_pin!(LPUART5, P1_19, Mux8); + +impl_rts_pin!(LPUART5, P1_13, Mux8); +impl_rts_pin!(LPUART5, P1_18, Mux8); + +// ============================================================================ +// ERROR TYPES AND RESULTS +// ============================================================================ + +/// LPUART error types +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Read error + Read, + /// Buffer overflow + Overrun, + /// Noise error + Noise, + /// Framing error + Framing, + /// Parity error + Parity, + /// Failure + Fail, + /// Invalid argument + InvalidArgument, + /// Lpuart baud rate cannot be supported with the given clock + UnsupportedBaudrate, + /// RX FIFO Empty + RxFifoEmpty, + /// TX FIFO Full + TxFifoFull, + /// TX Busy + TxBusy, + /// Clock Error + ClockSetup(ClockError), +} + +/// A specialized Result type for LPUART operations +pub type Result = core::result::Result; + +// ============================================================================ +// CONFIGURATION STRUCTURES +// ============================================================================ + +/// Lpuart config +#[derive(Debug, Clone, Copy)] +pub struct Config { + /// Power state required for this peripheral + pub power: PoweredClock, + /// Clock source + pub source: LpuartClockSel, + /// Clock divisor + pub div: Div4, + /// Baud rate in bits per second + pub baudrate_bps: u32, + /// Parity configuration + pub parity_mode: Option, + /// Number of data bits + pub data_bits_count: DataBits, + /// MSB First or LSB First configuration + pub msb_first: MsbFirst, + /// Number of stop bits + pub stop_bits_count: StopBits, + /// TX FIFO watermark + pub tx_fifo_watermark: u8, + /// RX FIFO watermark + pub rx_fifo_watermark: u8, + /// TX CTS source + pub tx_cts_source: TxCtsSource, + /// TX CTS configure + pub tx_cts_config: TxCtsConfig, + /// RX IDLE type + pub rx_idle_type: IdleType, + /// RX IDLE configuration + pub rx_idle_config: IdleConfig, + /// Swap TXD and RXD pins + pub swap_txd_rxd: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + baudrate_bps: 115_200u32, + parity_mode: None, + data_bits_count: DataBits::Data8, + msb_first: MsbFirst::LsbFirst, + stop_bits_count: StopBits::One, + tx_fifo_watermark: 0, + rx_fifo_watermark: 1, + tx_cts_source: TxCtsSource::Cts, + tx_cts_config: TxCtsConfig::Start, + rx_idle_type: IdleType::FromStart, + rx_idle_config: IdleConfig::Idle1, + swap_txd_rxd: false, + power: PoweredClock::NormalEnabledDeepSleepDisabled, + source: LpuartClockSel::FroLfDiv, + div: Div4::no_div(), + } + } +} + +/// LPUART status flags +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Status { + /// Transmit data register empty + pub tx_empty: bool, + /// Transmission complete + pub tx_complete: bool, + /// Receive data register full + pub rx_full: bool, + /// Idle line detected + pub idle: bool, + /// Receiver overrun + pub overrun: bool, + /// Noise error + pub noise: bool, + /// Framing error + pub framing: bool, + /// Parity error + pub parity: bool, +} + +// ============================================================================ +// MODE TRAITS (BLOCKING/ASYNC) +// ============================================================================ + +/// Driver move trait. +#[allow(private_bounds)] +pub trait Mode: sealed::Sealed {} + +/// Blocking mode. +pub struct Blocking; +impl sealed::Sealed for Blocking {} +impl Mode for Blocking {} + +/// Async mode. +pub struct Async; +impl sealed::Sealed for Async {} +impl Mode for Async {} + +// ============================================================================ +// CORE DRIVER STRUCTURES +// ============================================================================ + +/// Lpuart driver. +pub struct Lpuart<'a, M: Mode> { + info: Info, + tx: LpuartTx<'a, M>, + rx: LpuartRx<'a, M>, +} + +/// Lpuart TX driver. +pub struct LpuartTx<'a, M: Mode> { + info: Info, + _tx_pin: Peri<'a, AnyPin>, + _cts_pin: Option>, + _tx_dma: Option>, + mode: PhantomData<(&'a (), M)>, +} + +/// Lpuart Rx driver. +pub struct LpuartRx<'a, M: Mode> { + info: Info, + _rx_pin: Peri<'a, AnyPin>, + _rts_pin: Option>, + _rx_dma: Option>, + mode: PhantomData<(&'a (), M)>, +} + +// ============================================================================ +// LPUART CORE IMPLEMENTATION +// ============================================================================ + +impl<'a, M: Mode> Lpuart<'a, M> { + fn init( + enable_tx: bool, + enable_rx: bool, + enable_tx_cts: bool, + enable_rx_rts: bool, + config: Config, + ) -> Result<()> { + let regs = T::info().regs; + + // Enable clocks + let conf = LpuartConfig { + power: config.power, + source: config.source, + div: config.div, + instance: T::CLOCK_INSTANCE, + }; + let clock_freq = unsafe { enable_and_reset::(&conf).map_err(Error::ClockSetup)? }; + + // Perform initialization sequence + perform_software_reset(regs); + disable_transceiver(regs); + configure_baudrate(regs, config.baudrate_bps, clock_freq)?; + configure_frame_format(regs, &config); + configure_control_settings(regs, &config); + configure_fifo(regs, &config); + clear_all_status_flags(regs); + configure_flow_control(regs, enable_tx_cts, enable_rx_rts, &config); + configure_bit_order(regs, config.msb_first); + enable_transceiver(regs, enable_rx, enable_tx); + + Ok(()) + } + + /// Deinitialize the LPUART peripheral + pub fn deinit(&self) -> Result<()> { + let regs = self.info.regs; + + // Wait for TX operations to complete + wait_for_tx_complete(regs); + + // Clear all status flags + clear_all_status_flags(regs); + + // Disable the module - clear all CTRL register bits + regs.ctrl().reset(); + + Ok(()) + } + + /// Split the Lpuart into a transmitter and receiver + pub fn split(self) -> (LpuartTx<'a, M>, LpuartRx<'a, M>) { + (self.tx, self.rx) + } + + /// Split the Lpuart into a transmitter and receiver by mutable reference + pub fn split_ref(&mut self) -> (&mut LpuartTx<'a, M>, &mut LpuartRx<'a, M>) { + (&mut self.tx, &mut self.rx) + } +} + +// ============================================================================ +// BLOCKING MODE IMPLEMENTATIONS +// ============================================================================ + +impl<'a> Lpuart<'a, Blocking> { + /// Create a new blocking LPUART instance with RX/TX pins. + pub fn new_blocking( + _inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + rx_pin: Peri<'a, impl RxPin>, + config: Config, + ) -> Result { + // Configure the pins for LPUART usage + tx_pin.as_tx(); + rx_pin.as_rx(); + + // Initialize the peripheral + Self::init::(true, true, false, false, config)?; + + Ok(Self { + info: T::info(), + tx: LpuartTx::new_inner(T::info(), tx_pin.into(), None, None), + rx: LpuartRx::new_inner(T::info(), rx_pin.into(), None, None), + }) + } + + /// Create a new blocking LPUART instance with RX, TX and RTS/CTS flow control pins + pub fn new_blocking_with_rtscts( + _inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + rx_pin: Peri<'a, impl RxPin>, + cts_pin: Peri<'a, impl CtsPin>, + rts_pin: Peri<'a, impl RtsPin>, + config: Config, + ) -> Result { + // Configure the pins for LPUART usage + rx_pin.as_rx(); + tx_pin.as_tx(); + rts_pin.as_rts(); + cts_pin.as_cts(); + + // Initialize the peripheral with flow control + Self::init::(true, true, true, true, config)?; + + Ok(Self { + info: T::info(), + rx: LpuartRx::new_inner(T::info(), rx_pin.into(), Some(rts_pin.into()), None), + tx: LpuartTx::new_inner(T::info(), tx_pin.into(), Some(cts_pin.into()), None), + }) + } +} + +// ---------------------------------------------------------------------------- +// Blocking TX Implementation +// ---------------------------------------------------------------------------- + +impl<'a, M: Mode> LpuartTx<'a, M> { + fn new_inner( + info: Info, + tx_pin: Peri<'a, AnyPin>, + cts_pin: Option>, + tx_dma: Option>, + ) -> Self { + Self { + info, + _tx_pin: tx_pin, + _cts_pin: cts_pin, + _tx_dma: tx_dma, + mode: PhantomData, + } + } +} + +impl<'a> LpuartTx<'a, Blocking> { + /// Create a new blocking LPUART transmitter instance + pub fn new_blocking( + _inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + config: Config, + ) -> Result { + // Configure the pins for LPUART usage + tx_pin.as_tx(); + + // Initialize the peripheral + Lpuart::::init::(true, false, false, false, config)?; + + Ok(Self::new_inner(T::info(), tx_pin.into(), None, None)) + } + + /// Create a new blocking LPUART transmitter instance with CTS flow control + pub fn new_blocking_with_cts( + _inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + cts_pin: Peri<'a, impl CtsPin>, + config: Config, + ) -> Result { + tx_pin.as_tx(); + cts_pin.as_cts(); + + Lpuart::::init::(true, false, true, false, config)?; + + Ok(Self::new_inner(T::info(), tx_pin.into(), Some(cts_pin.into()), None)) + } + + fn write_byte_internal(&mut self, byte: u8) -> Result<()> { + self.info.regs.data().modify(|_, w| unsafe { w.bits(u32::from(byte)) }); + + Ok(()) + } + + fn blocking_write_byte(&mut self, byte: u8) -> Result<()> { + while self.info.regs.stat().read().tdre().is_txdata() {} + self.write_byte_internal(byte) + } + + fn write_byte(&mut self, byte: u8) -> Result<()> { + if self.info.regs.stat().read().tdre().is_txdata() { + Err(Error::TxFifoFull) + } else { + self.write_byte_internal(byte) + } + } + + /// Write data to LPUART TX blocking execution until all data is sent. + pub fn blocking_write(&mut self, buf: &[u8]) -> Result<()> { + for x in buf { + self.blocking_write_byte(*x)?; + } + + Ok(()) + } + + pub fn write_str_blocking(&mut self, buf: &str) { + let _ = self.blocking_write(buf.as_bytes()); + } + + /// Write data to LPUART TX without blocking. + pub fn write(&mut self, buf: &[u8]) -> Result<()> { + for x in buf { + self.write_byte(*x)?; + } + + Ok(()) + } + + /// Flush LPUART TX blocking execution until all data has been transmitted. + pub fn blocking_flush(&mut self) -> Result<()> { + while self.info.regs.water().read().txcount().bits() != 0 { + // Wait for TX FIFO to drain + } + + // Wait for last character to shift out + while self.info.regs.stat().read().tc().is_active() { + // Wait for transmission to complete + } + + Ok(()) + } + + /// Flush LPUART TX. + pub fn flush(&mut self) -> Result<()> { + // Check if TX FIFO is empty + if self.info.regs.water().read().txcount().bits() != 0 { + return Err(Error::TxBusy); + } + + // Check if transmission is complete + if self.info.regs.stat().read().tc().is_active() { + return Err(Error::TxBusy); + } + + Ok(()) + } +} + +// ---------------------------------------------------------------------------- +// Blocking RX Implementation +// ---------------------------------------------------------------------------- + +impl<'a, M: Mode> LpuartRx<'a, M> { + fn new_inner( + info: Info, + rx_pin: Peri<'a, AnyPin>, + rts_pin: Option>, + rx_dma: Option>, + ) -> Self { + Self { + info, + _rx_pin: rx_pin, + _rts_pin: rts_pin, + _rx_dma: rx_dma, + mode: PhantomData, + } + } +} + +impl<'a> LpuartRx<'a, Blocking> { + /// Create a new blocking LPUART Receiver instance + pub fn new_blocking( + _inner: Peri<'a, T>, + rx_pin: Peri<'a, impl RxPin>, + config: Config, + ) -> Result { + rx_pin.as_rx(); + + Lpuart::::init::(false, true, false, false, config)?; + + Ok(Self::new_inner(T::info(), rx_pin.into(), None, None)) + } + + /// Create a new blocking LPUART Receiver instance with RTS flow control + pub fn new_blocking_with_rts( + _inner: Peri<'a, T>, + rx_pin: Peri<'a, impl RxPin>, + rts_pin: Peri<'a, impl RtsPin>, + config: Config, + ) -> Result { + rx_pin.as_rx(); + rts_pin.as_rts(); + + Lpuart::::init::(false, true, false, true, config)?; + + Ok(Self::new_inner(T::info(), rx_pin.into(), Some(rts_pin.into()), None)) + } + + fn read_byte_internal(&mut self) -> Result { + let data = self.info.regs.data().read(); + + Ok((data.bits() & 0xFF) as u8) + } + + fn read_byte(&mut self) -> Result { + check_and_clear_rx_errors(self.info.regs)?; + + if !has_data(self.info.regs) { + return Err(Error::RxFifoEmpty); + } + + self.read_byte_internal() + } + + fn blocking_read_byte(&mut self) -> Result { + loop { + if has_data(self.info.regs) { + return self.read_byte_internal(); + } + + check_and_clear_rx_errors(self.info.regs)?; + } + } + + /// Read data from LPUART RX without blocking. + pub fn read(&mut self, buf: &mut [u8]) -> Result<()> { + for byte in buf.iter_mut() { + *byte = self.read_byte()?; + } + Ok(()) + } + + /// Read data from LPUART RX blocking execution until the buffer is filled. + pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<()> { + for byte in buf.iter_mut() { + *byte = self.blocking_read_byte()?; + } + Ok(()) + } +} + +impl<'a> Lpuart<'a, Blocking> { + /// Read data from LPUART RX blocking execution until the buffer is filled + pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<()> { + self.rx.blocking_read(buf) + } + + /// Read data from LPUART RX without blocking + pub fn read(&mut self, buf: &mut [u8]) -> Result<()> { + self.rx.read(buf) + } + + /// Write data to LPUART TX blocking execution until all data is sent + pub fn blocking_write(&mut self, buf: &[u8]) -> Result<()> { + self.tx.blocking_write(buf) + } + + pub fn write_byte(&mut self, byte: u8) -> Result<()> { + self.tx.write_byte(byte) + } + + pub fn read_byte_blocking(&mut self) -> u8 { + loop { + if let Ok(b) = self.rx.read_byte() { + return b; + } + } + } + + pub fn write_str_blocking(&mut self, buf: &str) { + self.tx.write_str_blocking(buf); + } + + /// Write data to LPUART TX without blocking + pub fn write(&mut self, buf: &[u8]) -> Result<()> { + self.tx.write(buf) + } + + /// Flush LPUART TX blocking execution until all data has been transmitted + pub fn blocking_flush(&mut self) -> Result<()> { + self.tx.blocking_flush() + } + + /// Flush LPUART TX without blocking + pub fn flush(&mut self) -> Result<()> { + self.tx.flush() + } +} + +// ============================================================================ +// ASYNC MODE IMPLEMENTATIONS +// ============================================================================ + +// TODO: Implement async mode for LPUART + +// ============================================================================ +// EMBEDDED-HAL 0.2 TRAIT IMPLEMENTATIONS +// ============================================================================ + +impl embedded_hal_02::serial::Read for LpuartRx<'_, Blocking> { + type Error = Error; + + fn read(&mut self) -> core::result::Result> { + let mut buf = [0; 1]; + match self.read(&mut buf) { + Ok(_) => Ok(buf[0]), + Err(Error::RxFifoEmpty) => Err(nb::Error::WouldBlock), + Err(e) => Err(nb::Error::Other(e)), + } + } +} + +impl embedded_hal_02::serial::Write for LpuartTx<'_, Blocking> { + type Error = Error; + + fn write(&mut self, word: u8) -> core::result::Result<(), nb::Error> { + match self.write(&[word]) { + Ok(_) => Ok(()), + Err(Error::TxFifoFull) => Err(nb::Error::WouldBlock), + Err(e) => Err(nb::Error::Other(e)), + } + } + + fn flush(&mut self) -> core::result::Result<(), nb::Error> { + match self.flush() { + Ok(_) => Ok(()), + Err(Error::TxBusy) => Err(nb::Error::WouldBlock), + Err(e) => Err(nb::Error::Other(e)), + } + } +} + +impl embedded_hal_02::blocking::serial::Write for LpuartTx<'_, Blocking> { + type Error = Error; + + fn bwrite_all(&mut self, buffer: &[u8]) -> core::result::Result<(), Self::Error> { + self.blocking_write(buffer) + } + + fn bflush(&mut self) -> core::result::Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl embedded_hal_02::serial::Read for Lpuart<'_, Blocking> { + type Error = Error; + + fn read(&mut self) -> core::result::Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } +} + +impl embedded_hal_02::serial::Write for Lpuart<'_, Blocking> { + type Error = Error; + + fn write(&mut self, word: u8) -> core::result::Result<(), nb::Error> { + embedded_hal_02::serial::Write::write(&mut self.tx, word) + } + + fn flush(&mut self) -> core::result::Result<(), nb::Error> { + embedded_hal_02::serial::Write::flush(&mut self.tx) + } +} + +impl embedded_hal_02::blocking::serial::Write for Lpuart<'_, Blocking> { + type Error = Error; + + fn bwrite_all(&mut self, buffer: &[u8]) -> core::result::Result<(), Self::Error> { + self.blocking_write(buffer) + } + + fn bflush(&mut self) -> core::result::Result<(), Self::Error> { + self.blocking_flush() + } +} + +// ============================================================================ +// EMBEDDED-HAL-NB TRAIT IMPLEMENTATIONS +// ============================================================================ + +impl embedded_hal_nb::serial::Error for Error { + fn kind(&self) -> embedded_hal_nb::serial::ErrorKind { + match *self { + Self::Framing => embedded_hal_nb::serial::ErrorKind::FrameFormat, + Self::Overrun => embedded_hal_nb::serial::ErrorKind::Overrun, + Self::Parity => embedded_hal_nb::serial::ErrorKind::Parity, + Self::Noise => embedded_hal_nb::serial::ErrorKind::Noise, + _ => embedded_hal_nb::serial::ErrorKind::Other, + } + } +} + +impl embedded_hal_nb::serial::ErrorType for LpuartRx<'_, Blocking> { + type Error = Error; +} + +impl embedded_hal_nb::serial::ErrorType for LpuartTx<'_, Blocking> { + type Error = Error; +} + +impl embedded_hal_nb::serial::ErrorType for Lpuart<'_, Blocking> { + type Error = Error; +} + +impl embedded_hal_nb::serial::Read for LpuartRx<'_, Blocking> { + fn read(&mut self) -> nb::Result { + let mut buf = [0; 1]; + match self.read(&mut buf) { + Ok(_) => Ok(buf[0]), + Err(Error::RxFifoEmpty) => Err(nb::Error::WouldBlock), + Err(e) => Err(nb::Error::Other(e)), + } + } +} + +impl embedded_hal_nb::serial::Write for LpuartTx<'_, Blocking> { + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + match self.write(&[word]) { + Ok(_) => Ok(()), + Err(Error::TxFifoFull) => Err(nb::Error::WouldBlock), + Err(e) => Err(nb::Error::Other(e)), + } + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + match self.flush() { + Ok(_) => Ok(()), + Err(Error::TxBusy) => Err(nb::Error::WouldBlock), + Err(e) => Err(nb::Error::Other(e)), + } + } +} + +impl embedded_hal_nb::serial::Read for Lpuart<'_, Blocking> { + fn read(&mut self) -> nb::Result { + embedded_hal_nb::serial::Read::read(&mut self.rx) + } +} + +impl embedded_hal_nb::serial::Write for Lpuart<'_, Blocking> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + embedded_hal_nb::serial::Write::write(&mut self.tx, char) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + embedded_hal_nb::serial::Write::flush(&mut self.tx) + } +} + +// ============================================================================ +// EMBEDDED-IO TRAIT IMPLEMENTATIONS +// ============================================================================ + +impl embedded_io::Error for Error { + fn kind(&self) -> embedded_io::ErrorKind { + embedded_io::ErrorKind::Other + } +} + +impl embedded_io::ErrorType for LpuartRx<'_, Blocking> { + type Error = Error; +} + +impl embedded_io::ErrorType for LpuartTx<'_, Blocking> { + type Error = Error; +} + +impl embedded_io::ErrorType for Lpuart<'_, Blocking> { + type Error = Error; +} + +impl embedded_io::Read for LpuartRx<'_, Blocking> { + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.blocking_read(buf).map(|_| buf.len()) + } +} + +impl embedded_io::Write for LpuartTx<'_, Blocking> { + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.blocking_write(buf).map(|_| buf.len()) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl embedded_io::Read for Lpuart<'_, Blocking> { + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + embedded_io::Read::read(&mut self.rx, buf) + } +} + +impl embedded_io::Write for Lpuart<'_, Blocking> { + fn write(&mut self, buf: &[u8]) -> core::result::Result { + embedded_io::Write::write(&mut self.tx, buf) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + embedded_io::Write::flush(&mut self.tx) + } +} diff --git a/embassy-mcxa/src/ostimer.rs b/embassy-mcxa/src/ostimer.rs new file mode 100644 index 000000000..c51812e3d --- /dev/null +++ b/embassy-mcxa/src/ostimer.rs @@ -0,0 +1,745 @@ +//! # OSTIMER Driver with Robustness Features +//! +//! This module provides an async timer driver for the NXP MCXA276 OSTIMER peripheral +//! with protection against race conditions and timer rollover issues. +//! +//! ## Features +//! +//! - Async timing with embassy-time integration +//! - Gray code counter handling (42-bit counter) +//! - Interrupt-driven wakeups +//! - Configurable interrupt priority +//! - **Race condition protection**: Critical sections and atomic operations +//! - **Timer rollover handling**: Bounds checking and rollover prevention +//! +//! ## Clock Frequency Configuration +//! +//! The OSTIMER frequency depends on your system's clock configuration. You must provide +//! the actual frequency when calling `time_driver::init()`. +//! +//! ## Race Condition Protection +//! - Critical sections in interrupt handlers prevent concurrent access +//! - Atomic register operations with memory barriers +//! - Proper interrupt flag clearing and validation +//! +//! ## Timer Rollover Handling +//! - Bounds checking prevents scheduling beyond timer capacity +//! - Immediate wake for timestamps that would cause rollover issues +#![allow(dead_code)] + +use core::sync::atomic::{AtomicBool, Ordering}; + +use embassy_hal_internal::{Peri, PeripheralType}; + +use crate::clocks::periph_helpers::{OsTimerConfig, OstimerClockSel}; +use crate::clocks::{assert_reset, enable_and_reset, is_reset_released, release_reset, Gate, PoweredClock}; +use crate::interrupt::InterruptExt; +use crate::pac; + +// PAC defines the shared RegisterBlock under `ostimer0`. +type Regs = pac::ostimer0::RegisterBlock; + +// OSTIMER EVTIMER register layout constants +/// Total width of the EVTIMER counter in bits (42 bits total) +const EVTIMER_TOTAL_BITS: u32 = 42; +/// Width of the low part of EVTIMER (bits 31:0) +const EVTIMER_LO_BITS: u32 = 32; +/// Width of the high part of EVTIMER (bits 41:32) +const EVTIMER_HI_BITS: u32 = 10; +/// Bit position where high part starts in the combined 64-bit value +const EVTIMER_HI_SHIFT: u32 = 32; + +/// Bit mask for the high part of EVTIMER +const EVTIMER_HI_MASK: u16 = (1 << EVTIMER_HI_BITS) - 1; + +/// Maximum value for MATCH_L register (32-bit) +const MATCH_L_MAX: u32 = u32::MAX; +/// Maximum value for MATCH_H register (10-bit) +const MATCH_H_MAX: u16 = EVTIMER_HI_MASK; + +/// Bit mask for extracting the low 32 bits from a 64-bit value +const LOW_32_BIT_MASK: u64 = u32::MAX as u64; + +/// Gray code conversion bit shifts (most significant to least) +const GRAY_CONVERSION_SHIFTS: [u32; 6] = [32, 16, 8, 4, 2, 1]; + +/// Maximum timer value before rollover (2^42 - 1 ticks) +/// Actual rollover time depends on the configured clock frequency +const TIMER_MAX_VALUE: u64 = (1u64 << EVTIMER_TOTAL_BITS) - 1; + +/// Threshold for detecting timer rollover in comparisons (1 second at 1MHz) +const TIMER_ROLLOVER_THRESHOLD: u64 = 1_000_000; + +/// Common default interrupt priority for OSTIMER +const DEFAULT_INTERRUPT_PRIORITY: u8 = 3; + +// Global alarm state for interrupt handling +static ALARM_ACTIVE: AtomicBool = AtomicBool::new(false); +static mut ALARM_CALLBACK: Option = None; +static mut ALARM_FLAG: Option<*const AtomicBool> = None; +static mut ALARM_TARGET_TIME: u64 = 0; + +/// Number of tight spin iterations between elapsed time checks while waiting for MATCH writes to return to the idle (0) state. +const MATCH_WRITE_READY_SPINS: usize = 512; +/// Maximum time (in OSTIMER ticks) to wait for MATCH registers to become writable (~5 ms at 1 MHz). +const MATCH_WRITE_READY_TIMEOUT_TICKS: u64 = 5_000; +/// Short stabilization delay executed after toggling the MRCC reset line to let the OSTIMER bus interface settle. +const RESET_STABILIZE_SPINS: usize = 512; + +pub(super) fn wait_for_match_write_ready(r: &Regs) -> bool { + let start = now_ticks_read(); + let mut spin_budget = 0usize; + + loop { + if r.osevent_ctrl().read().match_wr_rdy().bit_is_clear() { + return true; + } + + cortex_m::asm::nop(); + spin_budget += 1; + + if spin_budget >= MATCH_WRITE_READY_SPINS { + spin_budget = 0; + + let elapsed = now_ticks_read().wrapping_sub(start); + if elapsed >= MATCH_WRITE_READY_TIMEOUT_TICKS { + return false; + } + } + } +} + +pub(super) fn wait_for_match_write_complete(r: &Regs) -> bool { + let start = now_ticks_read(); + let mut spin_budget = 0usize; + + loop { + if r.osevent_ctrl().read().match_wr_rdy().bit_is_clear() { + return true; + } + + cortex_m::asm::nop(); + spin_budget += 1; + + if spin_budget >= MATCH_WRITE_READY_SPINS { + spin_budget = 0; + + let elapsed = now_ticks_read().wrapping_sub(start); + if elapsed >= MATCH_WRITE_READY_TIMEOUT_TICKS { + return false; + } + } + } +} + +fn prime_match_registers(r: &Regs) { + // Disable the interrupt, clear any pending flag, then wait until the MATCH registers are writable. + r.osevent_ctrl() + .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); + + if wait_for_match_write_ready(r) { + r.match_l().write(|w| unsafe { w.match_value().bits(MATCH_L_MAX) }); + r.match_h().write(|w| unsafe { w.match_value().bits(MATCH_H_MAX) }); + let _ = wait_for_match_write_complete(r); + } +} + +/// Single-shot alarm functionality for OSTIMER +pub struct Alarm<'d> { + /// Whether the alarm is currently active + active: AtomicBool, + /// Callback to execute when alarm expires (optional) + callback: Option, + /// Flag that gets set when alarm expires (optional) + flag: Option<&'d AtomicBool>, + _phantom: core::marker::PhantomData<&'d mut ()>, +} + +impl<'d> Default for Alarm<'d> { + fn default() -> Self { + Self::new() + } +} + +impl<'d> Alarm<'d> { + /// Create a new alarm instance + pub fn new() -> Self { + Self { + active: AtomicBool::new(false), + callback: None, + flag: None, + _phantom: core::marker::PhantomData, + } + } + + /// Set a callback that will be executed when the alarm expires + /// Note: Due to interrupt handler constraints, callbacks must be static function pointers + pub fn with_callback(mut self, callback: fn()) -> Self { + self.callback = Some(callback); + self + } + + /// Set a flag that will be set to true when the alarm expires + pub fn with_flag(mut self, flag: &'d AtomicBool) -> Self { + self.flag = Some(flag); + self + } + + /// Check if the alarm is currently active + pub fn is_active(&self) -> bool { + self.active.load(Ordering::Acquire) + } + + /// Cancel the alarm if it's active + pub fn cancel(&self) { + self.active.store(false, Ordering::Release); + } +} + +/// Configuration for Ostimer::new() +#[derive(Copy, Clone)] +pub struct Config { + /// Initialize MATCH registers to their max values and mask/clear the interrupt flag. + pub init_match_max: bool, + pub power: PoweredClock, + pub source: OstimerClockSel, +} + +impl Default for Config { + fn default() -> Self { + Self { + init_match_max: true, + power: PoweredClock::NormalEnabledDeepSleepDisabled, + source: OstimerClockSel::Clk1M, + } + } +} + +/// OSTIMER peripheral instance +pub struct Ostimer<'d, I: Instance> { + _inst: core::marker::PhantomData, + clock_frequency_hz: u64, + _phantom: core::marker::PhantomData<&'d mut ()>, +} + +impl<'d, I: Instance> Ostimer<'d, I> { + /// Construct OSTIMER handle. + /// Requires clocks for the instance to be enabled by the board before calling. + /// Does not enable NVIC or INTENA; use time_driver::init() for async operation. + pub fn new(_inst: Peri<'d, I>, cfg: Config) -> Self { + let clock_freq = unsafe { + enable_and_reset::(&OsTimerConfig { + power: cfg.power, + source: cfg.source, + }) + .expect("Enabling OsTimer clock should not fail") + }; + + assert!(clock_freq > 0, "OSTIMER frequency must be greater than 0"); + + if cfg.init_match_max { + let r: &Regs = unsafe { &*I::ptr() }; + // Mask INTENA, clear pending flag, and set MATCH to max so no spurious IRQ fires. + prime_match_registers(r); + } + + Self { + _inst: core::marker::PhantomData, + clock_frequency_hz: clock_freq as u64, + _phantom: core::marker::PhantomData, + } + } + + /// Get the configured clock frequency in Hz + pub fn clock_frequency_hz(&self) -> u64 { + self.clock_frequency_hz + } + + /// Read the current timer counter value in timer ticks + /// + /// # Returns + /// Current timer counter value as a 64-bit unsigned integer + pub fn now(&self) -> u64 { + now_ticks_read() + } + + /// Reset the timer counter to zero + /// + /// This performs a hardware reset of the OSTIMER peripheral, which will reset + /// the counter to zero and clear any pending interrupts. Note that this will + /// affect all timer operations including embassy-time. + /// + /// # Safety + /// This operation will reset the entire OSTIMER peripheral. Any active alarms + /// or time_driver operations will be disrupted. Use with caution. + pub fn reset(&self, _peripherals: &crate::pac::Peripherals) { + critical_section::with(|_| { + let r: &Regs = unsafe { &*I::ptr() }; + + // Mask the peripheral interrupt flag before we toggle the reset line so that + // no new NVIC activity races with the reset sequence. + r.osevent_ctrl() + .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); + + unsafe { + assert_reset::(); + + for _ in 0..RESET_STABILIZE_SPINS { + cortex_m::asm::nop(); + } + + release_reset::(); + + while !is_reset_released::() { + cortex_m::asm::nop(); + } + } + + for _ in 0..RESET_STABILIZE_SPINS { + cortex_m::asm::nop(); + } + + // Clear alarm bookkeeping before re-arming MATCH registers. + ALARM_ACTIVE.store(false, Ordering::Release); + unsafe { + ALARM_TARGET_TIME = 0; + ALARM_CALLBACK = None; + ALARM_FLAG = None; + } + + prime_match_registers(r); + }); + + // Ensure no stale OS_EVENT request remains pending after the reset sequence. + crate::interrupt::OS_EVENT.unpend(); + } + + /// Schedule a single-shot alarm to expire after the specified delay in microseconds + /// + /// # Parameters + /// * `alarm` - The alarm instance to schedule + /// * `delay_us` - Delay in microseconds from now + /// + /// # Returns + /// `true` if the alarm was scheduled successfully, `false` if it would exceed timer capacity + pub fn schedule_alarm_delay(&self, alarm: &Alarm, delay_us: u64) -> bool { + let delay_ticks = (delay_us * self.clock_frequency_hz) / 1_000_000; + let target_time = now_ticks_read() + delay_ticks; + self.schedule_alarm_at(alarm, target_time) + } + + /// Schedule a single-shot alarm to expire at the specified absolute time in timer ticks + /// + /// # Parameters + /// * `alarm` - The alarm instance to schedule + /// * `target_ticks` - Absolute time in timer ticks when the alarm should expire + /// + /// # Returns + /// `true` if the alarm was scheduled successfully, `false` if it would exceed timer capacity + pub fn schedule_alarm_at(&self, alarm: &Alarm, target_ticks: u64) -> bool { + let now = now_ticks_read(); + + // Check if target time is in the past + if target_ticks <= now { + // Execute callback immediately if alarm was supposed to be active + if alarm.active.load(Ordering::Acquire) { + alarm.active.store(false, Ordering::Release); + if let Some(callback) = alarm.callback { + callback(); + } + if let Some(flag) = &alarm.flag { + flag.store(true, Ordering::Release); + } + } + return true; + } + + // Check for timer rollover + let max_future = now + TIMER_MAX_VALUE; + if target_ticks > max_future { + return false; // Would exceed timer capacity + } + + // Program the timer + let r: &Regs = unsafe { &*I::ptr() }; + + critical_section::with(|_| { + // Disable interrupt and clear flag + r.osevent_ctrl() + .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); + + if !wait_for_match_write_ready(r) { + prime_match_registers(r); + + if !wait_for_match_write_ready(r) { + alarm.active.store(false, Ordering::Release); + ALARM_ACTIVE.store(false, Ordering::Release); + unsafe { + ALARM_TARGET_TIME = 0; + ALARM_CALLBACK = None; + ALARM_FLAG = None; + } + return false; + } + } + + // Mark alarm as active now that we know the MATCH registers are writable + alarm.active.store(true, Ordering::Release); + + // Set global alarm state for interrupt handler + ALARM_ACTIVE.store(true, Ordering::Release); + unsafe { + ALARM_TARGET_TIME = target_ticks; + ALARM_CALLBACK = alarm.callback; + ALARM_FLAG = alarm.flag.map(|f| f as *const AtomicBool); + } + + // Program MATCH registers (Gray-coded) + let gray = bin_to_gray(target_ticks); + let l = (gray & LOW_32_BIT_MASK) as u32; + let h = (((gray >> EVTIMER_HI_SHIFT) as u16) & EVTIMER_HI_MASK) as u16; + + r.match_l().write(|w| unsafe { w.match_value().bits(l) }); + r.match_h().write(|w| unsafe { w.match_value().bits(h) }); + + if !wait_for_match_write_complete(r) { + alarm.active.store(false, Ordering::Release); + ALARM_ACTIVE.store(false, Ordering::Release); + unsafe { + ALARM_TARGET_TIME = 0; + ALARM_CALLBACK = None; + ALARM_FLAG = None; + } + return false; + } + + let now_after_program = now_ticks_read(); + let intrflag_set = r.osevent_ctrl().read().ostimer_intrflag().bit_is_set(); + if now_after_program >= target_ticks && !intrflag_set { + alarm.active.store(false, Ordering::Release); + ALARM_ACTIVE.store(false, Ordering::Release); + unsafe { + ALARM_TARGET_TIME = 0; + ALARM_CALLBACK = None; + ALARM_FLAG = None; + } + return false; + } + + // Enable interrupt + r.osevent_ctrl().write(|w| w.ostimer_intena().set_bit()); + + true + }) + } + + /// Cancel any active alarm + pub fn cancel_alarm(&self, alarm: &Alarm) { + critical_section::with(|_| { + alarm.cancel(); + + // Clear global alarm state + ALARM_ACTIVE.store(false, Ordering::Release); + unsafe { ALARM_TARGET_TIME = 0 }; + + // Reset MATCH registers to maximum values to prevent spurious interrupts + let r: &Regs = unsafe { &*I::ptr() }; + prime_match_registers(r); + }); + } + + /// Check if an alarm has expired (call this from your interrupt handler) + /// Returns true if the alarm was active and has now expired + pub fn check_alarm_expired(&self, alarm: &Alarm) -> bool { + if alarm.active.load(Ordering::Acquire) { + alarm.active.store(false, Ordering::Release); + + // Execute callback + if let Some(callback) = alarm.callback { + callback(); + } + + // Set flag + if let Some(flag) = &alarm.flag { + flag.store(true, Ordering::Release); + } + + true + } else { + false + } + } +} + +/// Read current EVTIMER (Gray-coded) and convert to binary ticks. +#[inline(always)] +fn now_ticks_read() -> u64 { + let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; + + // Read high then low to minimize incoherent snapshots + let hi = (r.evtimerh().read().evtimer_count_value().bits() as u64) & (EVTIMER_HI_MASK as u64); + let lo = r.evtimerl().read().evtimer_count_value().bits() as u64; + // Combine and convert from Gray code to binary + let gray = lo | (hi << EVTIMER_HI_SHIFT); + gray_to_bin(gray) +} + +// Instance trait like other drivers, providing a PAC pointer for this OSTIMER instance +pub trait Instance: Gate + PeripheralType { + fn ptr() -> *const Regs; +} + +#[cfg(not(feature = "time"))] +impl Instance for crate::peripherals::OSTIMER0 { + #[inline(always)] + fn ptr() -> *const Regs { + pac::Ostimer0::ptr() + } +} + +#[inline(always)] +fn bin_to_gray(x: u64) -> u64 { + x ^ (x >> 1) +} + +#[inline(always)] +fn gray_to_bin(gray: u64) -> u64 { + // More efficient iterative conversion using predefined shifts + let mut bin = gray; + for &shift in &GRAY_CONVERSION_SHIFTS { + bin ^= bin >> shift; + } + bin +} + +#[cfg(feature = "time")] +pub mod time_driver { + use core::sync::atomic::Ordering; + use core::task::Waker; + + use embassy_sync::waitqueue::AtomicWaker; + use embassy_time_driver as etd; + + use super::{ + bin_to_gray, now_ticks_read, Regs, ALARM_ACTIVE, ALARM_CALLBACK, ALARM_FLAG, ALARM_TARGET_TIME, + EVTIMER_HI_MASK, EVTIMER_HI_SHIFT, LOW_32_BIT_MASK, + }; + use crate::clocks::periph_helpers::{OsTimerConfig, OstimerClockSel}; + use crate::clocks::{enable_and_reset, PoweredClock}; + use crate::pac; + + #[allow(non_camel_case_types)] + pub(crate) struct _OSTIMER0_TIME_DRIVER { + _x: (), + } + + // #[cfg(feature = "time")] + // impl_cc_gate!(_OSTIMER0_TIME_DRIVER, mrcc_glb_cc1, mrcc_glb_rst1, ostimer0, OsTimerConfig); + + impl crate::clocks::Gate for _OSTIMER0_TIME_DRIVER { + type MrccPeriphConfig = crate::clocks::periph_helpers::OsTimerConfig; + + #[inline] + unsafe fn enable_clock() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.mrcc_glb_cc1().modify(|_, w| w.ostimer0().enabled()); + } + + #[inline] + unsafe fn disable_clock() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.mrcc_glb_cc1().modify(|_r, w| w.ostimer0().disabled()); + } + + #[inline] + fn is_clock_enabled() -> bool { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.mrcc_glb_cc1().read().ostimer0().is_enabled() + } + + #[inline] + unsafe fn release_reset() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.mrcc_glb_rst1().modify(|_, w| w.ostimer0().enabled()); + } + + #[inline] + unsafe fn assert_reset() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.mrcc_glb_rst1().modify(|_, w| w.ostimer0().disabled()); + } + + #[inline] + fn is_reset_released() -> bool { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.mrcc_glb_rst1().read().ostimer0().is_enabled() + } + } + + pub struct Driver; + static TIMER_WAKER: AtomicWaker = AtomicWaker::new(); + + impl etd::Driver for Driver { + fn now(&self) -> u64 { + // Use the hardware counter (frequency configured in init) + super::now_ticks_read() + } + + fn schedule_wake(&self, timestamp: u64, waker: &Waker) { + let now = self.now(); + + // If timestamp is in the past or very close to now, wake immediately + if timestamp <= now { + waker.wake_by_ref(); + return; + } + + // Prevent scheduling too far in the future (beyond timer rollover) + // This prevents wraparound issues + let max_future = now + super::TIMER_MAX_VALUE; + if timestamp > max_future { + // For very long timeouts, wake immediately to avoid rollover issues + waker.wake_by_ref(); + return; + } + + // Register the waker first so any immediate wake below is observed by the executor. + TIMER_WAKER.register(waker); + + let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; + + critical_section::with(|_| { + // Mask INTENA and clear flag + r.osevent_ctrl() + .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); + + // Read back to ensure W1C took effect on hardware + let _ = r.osevent_ctrl().read().ostimer_intrflag().bit(); + + if !super::wait_for_match_write_ready(r) { + super::prime_match_registers(r); + + if !super::wait_for_match_write_ready(r) { + // If we can't safely program MATCH, wake immediately and leave INTENA masked. + waker.wake_by_ref(); + return; + } + } + + // Program MATCH (Gray-coded). Write low then high, then fence. + let gray = bin_to_gray(timestamp); + let l = (gray & LOW_32_BIT_MASK) as u32; + + let h = (((gray >> EVTIMER_HI_SHIFT) as u16) & EVTIMER_HI_MASK) as u16; + + r.match_l().write(|w| unsafe { w.match_value().bits(l) }); + r.match_h().write(|w| unsafe { w.match_value().bits(h) }); + + if !super::wait_for_match_write_complete(r) { + waker.wake_by_ref(); + return; + } + + let now_after_program = super::now_ticks_read(); + let intrflag_set = r.osevent_ctrl().read().ostimer_intrflag().bit_is_set(); + if now_after_program >= timestamp && !intrflag_set { + waker.wake_by_ref(); + return; + } + + // Enable peripheral interrupt + r.osevent_ctrl().write(|w| w.ostimer_intena().set_bit()); + }); + } + } + + /// Install the global embassy-time driver and configure NVIC priority for OS_EVENT. + /// + /// # Parameters + /// * `priority` - Interrupt priority for the OSTIMER interrupt + /// * `frequency_hz` - Actual OSTIMER clock frequency in Hz (stored for future use) + /// + /// Note: The frequency parameter is currently accepted for API compatibility. + /// The embassy_time_driver macro handles driver registration automatically. + pub fn init(priority: crate::interrupt::Priority, frequency_hz: u64) { + let _clock_freq = unsafe { + enable_and_reset::<_OSTIMER0_TIME_DRIVER>(&OsTimerConfig { + power: PoweredClock::AlwaysEnabled, + source: OstimerClockSel::Clk1M, + }) + .expect("Enabling OsTimer clock should not fail") + }; + + // Mask/clear at peripheral and set default MATCH + let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; + super::prime_match_registers(r); + + // Configure NVIC for timer operation + crate::interrupt::OS_EVENT.configure_for_timer(priority); + + // Note: The embassy_time_driver macro automatically registers the driver + // The frequency parameter is accepted for future compatibility + let _ = frequency_hz; // Suppress unused parameter warning + } + + // Export the global time driver expected by embassy-time + embassy_time_driver::time_driver_impl!(static DRIVER: Driver = Driver); + + /// To be called from the OS_EVENT IRQ. + pub fn on_interrupt() { + let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; + + // Critical section to prevent races with schedule_wake + critical_section::with(|_| { + // Check if interrupt is actually pending and handle it atomically + if r.osevent_ctrl().read().ostimer_intrflag().bit_is_set() { + // Clear flag and disable interrupt atomically + r.osevent_ctrl().write(|w| { + w.ostimer_intrflag() + .clear_bit_by_one() // Write-1-to-clear using safe helper + .ostimer_intena() + .clear_bit() + }); + + // Wake the waiting task + TIMER_WAKER.wake(); + + // Handle alarm callback if active and this interrupt is for the alarm + if ALARM_ACTIVE.load(Ordering::SeqCst) { + let current_time = now_ticks_read(); + let target_time = unsafe { ALARM_TARGET_TIME }; + + // Check if current time is close to alarm target time (within 1000 ticks for timing variations) + if current_time >= target_time && current_time <= target_time + 1000 { + ALARM_ACTIVE.store(false, Ordering::SeqCst); + unsafe { ALARM_TARGET_TIME = 0 }; + + // Execute callback if set + unsafe { + if let Some(callback) = ALARM_CALLBACK { + callback(); + } + } + + // Set flag if provided + unsafe { + if let Some(flag) = ALARM_FLAG { + (*flag).store(true, Ordering::SeqCst); + } + } + } + } + } + }); + } +} + +#[cfg(feature = "time")] +use crate::pac::interrupt; + +#[cfg(feature = "time")] +#[allow(non_snake_case)] +#[interrupt] +fn OS_EVENT() { + time_driver::on_interrupt() +} diff --git a/embassy-mcxa/src/pins.rs b/embassy-mcxa/src/pins.rs new file mode 100644 index 000000000..fdf1b0a86 --- /dev/null +++ b/embassy-mcxa/src/pins.rs @@ -0,0 +1,28 @@ +//! Pin configuration helpers (separate from peripheral drivers). +use crate::pac; + +pub unsafe fn configure_adc_pins() { + // P1_10 = ADC1_A8 + let port1 = &*pac::Port1::ptr(); + port1.pcr10().write(|w| { + w.ps() + .ps0() + .pe() + .pe0() + .sre() + .sre0() + .ode() + .ode0() + .dse() + .dse0() + .mux() + .mux0() + .ibe() + .ibe0() + .inv() + .inv0() + .lk() + .lk0() + }); + core::arch::asm!("dsb sy; isb sy"); +} diff --git a/embassy-mcxa/src/rtc.rs b/embassy-mcxa/src/rtc.rs new file mode 100644 index 000000000..f975d9c9f --- /dev/null +++ b/embassy-mcxa/src/rtc.rs @@ -0,0 +1,453 @@ +//! RTC DateTime driver. +use core::marker::PhantomData; + +use embassy_hal_internal::{Peri, PeripheralType}; +use maitake_sync::WaitCell; + +use crate::clocks::with_clocks; +use crate::interrupt::typelevel::{Handler, Interrupt}; +use crate::pac; +use crate::pac::rtc0::cr::Um; + +type Regs = pac::rtc0::RegisterBlock; + +/// Global wait cell for alarm notifications +static WAKER: WaitCell = WaitCell::new(); + +/// RTC interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +/// Trait for RTC peripheral instances +pub trait Instance: PeripheralType { + type Interrupt: Interrupt; + fn ptr() -> *const Regs; +} + +/// Token for RTC0 +pub type Rtc0 = crate::peripherals::RTC0; +impl Instance for crate::peripherals::RTC0 { + type Interrupt = crate::interrupt::typelevel::RTC; + #[inline(always)] + fn ptr() -> *const Regs { + pac::Rtc0::ptr() + } +} + +/// Number of days in a standard year +const DAYS_IN_A_YEAR: u32 = 365; +/// Number of seconds in a day +const SECONDS_IN_A_DAY: u32 = 86400; +/// Number of seconds in an hour +const SECONDS_IN_A_HOUR: u32 = 3600; +/// Number of seconds in a minute +const SECONDS_IN_A_MINUTE: u32 = 60; +/// Unix epoch start year +const YEAR_RANGE_START: u16 = 1970; + +/// Date and time structure for RTC operations +#[derive(Debug, Clone, Copy)] +pub struct RtcDateTime { + pub year: u16, + pub month: u8, + pub day: u8, + pub hour: u8, + pub minute: u8, + pub second: u8, +} +#[derive(Copy, Clone)] +pub struct RtcConfig { + #[allow(dead_code)] + wakeup_select: bool, + update_mode: Um, + #[allow(dead_code)] + supervisor_access: bool, + compensation_interval: u8, + compensation_time: u8, +} + +/// RTC interrupt enable flags +#[derive(Copy, Clone)] +pub struct RtcInterruptEnable; +impl RtcInterruptEnable { + pub const RTC_TIME_INVALID_INTERRUPT_ENABLE: u32 = 1 << 0; + pub const RTC_TIME_OVERFLOW_INTERRUPT_ENABLE: u32 = 1 << 1; + pub const RTC_ALARM_INTERRUPT_ENABLE: u32 = 1 << 2; + pub const RTC_SECONDS_INTERRUPT_ENABLE: u32 = 1 << 4; +} + +/// Converts a DateTime structure to Unix timestamp (seconds since 1970-01-01) +/// +/// # Arguments +/// +/// * `datetime` - The date and time to convert +/// +/// # Returns +/// +/// Unix timestamp as u32 +/// +/// # Note +/// +/// This function handles leap years correctly. +pub fn convert_datetime_to_seconds(datetime: &RtcDateTime) -> u32 { + let month_days: [u16; 13] = [0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; + + let mut seconds = (datetime.year as u32 - 1970) * DAYS_IN_A_YEAR; + seconds += (datetime.year as u32 / 4) - (1970 / 4); + seconds += month_days[datetime.month as usize] as u32; + seconds += datetime.day as u32 - 1; + + if (datetime.year & 3 == 0) && (datetime.month <= 2) { + seconds -= 1; + } + + seconds = seconds * SECONDS_IN_A_DAY + + (datetime.hour as u32 * SECONDS_IN_A_HOUR) + + (datetime.minute as u32 * SECONDS_IN_A_MINUTE) + + datetime.second as u32; + + seconds +} + +/// Converts Unix timestamp to DateTime structure +/// +/// # Arguments +/// +/// * `seconds` - Unix timestamp (seconds since 1970-01-01) +/// +/// # Returns +/// +/// RtcDateTime structure with the converted date and time +/// +/// # Note +/// +/// This function handles leap years correctly. +pub fn convert_seconds_to_datetime(seconds: u32) -> RtcDateTime { + let mut seconds_remaining = seconds; + let mut days = seconds_remaining / SECONDS_IN_A_DAY + 1; + seconds_remaining %= SECONDS_IN_A_DAY; + + let hour = (seconds_remaining / SECONDS_IN_A_HOUR) as u8; + seconds_remaining %= SECONDS_IN_A_HOUR; + let minute = (seconds_remaining / SECONDS_IN_A_MINUTE) as u8; + let second = (seconds_remaining % SECONDS_IN_A_MINUTE) as u8; + + let mut year = YEAR_RANGE_START; + let mut days_in_year = DAYS_IN_A_YEAR; + + while days > days_in_year { + days -= days_in_year; + year += 1; + + days_in_year = if year.is_multiple_of(4) { + DAYS_IN_A_YEAR + 1 + } else { + DAYS_IN_A_YEAR + }; + } + + let days_per_month = [ + 31, + if year.is_multiple_of(4) { 29 } else { 28 }, + 31, + 30, + 31, + 30, + 31, + 31, + 30, + 31, + 30, + 31, + ]; + + let mut month = 1; + for (m, month_days) in days_per_month.iter().enumerate() { + let m = m + 1; + if days <= *month_days as u32 { + month = m; + break; + } else { + days -= *month_days as u32; + } + } + + let day = days as u8; + + RtcDateTime { + year, + month: month as u8, + day, + hour, + minute, + second, + } +} + +/// Returns default RTC configuration +/// +/// # Returns +/// +/// RtcConfig with sensible default values: +/// - No wakeup selection +/// - Update mode 0 (immediate updates) +/// - No supervisor access restriction +/// - No compensation +pub fn get_default_config() -> RtcConfig { + RtcConfig { + wakeup_select: false, + update_mode: Um::Um0, + supervisor_access: false, + compensation_interval: 0, + compensation_time: 0, + } +} +/// Minimal RTC handle for a specific instance I (store the zero-sized token like embassy) +pub struct Rtc<'a, I: Instance> { + _inst: core::marker::PhantomData<&'a mut I>, +} + +impl<'a, I: Instance> Rtc<'a, I> { + /// Create a new instance of the real time clock. + pub fn new( + _inst: Peri<'a, I>, + _irq: impl crate::interrupt::typelevel::Binding> + 'a, + config: RtcConfig, + ) -> Self { + let rtc = unsafe { &*I::ptr() }; + + // The RTC is NOT gated by the MRCC, but we DO need to make sure the 16k clock + // on the vsys domain is active + let clocks = with_clocks(|c| c.clk_16k_vsys.clone()); + match clocks { + None => panic!("Clocks have not been initialized"), + Some(None) => panic!("Clocks initialized, but clk_16k_vsys not active"), + Some(Some(_)) => {} + } + + // RTC reset + rtc.cr().modify(|_, w| w.swr().set_bit()); + rtc.cr().modify(|_, w| w.swr().clear_bit()); + rtc.tsr().write(|w| unsafe { w.bits(1) }); + + rtc.cr().modify(|_, w| w.um().variant(config.update_mode)); + + rtc.tcr().modify(|_, w| unsafe { + w.cir() + .bits(config.compensation_interval) + .tcr() + .bits(config.compensation_time) + }); + + // Enable RTC interrupt + I::Interrupt::unpend(); + unsafe { I::Interrupt::enable() }; + + Self { + _inst: core::marker::PhantomData, + } + } + + /// Set the current date and time + /// + /// # Arguments + /// + /// * `datetime` - The date and time to set + /// + /// # Note + /// + /// The datetime is converted to Unix timestamp and written to the time seconds register. + pub fn set_datetime(&self, datetime: RtcDateTime) { + let rtc = unsafe { &*I::ptr() }; + let seconds = convert_datetime_to_seconds(&datetime); + rtc.tsr().write(|w| unsafe { w.bits(seconds) }); + } + + /// Get the current date and time + /// + /// # Returns + /// + /// Current date and time as RtcDateTime + /// + /// # Note + /// + /// Reads the current Unix timestamp from the time seconds register and converts it. + pub fn get_datetime(&self) -> RtcDateTime { + let rtc = unsafe { &*I::ptr() }; + let seconds = rtc.tsr().read().bits(); + convert_seconds_to_datetime(seconds) + } + + /// Set the alarm date and time + /// + /// # Arguments + /// + /// * `alarm` - The date and time when the alarm should trigger + /// + /// # Note + /// + /// This function: + /// - Clears any existing alarm by writing 0 to the alarm register + /// - Waits for the clear operation to complete + /// - Sets the new alarm time + /// - Waits for the write operation to complete + /// - Uses timeouts to prevent infinite loops + /// - Enables the alarm interrupt after setting + pub fn set_alarm(&self, alarm: RtcDateTime) { + let rtc = unsafe { &*I::ptr() }; + let seconds = convert_datetime_to_seconds(&alarm); + + rtc.tar().write(|w| unsafe { w.bits(0) }); + let mut timeout = 10000; + while rtc.tar().read().bits() != 0 && timeout > 0 { + timeout -= 1; + } + + rtc.tar().write(|w| unsafe { w.bits(seconds) }); + + let mut timeout = 10000; + while rtc.tar().read().bits() != seconds && timeout > 0 { + timeout -= 1; + } + + self.set_interrupt(RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE); + } + + /// Get the current alarm date and time + /// + /// # Returns + /// + /// Alarm date and time as RtcDateTime + /// + /// # Note + /// + /// Reads the alarm timestamp from the time alarm register and converts it. + pub fn get_alarm(&self) -> RtcDateTime { + let rtc = unsafe { &*I::ptr() }; + let alarm_seconds = rtc.tar().read().bits(); + convert_seconds_to_datetime(alarm_seconds) + } + + /// Start the RTC time counter + /// + /// # Note + /// + /// Sets the Time Counter Enable (TCE) bit in the status register. + pub fn start(&self) { + let rtc = unsafe { &*I::ptr() }; + rtc.sr().modify(|_, w| w.tce().set_bit()); + } + + /// Stop the RTC time counter + /// + /// # Note + /// + /// Clears the Time Counter Enable (TCE) bit in the status register. + pub fn stop(&self) { + let rtc = unsafe { &*I::ptr() }; + rtc.sr().modify(|_, w| w.tce().clear_bit()); + } + + /// Enable specific RTC interrupts + /// + /// # Arguments + /// + /// * `mask` - Bitmask of interrupts to enable (use RtcInterruptEnable constants) + /// + /// # Note + /// + /// This function enables the specified interrupt types and resets the alarm occurred flag. + /// Available interrupts: + /// - Time Invalid Interrupt + /// - Time Overflow Interrupt + /// - Alarm Interrupt + /// - Seconds Interrupt + pub fn set_interrupt(&self, mask: u32) { + let rtc = unsafe { &*I::ptr() }; + + if (RtcInterruptEnable::RTC_TIME_INVALID_INTERRUPT_ENABLE & mask) != 0 { + rtc.ier().modify(|_, w| w.tiie().tiie_1()); + } + if (RtcInterruptEnable::RTC_TIME_OVERFLOW_INTERRUPT_ENABLE & mask) != 0 { + rtc.ier().modify(|_, w| w.toie().toie_1()); + } + if (RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE & mask) != 0 { + rtc.ier().modify(|_, w| w.taie().taie_1()); + } + if (RtcInterruptEnable::RTC_SECONDS_INTERRUPT_ENABLE & mask) != 0 { + rtc.ier().modify(|_, w| w.tsie().tsie_1()); + } + } + + /// Disable specific RTC interrupts + /// + /// # Arguments + /// + /// * `mask` - Bitmask of interrupts to disable (use RtcInterruptEnable constants) + /// + /// # Note + /// + /// This function disables the specified interrupt types. + pub fn disable_interrupt(&self, mask: u32) { + let rtc = unsafe { &*I::ptr() }; + + if (RtcInterruptEnable::RTC_TIME_INVALID_INTERRUPT_ENABLE & mask) != 0 { + rtc.ier().modify(|_, w| w.tiie().tiie_0()); + } + if (RtcInterruptEnable::RTC_TIME_OVERFLOW_INTERRUPT_ENABLE & mask) != 0 { + rtc.ier().modify(|_, w| w.toie().toie_0()); + } + if (RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE & mask) != 0 { + rtc.ier().modify(|_, w| w.taie().taie_0()); + } + if (RtcInterruptEnable::RTC_SECONDS_INTERRUPT_ENABLE & mask) != 0 { + rtc.ier().modify(|_, w| w.tsie().tsie_0()); + } + } + + /// Clear the alarm interrupt flag + /// + /// # Note + /// + /// This function clears the Time Alarm Interrupt Enable bit. + pub fn clear_alarm_flag(&self) { + let rtc = unsafe { &*I::ptr() }; + rtc.ier().modify(|_, w| w.taie().clear_bit()); + } + + /// Wait for an RTC alarm to trigger. + /// + /// # Arguments + /// + /// * `alarm` - The date and time when the alarm should trigger + /// This function will wait until the RTC alarm is triggered. + /// If no alarm is scheduled, it will wait indefinitely until one is scheduled and triggered. + pub async fn wait_for_alarm(&mut self, alarm: RtcDateTime) { + let wait = WAKER.subscribe().await; + + self.set_alarm(alarm); + self.start(); + + // REVISIT: propagate error? + let _ = wait.await; + + // Clear the interrupt and disable the alarm after waking up + self.disable_interrupt(RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE); + } +} + +/// RTC interrupt handler +/// +/// This struct implements the interrupt handler for RTC events. +impl Handler for InterruptHandler { + unsafe fn on_interrupt() { + let rtc = &*pac::Rtc0::ptr(); + // Check if this is actually a time alarm interrupt + let sr = rtc.sr().read(); + if sr.taf().bit_is_set() { + rtc.ier().modify(|_, w| w.taie().clear_bit()); + WAKER.wake(); + } + } +} -- cgit