From 0443134bc47918d2f8f0ede1b292b372629f8894 Mon Sep 17 00:00:00 2001 From: Bogdan Petru Chircu Mare Date: Fri, 31 Oct 2025 21:44:48 -0700 Subject: feat(mcxa276): initial HAL import --- src/adc.rs | 376 +++++++++++++++ src/baremetal/mod.rs | 6 - src/board.rs | 14 + src/clocks.rs | 136 ++++++ src/config.rs | 20 + src/gpio.rs | 246 ++++++++++ src/interrupt.rs | 349 ++++++++++++++ src/lib.rs | 165 +++++++ src/lpuart/buffered.rs | 686 +++++++++++++++++++++++++++ src/lpuart/mod.rs | 1208 ++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 18 - src/ostimer.rs | 704 ++++++++++++++++++++++++++++ src/pins.rs | 127 +++++ src/reset.rs | 112 +++++ src/rtc.rs | 281 +++++++++++ src/uart.rs | 308 ++++++++++++ 16 files changed, 4732 insertions(+), 24 deletions(-) create mode 100644 src/adc.rs delete mode 100644 src/baremetal/mod.rs create mode 100644 src/board.rs create mode 100644 src/clocks.rs create mode 100644 src/config.rs create mode 100644 src/gpio.rs create mode 100644 src/interrupt.rs create mode 100644 src/lib.rs create mode 100644 src/lpuart/buffered.rs create mode 100644 src/lpuart/mod.rs delete mode 100644 src/main.rs create mode 100644 src/ostimer.rs create mode 100644 src/pins.rs create mode 100644 src/reset.rs create mode 100644 src/rtc.rs create mode 100644 src/uart.rs (limited to 'src') diff --git a/src/adc.rs b/src/adc.rs new file mode 100644 index 000000000..dd6530605 --- /dev/null +++ b/src/adc.rs @@ -0,0 +1,376 @@ +//! ADC driver +use crate::pac; +use core::sync::atomic::{AtomicBool, Ordering}; + +use crate::pac::adc1::ctrl::{CalAvgs}; +use crate::pac::adc1::cfg::{Refsel, Pwrsel, Tprictrl, Tres, Tcmdres, HptExdi}; +use crate::pac::adc1::tctrl::{Tcmd, Tpri}; +use crate::pac::adc1::cmdl1::{Adch, Ctype, Mode}; +use crate::pac::adc1::cmdh1::{Next, Avgs, Sts, Cmpen}; + +type Regs = pac::adc1::RegisterBlock; + +static INTERRUPT_TRIGGERED: AtomicBool = AtomicBool::new(false); +// Token-based instance pattern like embassy-imxrt +pub trait Instance { + fn ptr() -> *const Regs; +} + +/// Token for ADC1 +#[cfg(feature = "adc1")] +pub type Adc1 = crate::peripherals::ADC1; +#[cfg(feature = "adc1")] +impl Instance for crate::peripherals::ADC1 { + #[inline(always)] + fn ptr() -> *const Regs { + pac::Adc1::ptr() + } +} + +// Also implement Instance for the Peri wrapper type +#[cfg(feature = "adc1")] +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, +} + +#[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 { + _inst: core::marker::PhantomData, +} + +impl Adc { + /// initialize ADC + pub fn new(_inst: impl Instance, config : LpadcConfig) -> Self { + let adc = unsafe { &*I::ptr() }; + + /* 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 get_default_config() -> LpadcConfig { + 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, + } + } + + 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 = 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 >> 0) + 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) + .ctype().variant(config.sample_channel_mode) + .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(); } +} \ No newline at end of file diff --git a/src/baremetal/mod.rs b/src/baremetal/mod.rs deleted file mode 100644 index c03b9538b..000000000 --- a/src/baremetal/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -use core::panic::PanicInfo; - -#[panic_handler] -fn panic(_info: &PanicInfo) -> ! { - loop {} -} diff --git a/src/board.rs b/src/board.rs new file mode 100644 index 000000000..fa679e82c --- /dev/null +++ b/src/board.rs @@ -0,0 +1,14 @@ +use crate::{clocks, pac, pins}; + +/// Initialize clocks and pin muxing for UART2 debug console. +pub unsafe fn init_uart2(p: &pac::Peripherals) { + clocks::ensure_frolf_running(p); + clocks::enable_uart2_port2(p); + pins::configure_uart2_pins_port2(); + clocks::select_uart2_clock(p); +} + +/// Initialize clocks for the LED GPIO/PORT used by the blink example. +pub unsafe fn init_led(p: &pac::Peripherals) { + clocks::enable_led_port(p); +} diff --git a/src/clocks.rs b/src/clocks.rs new file mode 100644 index 000000000..5336c3efe --- /dev/null +++ b/src/clocks.rs @@ -0,0 +1,136 @@ +//! Clock control helpers (no magic numbers, PAC field access only). +//! Provides reusable gate abstractions for peripherals used by the examples. +use crate::pac; + +/// Trait describing an AHB clock gate that can be toggled through MRCC. +pub trait Gate { + /// Enable the clock gate. + unsafe fn enable(mrcc: &pac::mrcc0::RegisterBlock); + + /// Return whether the clock gate is currently enabled. + fn is_enabled(mrcc: &pac::mrcc0::RegisterBlock) -> bool; +} + +/// Enable a clock gate for the given peripheral set. +#[inline] +pub unsafe fn enable(peripherals: &pac::Peripherals) { + let mrcc = &peripherals.mrcc0; + G::enable(mrcc); + while !G::is_enabled(mrcc) {} + core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags)); +} + +/// Check whether a gate is currently enabled. +#[inline] +pub fn is_enabled(peripherals: &pac::Peripherals) -> bool { + G::is_enabled(&peripherals.mrcc0) +} + +macro_rules! impl_cc_gate { + ($name:ident, $reg:ident, $field:ident) => { + pub struct $name; + + impl Gate for $name { + #[inline] + unsafe fn enable(mrcc: &pac::mrcc0::RegisterBlock) { + mrcc.$reg().modify(|_, w| w.$field().enabled()); + } + + #[inline] + fn is_enabled(mrcc: &pac::mrcc0::RegisterBlock) -> bool { + mrcc.$reg().read().$field().is_enabled() + } + } + }; +} + +pub mod gate { + use super::*; + + impl_cc_gate!(Port2, mrcc_glb_cc1, port2); + impl_cc_gate!(Port3, mrcc_glb_cc1, port3); + impl_cc_gate!(Ostimer0, mrcc_glb_cc1, ostimer0); + impl_cc_gate!(Lpuart2, mrcc_glb_cc0, lpuart2); + impl_cc_gate!(Gpio3, mrcc_glb_cc2, gpio3); + impl_cc_gate!(Port1, mrcc_glb_cc1, port1); + impl_cc_gate!(Adc1, mrcc_glb_cc1, adc1); +} + +/// Convenience helper enabling the PORT2 and LPUART2 gates required for the debug UART. +pub unsafe fn enable_uart2_port2(peripherals: &pac::Peripherals) { + enable::(peripherals); + enable::(peripherals); +} + +/// Convenience helper enabling the PORT3 and GPIO3 gates used by the LED in the examples. +pub unsafe fn enable_led_port(peripherals: &pac::Peripherals) { + enable::(peripherals); + enable::(peripherals); +} + +/// Convenience helper enabling the OSTIMER0 clock gate. +pub unsafe fn enable_ostimer0(peripherals: &pac::Peripherals) { + enable::(peripherals); +} + +pub unsafe fn select_uart2_clock(peripherals: &pac::Peripherals) { + // Use FRO_LF_DIV (already running) MUX=0 DIV=0 + let mrcc = &peripherals.mrcc0; + mrcc.mrcc_lpuart2_clksel() + .write(|w| w.mux().clkroot_func_0()); + mrcc.mrcc_lpuart2_clkdiv().write(|w| unsafe { w.bits(0) }); +} + +pub unsafe fn ensure_frolf_running(peripherals: &pac::Peripherals) { + // Ensure FRO_LF divider clock is running (reset default HALT=1 stops it) + let sys = &peripherals.syscon; + sys.frolfdiv().modify(|_, w| { + // DIV defaults to 0; keep it explicit and clear HALT + unsafe { w.div().bits(0) }.halt().run() + }); +} + +/// Compute the FRO_LF_DIV output frequency currently selected for LPUART2. +/// Assumes select_uart2_clock() has chosen MUX=0 (FRO_LF_DIV) and DIV is set in SYSCON.FRO_LF_DIV. +pub unsafe fn uart2_src_hz(peripherals: &pac::Peripherals) -> u32 { + // SYSCON.FRO_LF_DIV: DIV field is simple divider: freq_out = 12_000_000 / (DIV+1) for many NXP parts. + // On MCXA276 FRO_LF base is 12 MHz; our init keeps DIV=0, so result=12_000_000. + // Read it anyway for future generality. + let div = peripherals.syscon.frolfdiv().read().div().bits() as u32; + let base = 12_000_000u32; + base / (div + 1) +} + +/// Enable clock gate and release reset for OSTIMER0. +/// Select OSTIMER0 clock source = 1 MHz root (working bring-up configuration). +pub unsafe fn select_ostimer0_clock_1m(peripherals: &pac::Peripherals) { + let mrcc = &peripherals.mrcc0; + mrcc.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_1m()); +} + +pub unsafe fn init_fro16k(peripherals: &pac::Peripherals) { + let vbat = &peripherals.vbat0; + // Enable FRO16K oscillator + vbat.froctla().modify(|_, w| w.fro_en().set_bit()); + + // Lock the control register + vbat.frolcka().modify(|_, w| w.lock().set_bit()); + + // 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 + vbat.froclke().modify(|_, w| unsafe { w.clke().bits(0x3) }); +} + +pub unsafe fn enable_adc(peripherals: &pac::Peripherals) { + enable::(peripherals); + enable::(peripherals); +} + +pub unsafe fn select_adc_clock(peripherals: &pac::Peripherals) { + // Use FRO_LF_DIV (already running) MUX=0 DIV=0 + let mrcc = &peripherals.mrcc0; + mrcc.mrcc_adc_clksel() + .write(|w| w.mux().clkroot_func_0()); + mrcc.mrcc_adc_clkdiv().write(|w| unsafe { w.bits(0) }); +} \ No newline at end of file diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 000000000..93aed5a99 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,20 @@ +// HAL configuration (minimal), mirroring embassy-imxrt style + +use crate::interrupt::Priority; + +#[non_exhaustive] +pub struct Config { + pub time_interrupt_priority: Priority, + pub rtc_interrupt_priority: Priority, + pub adc_interrupt_priority: Priority, +} + +impl Default for Config { + fn default() -> Self { + Self { + time_interrupt_priority: Priority::from(0), + rtc_interrupt_priority: Priority::from(0), + adc_interrupt_priority: Priority::from(0), + } + } +} diff --git a/src/gpio.rs b/src/gpio.rs new file mode 100644 index 000000000..08f375cba --- /dev/null +++ b/src/gpio.rs @@ -0,0 +1,246 @@ +//! 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::marker::PhantomData; + +use crate::{pac, pins as pin_config}; + +/// Logical level for GPIO pins. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum Level { + Low, + High, +} + +pub type Gpio = crate::peripherals::GPIO; + +/// Type-erased representation of a GPIO pin. +#[derive(Copy, Clone)] +pub struct AnyPin { + port: u8, + pin: u8, + gpio: *const pac::gpio0::RegisterBlock, +} + +impl AnyPin { + /// Create an `AnyPin` from raw components. + pub fn new(port: u8, pin: u8, gpio: *const pac::gpio0::RegisterBlock) -> Self { + Self { port, pin, gpio } + } + + #[inline(always)] + fn mask(&self) -> u32 { + 1u32 << self.pin + } + + #[inline(always)] + fn gpio(&self) -> &'static pac::gpio0::RegisterBlock { + unsafe { &*self.gpio } + } + + #[inline(always)] + pub fn port_index(&self) -> u8 { + self.port + } + + #[inline(always)] + pub fn pin_index(&self) -> u8 { + self.pin + } +} + +/// Type-level trait implemented by concrete pin ZSTs. +pub trait PinId { + fn port_index() -> u8; + fn pin_index() -> u8; + fn gpio_ptr() -> *const pac::gpio0::RegisterBlock; + + fn set_mux_gpio() { + unsafe { pin_config::set_pin_mux_gpio(Self::port_index(), Self::pin_index()) } + } + + fn degrade() -> AnyPin { + AnyPin::new(Self::port_index(), Self::pin_index(), Self::gpio_ptr()) + } +} + +pub mod pins { + use super::{pac, AnyPin, PinId}; + + macro_rules! define_pin { + ($Name:ident, $port:literal, $pin:literal, $GpioBlk:ident) => { + pub struct $Name; + impl super::PinId for $Name { + #[inline(always)] + fn port_index() -> u8 { + $port + } + #[inline(always)] + fn pin_index() -> u8 { + $pin + } + #[inline(always)] + fn gpio_ptr() -> *const pac::gpio0::RegisterBlock { + pac::$GpioBlk::ptr() + } + } + + impl $Name { + /// Convenience helper to obtain a type-erased handle to this pin. + pub fn degrade() -> AnyPin { + ::degrade() + } + + pub fn set_mux_gpio() { + ::set_mux_gpio() + } + } + }; + } + + // Extend this list as more pins are needed. + define_pin!(PIO3_18, 3, 18, Gpio3); +} + +/// A flexible pin that can be configured as input or output. +pub struct Flex<'d> { + pin: AnyPin, + _marker: PhantomData<&'d mut ()>, +} + +impl<'d> Flex<'d> { + pub fn new(pin: AnyPin) -> Self { + Self { + pin, + _marker: PhantomData, + } + } + + #[inline(always)] + fn gpio(&self) -> &'static pac::gpio0::RegisterBlock { + self.pin.gpio() + } + + #[inline(always)] + fn mask(&self) -> u32 { + self.pin.mask() + } + + pub fn set_as_input(&mut self) { + let mask = self.mask(); + let gpio = self.gpio(); + gpio.pddr() + .modify(|r, w| unsafe { w.bits(r.bits() & !mask) }); + } + + pub fn set_as_output(&mut self) { + let mask = self.mask(); + let gpio = self.gpio(); + gpio.pddr() + .modify(|r, w| unsafe { w.bits(r.bits() | mask) }); + } + + pub fn set_high(&mut self) { + self.gpio().psor().write(|w| unsafe { w.bits(self.mask()) }); + } + + pub fn set_low(&mut self) { + self.gpio().pcor().write(|w| unsafe { w.bits(self.mask()) }); + } + + pub fn set_level(&mut self, level: Level) { + match level { + Level::High => self.set_high(), + Level::Low => self.set_low(), + } + } + + pub fn toggle(&mut self) { + self.gpio().ptor().write(|w| unsafe { w.bits(self.mask()) }); + } + + pub fn is_high(&self) -> bool { + (self.gpio().pdir().read().bits() & self.mask()) != 0 + } + + pub fn is_low(&self) -> bool { + !self.is_high() + } +} + +/// GPIO output driver that owns a `Flex` pin. +pub struct Output<'d> { + flex: Flex<'d>, +} + +impl<'d> Output<'d> { + pub fn new(pin: AnyPin, initial: Level) -> Self { + let mut flex = Flex::new(pin); + flex.set_level(initial); + flex.set_as_output(); + Self { flex } + } + + #[inline] + pub fn set_high(&mut self) { + self.flex.set_high(); + } + + #[inline] + pub fn set_low(&mut self) { + self.flex.set_low(); + } + + #[inline] + pub fn set_level(&mut self, level: Level) { + self.flex.set_level(level); + } + + #[inline] + pub fn toggle(&mut self) { + self.flex.toggle(); + } + + #[inline] + pub fn is_set_high(&self) -> bool { + self.flex.is_high() + } + + #[inline] + pub fn is_set_low(&self) -> bool { + !self.is_set_high() + } + + /// Expose the inner `Flex` if callers need to reconfigure the pin. + 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> { + pub fn new(pin: AnyPin) -> Self { + let mut flex = Flex::new(pin); + flex.set_as_input(); + Self { flex } + } + + #[inline] + pub fn is_high(&self) -> bool { + self.flex.is_high() + } + + #[inline] + pub fn is_low(&self) -> bool { + self.flex.is_low() + } + + pub fn into_flex(self) -> Flex<'d> { + self.flex + } +} diff --git a/src/interrupt.rs b/src/interrupt.rs new file mode 100644 index 000000000..8226f01ab --- /dev/null +++ b/src/interrupt.rs @@ -0,0 +1,349 @@ +//! 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. + +mod generated { + embassy_hal_internal::interrupt_mod!(OS_EVENT, LPUART2, RTC, ADC1); +} + +pub use generated::interrupt::typelevel; +pub use generated::interrupt::Priority; + +use crate::pac::Interrupt; +use core::sync::atomic::{AtomicU16, AtomicU32, Ordering}; + +/// 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) + } +} + +/// 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/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..eb4727106 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,165 @@ +#![no_std] + +pub mod clocks; // still provide clock helpers +#[cfg(feature = "gpio")] +pub mod gpio; +pub mod pins; // pin mux helpers +pub mod reset; // reset control helpers + +pub mod config; +pub mod interrupt; +pub mod ostimer; +pub mod uart; +pub mod lpuart; +pub mod rtc; +pub mod adc; + +embassy_hal_internal::peripherals!( + #[cfg(feature = "lpuart2")] + LPUART2, + #[cfg(feature = "ostimer0")] + OSTIMER0, + #[cfg(feature = "gpio")] + GPIO, + #[cfg(feature = "rtc0")] + RTC0, + #[cfg(feature = "adc1")] + ADC1, +); + +/// Get access to the PAC Peripherals for low-level register access. +/// This is a lazy-initialized singleton that can be called after init(). +#[allow(static_mut_refs)] +pub fn pac() -> &'static pac::Peripherals { + // SAFETY: We only call this after init(), and the PAC is a singleton. + // The embassy peripheral tokens ensure we don't have multiple mutable accesses. + unsafe { + static mut PAC_INSTANCE: Option = None; + if PAC_INSTANCE.is_none() { + PAC_INSTANCE = Some(pac::Peripherals::steal()); + } + PAC_INSTANCE.as_ref().unwrap() + } +} + +pub use cortex_m_rt; +pub use mcxa276_pac as pac; +// Use cortex-m-rt's #[interrupt] attribute directly; PAC does not re-export it. + +// Re-export interrupt traits and types +pub use interrupt::InterruptExt; +#[cfg(feature = "ostimer0")] +pub use ostimer::Ostimer0 as Ostimer0Token; +#[cfg(feature = "lpuart2")] +pub use uart::Lpuart2 as Uart2Token; +#[cfg(feature = "rtc0")] +pub use rtc::Rtc0 as Rtc0Token; +#[cfg(feature = "adc1")] +pub use adc::Adc1 as Adc1Token; +#[cfg(feature = "gpio")] +pub use gpio::{pins::*, AnyPin, Flex, Gpio as GpioToken, Input, Level, Output}; + +/// 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). +#[allow(unused_variables)] +pub fn init(cfg: crate::config::Config) -> Peripherals { + let peripherals = Peripherals::take(); + #[cfg(feature = "ostimer0")] + { + // Apply user-configured priority early; enabling is left to examples/apps + crate::interrupt::OS_EVENT.set_priority(cfg.time_interrupt_priority); + } + #[cfg(feature = "rtc0")] + { + // Apply user-configured priority early; enabling is left to examples/apps + crate::interrupt::RTC.set_priority(cfg.rtc_interrupt_priority); + } + #[cfg(feature = "adc1")] + { + // Apply user-configured priority early; enabling is left to examples/apps + crate::interrupt::ADC1.set_priority(cfg.adc_interrupt_priority); + } + 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/src/lpuart/buffered.rs b/src/lpuart/buffered.rs new file mode 100644 index 000000000..03673d975 --- /dev/null +++ b/src/lpuart/buffered.rs @@ -0,0 +1,686 @@ +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 { + rx_waker: AtomicWaker, + rx_buf: RingBuffer, + tx_waker: AtomicWaker, + tx_buf: RingBuffer, + tx_done: AtomicBool, + initialized: AtomicBool, +} + +impl State { + /// Create a new state instance + pub const fn new() -> Self { + Self { + rx_waker: AtomicWaker::new(), + rx_buf: RingBuffer::new(), + tx_waker: AtomicWaker::new(), + tx_buf: RingBuffer::new(), + tx_done: AtomicBool::new(true), + 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>, +} + +/// Buffered LPUART RX driver +pub struct BufferedLpuartRx<'a> { + info: Info, + state: &'static State, + _rx_pin: Peri<'a, AnyPin>, +} + +// ============================================================================ +// BUFFERED LPUART IMPLEMENTATION +// ============================================================================ + +impl<'a> BufferedLpuart<'a> { + /// Create a new buffered LPUART instance + 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 { + // Configure pins + tx_pin.as_tx(); + rx_pin.as_rx(); + + // Convert pins to AnyPin + let tx_pin: Peri<'a, AnyPin> = tx_pin.into(); + let rx_pin: Peri<'a, AnyPin> = rx_pin.into(); + + let state = T::buffered_state(); + + // Initialize the peripheral + Self::init::( + Some(&tx_pin), + Some(&rx_pin), + None, + None, + tx_buffer, + rx_buffer, + config, + )?; + + Ok(Self { + tx: BufferedLpuartTx { + info: T::info(), + state, + _tx_pin: tx_pin, + }, + rx: BufferedLpuartRx { + info: T::info(), + state, + _rx_pin: rx_pin, + }, + }) + } + + /// Create a new buffered LPUART with flexible pin configuration + pub fn new_with_pins( + _inner: Peri<'a, T>, + tx_pin: Option>>, + rx_pin: Option>>, + rts_pin: Option>>, + cts_pin: Option>>, + _irq: impl interrupt::typelevel::Binding> + 'a, + tx_buffer: &'a mut [u8], + rx_buffer: &'a mut [u8], + config: Config, + ) -> Result { + // Configure pins if provided + let tx_pin = tx_pin.map(|pin| { + pin.as_tx(); + let converted: Peri<'a, AnyPin> = pin.into(); + converted + }); + + let rx_pin = rx_pin.map(|pin| { + pin.as_rx(); + let converted: Peri<'a, AnyPin> = pin.into(); + converted + }); + + let rts_pin = rts_pin.map(|pin| { + pin.as_rts(); + let converted: Peri<'a, AnyPin> = pin.into(); + converted + }); + + let cts_pin = cts_pin.map(|pin| { + pin.as_cts(); + let converted: Peri<'a, AnyPin> = pin.into(); + converted + }); + + let state = T::buffered_state(); + + // Initialize the peripheral + Self::init::( + tx_pin.as_ref(), + rx_pin.as_ref(), + rts_pin.as_ref(), + cts_pin.as_ref(), + tx_buffer, + rx_buffer, + config, + )?; + + // Create TX and RX instances + let (tx, rx) = if let (Some(tx_pin), Some(rx_pin)) = (tx_pin, rx_pin) { + ( + BufferedLpuartTx { + info: T::info(), + state, + _tx_pin: tx_pin, + }, + BufferedLpuartRx { + info: T::info(), + state, + _rx_pin: rx_pin, + }, + ) + } else { + return Err(Error::InvalidArgument); + }; + + Ok(Self { tx, rx }) + } + + fn init( + _tx: Option<&Peri<'a, AnyPin>>, + _rx: Option<&Peri<'a, AnyPin>>, + _rts: Option<&Peri<'a, AnyPin>>, + _cts: Option<&Peri<'a, AnyPin>>, + tx_buffer: &'a mut [u8], + rx_buffer: &'a mut [u8], + mut config: Config, + ) -> Result<()> { + let regs = T::info().regs; + let state = T::buffered_state(); + + // Check if already initialized + if state.initialized.load(Ordering::Relaxed) { + return Err(Error::InvalidArgument); + } + + // Initialize ring buffers + assert!(!tx_buffer.is_empty()); + unsafe { state.tx_buf.init(tx_buffer.as_mut_ptr(), tx_buffer.len()) } + + assert!(!rx_buffer.is_empty()); + unsafe { state.rx_buf.init(rx_buffer.as_mut_ptr(), rx_buffer.len()) } + + // Mark as initialized + state.initialized.store(true, Ordering::Relaxed); + + // Enable TX and RX for buffered operation + config.enable_tx = true; + config.enable_rx = true; + + // Perform standard initialization + perform_software_reset(regs); + disable_transceiver(regs); + configure_baudrate(regs, config.baudrate_bps, config.clock)?; + configure_frame_format(regs, &config); + configure_control_settings(regs, &config); + configure_fifo(regs, &config); + clear_all_status_flags(regs); + configure_flow_control(regs, &config); + configure_bit_order(regs, config.msb_firs); + + // 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, config.enable_tx, config.enable_rx); + + // Enable the interrupt + // unsafe { + // // TODO: Used the propper interrupt enable method for the specific LPUART instance + // // T::Interrupt::enable(); + // } + + Ok(()) + } + + /// 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> { + /// Create a new TX-only buffered LPUART + 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(); + let tx_pin: Peri<'a, AnyPin> = tx_pin.into(); + + let info = T::info(); + let state = T::buffered_state(); + + // Check if already initialized + if state.initialized.load(Ordering::Relaxed) { + return Err(Error::InvalidArgument); + } + + // Initialize TX ring buffer only + unsafe { + let tx_buf = &state.tx_buf as *const _ as *mut RingBuffer; + (*tx_buf).init(tx_buffer.as_mut_ptr(), tx_buffer.len()); + } + + state.initialized.store(true, Ordering::Relaxed); + + // Initialize with TX only + BufferedLpuart::init::( + Some(&tx_pin), + None, + None, + None, + tx_buffer, + &mut [], // Empty RX buffer + config, + )?; + + Ok(Self { + info, + state, + _tx_pin: tx_pin, + }) + } + + /// 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> { + /// 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(); + let rx_pin: Peri<'a, AnyPin> = rx_pin.into(); + + let info = T::info(); + let state = T::buffered_state(); + + // Check if already initialized + if state.initialized.load(Ordering::Relaxed) { + return Err(Error::InvalidArgument); + } + + // Initialize RX ring buffer only + unsafe { + let rx_buf = &state.rx_buf as *const _ as *mut RingBuffer; + (*rx_buf).init(rx_buffer.as_mut_ptr(), rx_buffer.len()); + } + + state.initialized.store(true, Ordering::Relaxed); + + // Initialize with RX only + BufferedLpuart::init::( + None, + Some(&rx_pin), + None, + None, + &mut [], // Empty TX buffer + rx_buffer, + config, + )?; + + Ok(Self { + info, + state, + _rx_pin: rx_pin, + }) + } + + /// 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() { + if 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/src/lpuart/mod.rs b/src/lpuart/mod.rs new file mode 100644 index 000000000..431547f86 --- /dev/null +++ b/src/lpuart/mod.rs @@ -0,0 +1,1208 @@ +use crate::interrupt; +use core::marker::PhantomData; +use embassy_hal_internal::{Peri, PeripheralType}; +use paste::paste; + +use crate::pac; +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; + +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 LIB --- + +// Use our own instance of Peripherals, until we align `lib.rs` with the EMBASSY-IMXRT approach +// Inlined peripherals_definition! to bypass the `Peripherals::take_with_cs()` check +// SHOULD NOT BE USED IN THE FINAL VERSION +pub mod lib { + // embassy_hal_internal::peripherals!(LPUART2, PIO2_2, PIO2_3) + + embassy_hal_internal::peripherals_definition!(LPUART2, PIO2_2, PIO2_3,); + #[doc = r" Struct containing all the peripheral singletons."] + #[doc = r""] + #[doc = r" To obtain the peripherals, you must initialize the HAL, by calling [`crate::init`]."] + #[allow(non_snake_case)] + pub struct Peripherals { + #[doc = concat!(stringify!(LPUART2)," peripheral")] + pub LPUART2: embassy_hal_internal::Peri<'static, peripherals::LPUART2>, + #[doc = concat!(stringify!(PIO2_2)," peripheral")] + pub PIO2_2: embassy_hal_internal::Peri<'static, peripherals::PIO2_2>, + #[doc = concat!(stringify!(PIO2_3)," peripheral")] + pub PIO2_3: embassy_hal_internal::Peri<'static, peripherals::PIO2_3>, + } + impl Peripherals { + #[doc = r"Returns all the peripherals *once*"] + #[inline] + pub(crate) fn take() -> Self { + critical_section::with(Self::take_with_cs) + } + #[doc = r"Returns all the peripherals *once*"] + #[inline] + pub(crate) fn take_with_cs(_cs: critical_section::CriticalSection) -> Self { + #[no_mangle] + static mut _EMBASSY_DEVICE_PERIPHERALS2: bool = false; // ALIGN: Temporary fix to use stub Peripherals + unsafe { + if _EMBASSY_DEVICE_PERIPHERALS2 { + panic!("init called more than once!") + } + _EMBASSY_DEVICE_PERIPHERALS2 = true; + Self::steal() + } + } + } + impl Peripherals { + #[doc = r" Unsafely create an instance of this peripheral out of thin air."] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r""] + #[doc = r" You must ensure that you're only using one instance of this type at a time."] + #[inline] + pub unsafe fn steal() -> Self { + Self { + LPUART2: peripherals::LPUART2::steal(), + PIO2_2: peripherals::PIO2_2::steal(), + PIO2_3: peripherals::PIO2_3::steal(), + } + } + } + + /// Initialize HAL + pub fn init() -> Peripherals { + Peripherals::take() + } +} + +// --- END LIB --- + +// --- START GPIO --- + +mod gpio { + use embassy_hal_internal::PeripheralType; + trait SealedPin {} + + #[allow(private_bounds)] + pub trait GpioPin: SealedPin + Sized + PeripheralType + Into + 'static { + /// Type-erase the pin. + fn degrade(self) -> AnyPin { + todo!() + } + } + + // Add this macro to implement GpioPin for all pins + macro_rules! impl_gpio_pin { + ($($pin:ident),*) => { + $( + impl SealedPin for super::lib::peripherals::$pin {} + + impl GpioPin for super::lib::peripherals::$pin {} + + impl Into for super::lib::peripherals::$pin { + fn into(self) -> AnyPin { + AnyPin + } + } + )* + }; + } + + // Implement GpioPin for all pins from lib.rs + impl_gpio_pin!(PIO2_2, PIO2_3); + + #[derive(Debug, Clone, Copy)] + pub struct AnyPin; + + impl PeripheralType for AnyPin {} + + pub enum Alt { + ALT3, + } +} + +use gpio::{AnyPin, GpioPin as Pin}; + +// --- END GPIO --- + +// --- START DMA --- +mod dma { + pub struct Channel<'d> { + pub(super) _lifetime: core::marker::PhantomData<&'d ()>, + } +} + +use dma::Channel; + +// --- END DMA --- + +// --- START CLOCK --- +mod clock { + #[derive(Debug, Clone, Copy)] + pub enum Clock { + FroLf, // Low-Frequency Free-Running Oscillator + } +} + +use clock::Clock; + +// --- END CLOCK --- + +// ============================================================================ +// 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 { + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_instance { + ($($n:expr),*) => { + $( + paste!{ + impl SealedInstance for lib::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 lib::peripherals::[] { + type Interrupt = crate::interrupt::typelevel::[]; + } + } + )* + }; +} + +// impl_instance!(0, 1, 2, 3, 4); +impl_instance!(2); + +// ============================================================================ +// 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: Clock) -> Result<()> { + let clock_freq = get_fc_freq(clock)?; + 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) as u8); + // 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 as u8) + .txwater() + .bits(config.tx_fifo_watermark as u8) + }); + + // 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, config: &Config) { + if config.enable_rx_rts || config.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 config.enable_rx_rts { + w = w.rxrtse().enabled(); + } else { + w = w.rxrtse().disabled(); + } + + if config.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) + 1) / 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 = if calculated_baud > baudrate { + calculated_baud - baudrate + } else { + baudrate - calculated_baud + }; + + 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)) +} + +pub fn get_fc_freq(clock: Clock) -> Result { + // This is a placeholder - actual implementation would query the clock system + // In real implementation, this would get the LPUART clock frequency + match clock { + Clock::FroLf => Ok(12_000_000), // Low frequency oscillator + #[allow(unreachable_patterns)] + _ => Err(Error::InvalidArgument), + } +} + +/// 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: Pin + 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: Pin + 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: Pin + 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: Pin + sealed::Sealed + PeripheralType { + /// convert the pin to appropriate function for Lpuart Rts usage + fn as_rts(&self); +} + +macro_rules! impl_pin_trait { + ($fcn:ident, $mode:ident, $($pin:ident, $alt:ident),*) => { + paste! { + $( + impl [<$mode:camel Pin>] for lib::peripherals::$pin { + fn [](&self) { + let _alt = gpio::Alt::$alt; + // todo!("Configure pin for LPUART function") + } + } + )* + } + }; +} + +// Document identifier: MCXA343/344 Rev. 1DraftB ReleaseCandidate, 2025-07-10 - 6.1 MCX A173, A174 Signal Multiplexing and Pin Assignments +// impl_pin_trait!(LPUART0, rx, PIO2_0, ALT2, PIO0_2, ALT2, PIO0_20, ALT3); +// impl_pin_trait!(LPUART0, tx, PIO2_1, ALT2, PIO0_3, ALT2, PIO0_21, ALT3); +// impl_pin_trait!(LPUART0, rts, PIO2_2, ALT2, PIO0_0, ALT2, PIO0_22, ALT3); +// impl_pin_trait!(LPUART0, cts, PIO2_3, ALT2, PIO0_1, ALT2, PIO0_23, ALT3); +impl_pin_trait!(LPUART2, rx, PIO2_3, ALT3); +impl_pin_trait!(LPUART2, tx, PIO2_2, ALT3); + +// ============================================================================ +// 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, +} + +/// A specialized Result type for LPUART operations +pub type Result = core::result::Result; + +// ============================================================================ +// CONFIGURATION STRUCTURES +// ============================================================================ + +/// Lpuart config +#[derive(Debug, Clone, Copy)] +pub struct Config { + /// Baud rate in bits per second + pub baudrate_bps: u32, + /// Clock + pub clock: Clock, + /// Parity configuration + pub parity_mode: Option, + /// Number of data bits + pub data_bits_count: DataBits, + /// MSB First or LSB First configuration + pub msb_firs: 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, + /// RX RTS enable + pub enable_rx_rts: bool, + /// TX CTS enable + pub enable_tx_cts: bool, + /// 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, + /// Enable transmitter + pub enable_tx: bool, + /// Enable receiver + pub enable_rx: bool, + /// Swap TXD and RXD pins + pub swap_txd_rxd: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + baudrate_bps: 115_200u32, + clock: Clock::FroLf, + parity_mode: None, + data_bits_count: DataBits::Data8, + msb_firs: MsbFirst::LsbFirst, + stop_bits_count: StopBits::One, + tx_fifo_watermark: 0, + rx_fifo_watermark: 1, + enable_rx_rts: false, + enable_tx_cts: false, + tx_cts_source: TxCtsSource::Cts, + tx_cts_config: TxCtsConfig::Start, + rx_idle_type: IdleType::FromStart, + rx_idle_config: IdleConfig::Idle1, + enable_tx: false, + enable_rx: false, + swap_txd_rxd: false, + } + } +} + +/// 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>, + _tx_dma: Option>, + mode: PhantomData<(&'a (), M)>, +} + +/// Lpuart Rx driver. +pub struct LpuartRx<'a, M: Mode> { + info: Info, + _rx_pin: Peri<'a, AnyPin>, + _rx_dma: Option>, + mode: PhantomData<(&'a (), M)>, +} + +// ============================================================================ +// LPUART CORE IMPLEMENTATION +// ============================================================================ + +impl<'a, M: Mode> Lpuart<'a, M> { + fn init( + _tx: Option<&Peri<'a, AnyPin>>, + _rx: Option<&Peri<'a, AnyPin>>, + _rts: Option<&Peri<'a, AnyPin>>, + _cts: Option<&Peri<'a, AnyPin>>, + config: Config, + ) -> Result<()> { + let regs = T::info().regs; + + // Perform initialization sequence + perform_software_reset(regs); + disable_transceiver(regs); + configure_baudrate(regs, config.baudrate_bps, config.clock)?; + configure_frame_format(regs, &config); + configure_control_settings(regs, &config); + configure_fifo(regs, &config); + clear_all_status_flags(regs); + configure_flow_control(regs, &config); + configure_bit_order(regs, config.msb_firs); + enable_transceiver(regs, config.enable_tx, config.enable_rx); + + 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 TX and RX 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(); + + // Convert pins to AnyPin + let tx_pin: Peri<'a, AnyPin> = tx_pin.into(); + let rx_pin: Peri<'a, AnyPin> = rx_pin.into(); + + // Initialize the peripheral + Self::init::(Some(&tx_pin), Some(&rx_pin), None, None, config)?; + + Ok(Self { + info: T::info(), + tx: LpuartTx::new_inner(T::info(), tx_pin, None), + rx: LpuartRx::new_inner(T::info(), rx_pin, None), + }) + } +} + +// ---------------------------------------------------------------------------- +// Blocking TX Implementation +// ---------------------------------------------------------------------------- + +impl<'a, M: Mode> LpuartTx<'a, M> { + fn new_inner(info: Info, tx_pin: Peri<'a, AnyPin>, tx_dma: Option>) -> Self { + Self { + info, + _tx_pin: tx_pin, + _tx_dma: tx_dma, + mode: PhantomData, + } + } +} + +impl<'a> LpuartTx<'a, Blocking> { + /// Create a new blocking LPUART which can only send data. + pub fn new_blocking( + _inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + config: Config, + ) -> Result { + tx_pin.as_tx(); + + let tx_pin: Peri<'a, AnyPin> = tx_pin.into(); + + Lpuart::::init::(Some(&tx_pin), None, None, None, config)?; + + Ok(Self::new_inner(T::info(), tx_pin, 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(()) + } + + /// 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>, rx_dma: Option>) -> Self { + Self { + info, + _rx_pin: rx_pin, + _rx_dma: rx_dma, + mode: PhantomData, + } + } +} + +impl<'a> LpuartRx<'a, Blocking> { + /// Create a new blocking LPUART which can only receive data. + pub fn new_blocking( + _inner: Peri<'a, T>, + rx_pin: Peri<'a, impl RxPin>, + config: Config, + ) -> Result { + rx_pin.as_rx(); + + let rx_pin: Peri<'a, AnyPin> = rx_pin.into(); + + Lpuart::::init::(None, Some(&rx_pin), None, None, config)?; + + Ok(Self::new_inner(T::info(), rx_pin, 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) + } + + /// 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/src/main.rs b/src/main.rs deleted file mode 100644 index 7111536ff..000000000 --- a/src/main.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![cfg_attr(target_os = "none", no_std)] -#![cfg_attr(target_os = "none", no_main)] - -#[cfg(target_os = "none")] -mod baremetal; - -#[cfg(not(target_os = "none"))] -fn main() { - println!("Hello, world!"); -} - -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } -} diff --git a/src/ostimer.rs b/src/ostimer.rs new file mode 100644 index 000000000..b9688313a --- /dev/null +++ b/src/ostimer.rs @@ -0,0 +1,704 @@ +//! # 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 crate::interrupt::InterruptExt; +use crate::pac; +use core::sync::atomic::{AtomicBool, Ordering}; + +// 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> 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, + /// OSTIMER clock frequency in Hz (must match the actual hardware clock) + pub clock_frequency_hz: u64, +} + +impl Default for Config { + fn default() -> Self { + Self { + init_match_max: true, + // Default to 1MHz - user should override this with actual frequency + clock_frequency_hz: 1_000_000, + } + } +} + +/// 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: impl Instance, cfg: Config, _p: &'d crate::pac::Peripherals) -> Self { + assert!( + cfg.clock_frequency_hz > 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: cfg.clock_frequency_hz, + _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 { + crate::reset::assert::(peripherals); + } + + for _ in 0..RESET_STABILIZE_SPINS { + cortex_m::asm::nop(); + } + + unsafe { + crate::reset::release::(peripherals); + } + + while !::is_released( + &peripherals.mrcc0, + ) { + 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 { + fn ptr() -> *const Regs; +} + +// Token for OSTIMER0 provided by embassy-hal-internal peripherals macro. +#[cfg(feature = "ostimer0")] +pub type Ostimer0 = crate::peripherals::OSTIMER0; + +#[cfg(feature = "ostimer0")] +impl Instance for crate::peripherals::OSTIMER0 { + #[inline(always)] + fn ptr() -> *const Regs { + pac::Ostimer0::ptr() + } +} + +// Also implement Instance for the Peri wrapper type +#[cfg(feature = "ostimer0")] +impl Instance for embassy_hal_internal::Peri<'_, 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 = "ostimer0")] +pub mod time_driver { + 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::pac; + use core::sync::atomic::Ordering; + use core::task::Waker; + use embassy_sync::waitqueue::AtomicWaker; + use embassy_time_driver as etd; + 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) { + // 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); + } + } + } + } + } + }); + } + + /// Type-level handler to be used with bind_interrupts! for OS_EVENT. + pub struct OsEventHandler; + impl crate::interrupt::typelevel::Handler + for OsEventHandler + { + unsafe fn on_interrupt() { + on_interrupt(); + } + } +} diff --git a/src/pins.rs b/src/pins.rs new file mode 100644 index 000000000..d46a3e6b3 --- /dev/null +++ b/src/pins.rs @@ -0,0 +1,127 @@ +//! Pin configuration helpers (separate from peripheral drivers). +use crate::pac; + +pub unsafe fn configure_uart2_pins_port2() { + // P2_2 = LPUART2_TX ALT3, P2_3 = LPUART2_RX ALT3 with pull-up, input enable, high drive, slow slew. + let port2 = &*pac::Port2::ptr(); + port2.pcr2().write(|w| { + w.ps() + .ps1() + .pe() + .pe1() + .sre() + .sre1() + .dse() + .dse1() + .mux() + .mux11() + .ibe() + .ibe1() + }); + port2.pcr3().write(|w| { + w.ps() + .ps1() + .pe() + .pe1() + .sre() + .sre1() + .dse() + .dse1() + .mux() + .mux11() + .ibe() + .ibe1() + }); + core::arch::asm!("dsb sy; isb sy"); +} + +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() + .mux00() + .ibe() + .ibe0() + .inv() + .inv0() + .lk() + .lk0() + + }); + core::arch::asm!("dsb sy; isb sy"); +} + +/// Configure a pin for a specific mux alternative. +/// +/// # Arguments +/// * `port` - Port number (0-4) +/// * `pin` - Pin number (varies by port: PORT0=0-7, PORT1=0-19, PORT2=0-26, PORT3=0-31, PORT4=0-7) +/// * `mux` - Mux alternative (0-15, where 0 = GPIO, 1-15 = other functions) +pub unsafe fn set_pin_mux(port: u8, pin: u8, mux: u8) { + // Validate mux value (0-15) + if mux > 15 { + panic!("Invalid mux value: {}, must be 0-15", mux); + } + + // Validate pin number based on port + let max_pin = match port { + 0 => 7, // PORT0: pins 0-7 + 1 => 19, // PORT1: pins 0-19 + 2 => 26, // PORT2: pins 0-26 + 3 => 31, // PORT3: pins 0-31 + 4 => 7, // PORT4: pins 0-7 + _ => panic!("Unsupported GPIO port: {}", port), + }; + + if pin > max_pin { + panic!( + "Invalid pin {} for PORT{}, max pin is {}", + pin, port, max_pin + ); + } + + // Get the base address for the port + let port_base: *mut u32 = match port { + 0 => pac::Port0::ptr() as *mut u32, + 1 => pac::Port1::ptr() as *mut u32, + 2 => pac::Port2::ptr() as *mut u32, + 3 => pac::Port3::ptr() as *mut u32, + 4 => pac::Port4::ptr() as *mut u32, + _ => panic!("Unsupported GPIO port: {}", port), + }; + + // PCR registers are 4 bytes apart, starting at offset 0 for PCR0 + let pcr_addr = port_base.add(pin as usize); + + // Read current PCR value + let current_val = pcr_addr.read_volatile(); + + // Clear mux bits (bits 8-11) and set new mux value + let new_val = (current_val & !(0xF << 8)) | ((mux as u32) << 8); + + // Write back the new value + pcr_addr.write_volatile(new_val); + + core::arch::asm!("dsb sy; isb sy"); +} + +/// Configure a pin for GPIO mode (ALT0). +/// This is a convenience function that calls set_pin_mux with mux=0. +/// +/// # Arguments +/// * `port` - Port number (0-4) +/// * `pin` - Pin number (varies by port: PORT0=0-7, PORT1=0-19, PORT2=0-26, PORT3=0-31, PORT4=0-7) +pub unsafe fn set_pin_mux_gpio(port: u8, pin: u8) { + set_pin_mux(port, pin, 0); +} diff --git a/src/reset.rs b/src/reset.rs new file mode 100644 index 000000000..1c131d1cc --- /dev/null +++ b/src/reset.rs @@ -0,0 +1,112 @@ +//! Reset control helpers built on PAC field writers. +use crate::pac; + +/// Trait describing a reset line that can be asserted/deasserted. +pub trait ResetLine { + /// Drive the peripheral out of reset. + unsafe fn release(mrcc: &pac::mrcc0::RegisterBlock); + + /// Drive the peripheral into reset. + unsafe fn assert(mrcc: &pac::mrcc0::RegisterBlock); + + /// Check whether the peripheral is currently released. + fn is_released(mrcc: &pac::mrcc0::RegisterBlock) -> bool; +} + +/// Release a reset line for the given peripheral set. +#[inline] +pub unsafe fn release(peripherals: &pac::Peripherals) { + R::release(&peripherals.mrcc0); +} + +/// Assert a reset line for the given peripheral set. +#[inline] +pub unsafe fn assert(peripherals: &pac::Peripherals) { + R::assert(&peripherals.mrcc0); +} + +/// Pulse a reset line (assert then release) with a short delay. +#[inline] +pub unsafe fn pulse(peripherals: &pac::Peripherals) { + let mrcc = &peripherals.mrcc0; + R::assert(mrcc); + cortex_m::asm::nop(); + cortex_m::asm::nop(); + R::release(mrcc); +} + +macro_rules! impl_reset_line { + ($name:ident, $reg:ident, $field:ident) => { + pub struct $name; + + impl ResetLine for $name { + #[inline] + unsafe fn release(mrcc: &pac::mrcc0::RegisterBlock) { + mrcc.$reg().modify(|_, w| w.$field().enabled()); + } + + #[inline] + unsafe fn assert(mrcc: &pac::mrcc0::RegisterBlock) { + mrcc.$reg().modify(|_, w| w.$field().disabled()); + } + + #[inline] + fn is_released(mrcc: &pac::mrcc0::RegisterBlock) -> bool { + mrcc.$reg().read().$field().is_enabled() + } + } + }; +} + +pub mod line { + use super::*; + + impl_reset_line!(Port2, mrcc_glb_rst1, port2); + impl_reset_line!(Port3, mrcc_glb_rst1, port3); + impl_reset_line!(Gpio3, mrcc_glb_rst2, gpio3); + impl_reset_line!(Lpuart2, mrcc_glb_rst0, lpuart2); + impl_reset_line!(Ostimer0, mrcc_glb_rst1, ostimer0); + impl_reset_line!(Port1, mrcc_glb_rst1, port1); + impl_reset_line!(Adc1, mrcc_glb_rst1, adc1); +} + +#[inline] +pub unsafe fn release_reset_port2(peripherals: &pac::Peripherals) { + release::(peripherals); +} + +#[inline] +pub unsafe fn release_reset_port3(peripherals: &pac::Peripherals) { + release::(peripherals); +} + +#[inline] +pub unsafe fn release_reset_gpio3(peripherals: &pac::Peripherals) { + release::(peripherals); +} + +#[inline] +pub unsafe fn release_reset_lpuart2(peripherals: &pac::Peripherals) { + release::(peripherals); +} + +#[inline] +pub unsafe fn release_reset_ostimer0(peripherals: &pac::Peripherals) { + release::(peripherals); +} + +/// Convenience shim retained for existing call sites. +#[inline] +pub unsafe fn reset_ostimer0(peripherals: &pac::Peripherals) { + pulse::(peripherals); +} + +#[inline] +pub unsafe fn release_reset_port1(peripherals: &pac::Peripherals) { + release::(peripherals); +} + +#[inline] +pub unsafe fn release_reset_adc1(peripherals: &pac::Peripherals) { + release::(peripherals); +} diff --git a/src/rtc.rs b/src/rtc.rs new file mode 100644 index 000000000..f83baab5e --- /dev/null +++ b/src/rtc.rs @@ -0,0 +1,281 @@ +//! RTC DateTime driver. +use crate::pac; +use crate::pac::rtc0::cr::{Um}; +use core::sync::atomic::{AtomicBool, Ordering}; + +type Regs = pac::rtc0::RegisterBlock; + +static ALARM_TRIGGERED: AtomicBool = AtomicBool::new(false); + +// Token-based instance pattern like embassy-imxrt +pub trait Instance { + fn ptr() -> *const Regs; +} + +/// Token for RTC0 +#[cfg(feature = "rtc0")] +pub type Rtc0 = crate::peripherals::RTC0; +#[cfg(feature = "rtc0")] +impl Instance for crate::peripherals::RTC0 { + #[inline(always)] + fn ptr() -> *const Regs { + pac::Rtc0::ptr() + } +} + +// Also implement Instance for the Peri wrapper type +#[cfg(feature = "rtc0")] +impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::RTC0> { + #[inline(always)] + fn ptr() -> *const Regs { + pac::Rtc0::ptr() + } +} + +const DAYS_IN_A_YEAR: u32 = 365; +const SECONDS_IN_A_DAY: u32 = 86400; +const SECONDS_IN_A_HOUR: u32 = 3600; +const SECONDS_IN_A_MINUTE: u32 = 60; +const YEAR_RANGE_START: u16 = 1970; + +#[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, +} + +#[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; +} + +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 +} + + +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 % 4 == 0 { + DAYS_IN_A_YEAR + 1 + } else { + DAYS_IN_A_YEAR + }; + } + + let mut days_per_month = [0u8, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + if year % 4 == 0 { + days_per_month[2] = 29; + } + + let mut month = 1; + for m in 1..=12 { + if days <= days_per_month[m] as u32 { + month = m; + break; + } else { + days -= days_per_month[m] as u32; + } + } + + let day = days as u8; + + RtcDateTime { + year, + month: month as u8, + day, + hour, + minute, + second, + } +} + +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 { + _inst: core::marker::PhantomData, +} + +impl Rtc { + /// initialize RTC + pub fn new(_inst: impl Instance, config: RtcConfig) -> Self { + let rtc = unsafe { &*I::ptr() }; + + /* 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) + }); + + Self { _inst: core::marker::PhantomData } + } + + 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) }); + } + + pub fn get_datetime(&self) -> RtcDateTime { + let rtc = unsafe { &*I::ptr() }; + let seconds = rtc.tsr().read().bits(); + convert_seconds_to_datetime(seconds) + } + + 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; + } + } + + pub fn get_alarm(&self) -> RtcDateTime{ + let rtc = unsafe { &*I::ptr() }; + let alarm_seconds = rtc.tar().read().bits(); + convert_seconds_to_datetime(alarm_seconds) + } + + pub fn start(&self) { + let rtc = unsafe { &*I::ptr() }; + rtc.sr().modify(|_, w| w.tce().set_bit()); + } + + pub fn stop(&self) { + let rtc = unsafe { &*I::ptr() }; + rtc.sr().modify(|_, w| w.tce().clear_bit()); + } + + 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()); + } + + ALARM_TRIGGERED.store(false, Ordering::SeqCst); + } + + 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()); + } + } + + pub fn clear_alarm_flag(&self) { + let rtc = unsafe { &*I::ptr() }; + rtc.ier().modify(|_, w| w.taie().clear_bit()); + } + + pub fn is_alarm_triggered(&self) -> bool { + ALARM_TRIGGERED.load(Ordering::Relaxed) + } +} + +pub fn on_interrupt() { + let rtc = unsafe { &*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()); + ALARM_TRIGGERED.store(true, Ordering::SeqCst); + } +} + +pub struct RtcHandler; +impl crate::interrupt::typelevel::Handler for RtcHandler { + unsafe fn on_interrupt() { on_interrupt(); } +} \ No newline at end of file diff --git a/src/uart.rs b/src/uart.rs new file mode 100644 index 000000000..45b6b2be3 --- /dev/null +++ b/src/uart.rs @@ -0,0 +1,308 @@ +//! Minimal polling UART2 bring-up replicating MCUXpresso hello_world ordering. +//! WARNING: This is a narrow implementation only for debug console (115200 8N1). + +use crate::pac; +use core::cell::RefCell; +use cortex_m::interrupt::Mutex; +use embassy_sync::signal::Signal; + +// svd2rust defines the shared LPUART RegisterBlock under lpuart0; all instances reuse it. +type Regs = pac::lpuart0::RegisterBlock; + +// Token-based instance pattern like embassy-imxrt +pub trait Instance { + fn ptr() -> *const Regs; +} + +/// Token for LPUART2 provided by embassy-hal-internal peripherals macro. +#[cfg(feature = "lpuart2")] +pub type Lpuart2 = crate::peripherals::LPUART2; +#[cfg(feature = "lpuart2")] +impl Instance for crate::peripherals::LPUART2 { + #[inline(always)] + fn ptr() -> *const Regs { + pac::Lpuart2::ptr() + } +} + +// Also implement Instance for the Peri wrapper type +#[cfg(feature = "lpuart2")] +impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::LPUART2> { + #[inline(always)] + fn ptr() -> *const Regs { + pac::Lpuart2::ptr() + } +} + +/// UART configuration (explicit src_hz; no hardcoded frequencies) +#[derive(Copy, Clone)] +pub struct Config { + pub src_hz: u32, + pub baud: u32, + pub parity: Parity, + pub stop_bits: StopBits, +} + +#[derive(Copy, Clone)] +pub enum Parity { + None, + Even, + Odd, +} +#[derive(Copy, Clone)] +pub enum StopBits { + One, + Two, +} + +impl Config { + pub fn new(src_hz: u32) -> Self { + Self { + src_hz, + baud: 115_200, + parity: Parity::None, + stop_bits: StopBits::One, + } + } +} + +/// Compute a valid (OSR, SBR) tuple for given source clock and baud. +/// Uses a functional fold approach to find the best OSR/SBR combination +/// with minimal baud rate error. +fn compute_osr_sbr(src_hz: u32, baud: u32) -> (u8, u16) { + let (best_osr, best_sbr, _best_err) = (8u32..=32).fold( + (16u8, 4u16, u32::MAX), // (best_osr, best_sbr, best_err) + |(best_osr, best_sbr, best_err), osr| { + let denom = baud.saturating_mul(osr); + if denom == 0 { + return (best_osr, best_sbr, best_err); + } + + let sbr = (src_hz + denom / 2) / denom; // round + if sbr == 0 || sbr > 0x1FFF { + return (best_osr, best_sbr, best_err); + } + + let actual = src_hz / (osr * sbr); + let err = actual.abs_diff(baud); + + // Update best if this is better, or same error but higher OSR + if err < best_err || (err == best_err && osr as u8 > best_osr) { + (osr as u8, sbr as u16, err) + } else { + (best_osr, best_sbr, best_err) + } + }, + ); + (best_osr, best_sbr) +} + +/// Minimal UART handle for a specific instance I (store the zero-sized token like embassy) +pub struct Uart { + _inst: core::marker::PhantomData, +} + +impl Uart { + /// Create and initialize LPUART (reset + config). Clocks and pins must be prepared by the caller. + pub fn new(_inst: impl Instance, cfg: Config) -> Self { + let l = unsafe { &*I::ptr() }; + // 1) software reset pulse + l.global().write(|w| w.rst().reset()); + cortex_m::asm::delay(3); // Short delay for reset to take effect + l.global().write(|w| w.rst().no_effect()); + cortex_m::asm::delay(10); // Allow peripheral to stabilize after reset + // 2) BAUD + let (osr, sbr) = compute_osr_sbr(cfg.src_hz, cfg.baud); + l.baud().modify(|_, w| { + let w = match cfg.stop_bits { + StopBits::One => w.sbns().one(), + StopBits::Two => w.sbns().two(), + }; + // OSR field encodes (osr-1); use raw bits to avoid a long match on all variants + let raw_osr = osr.saturating_sub(1) as u8; + unsafe { w.osr().bits(raw_osr).sbr().bits(sbr) } + }); + // 3) CTRL baseline and parity + l.ctrl().write(|w| { + let w = w.ilt().from_stop().idlecfg().idle_2(); + let w = match cfg.parity { + Parity::None => w.pe().disabled(), + Parity::Even => w.pe().enabled().pt().even(), + Parity::Odd => w.pe().enabled().pt().odd(), + }; + w.re().enabled().te().enabled().rie().disabled() + }); + // 4) FIFOs and WATER: keep it simple for polling; disable FIFOs and set RX watermark to 0 + l.fifo().modify(|_, w| { + w.txfe() + .disabled() + .rxfe() + .disabled() + .txflush() + .txfifo_rst() + .rxflush() + .rxfifo_rst() + }); + l.water() + .modify(|_, w| unsafe { w.txwater().bits(0).rxwater().bits(0) }); + Self { _inst: core::marker::PhantomData } + } + + /// Enable RX interrupts. The caller must ensure an appropriate IRQ handler is installed. + pub unsafe fn enable_rx_interrupts(&self) { + let l = &*I::ptr(); + l.ctrl().modify(|_, w| w.rie().enabled()); + } + + #[inline(never)] + pub fn write_byte(&self, b: u8) { + let l = unsafe { &*I::ptr() }; + // Timeout after ~10ms at 12MHz (assuming 115200 baud, should be plenty) + const DATA_OFFSET: usize = 0x1C; // DATA register offset inside LPUART block + let data_ptr = unsafe { (I::ptr() as *mut u8).add(DATA_OFFSET) }; + for _ in 0..120000 { + if l.water().read().txcount().bits() == 0 { + unsafe { core::ptr::write_volatile(data_ptr, b) }; + return; + } + } + // If timeout, skip the write to avoid hanging + } + + #[inline(never)] + pub fn write_str_blocking(&self, s: &str) { + for &b in s.as_bytes() { + if b == b'\n' { + self.write_byte(b'\r'); + } + self.write_byte(b); + } + } + pub fn read_byte_blocking(&self) -> u8 { + let l = unsafe { &*I::ptr() }; + while !l.stat().read().rdrf().is_rxdata() {} + (l.data().read().bits() & 0xFF) as u8 + } +} + +// Simple ring buffer for UART RX data +const RX_BUFFER_SIZE: usize = 256; +pub struct RingBuffer { + buffer: [u8; RX_BUFFER_SIZE], + read_idx: usize, + write_idx: usize, + count: usize, +} + +impl RingBuffer { + pub const fn new() -> Self { + Self { + buffer: [0; RX_BUFFER_SIZE], + read_idx: 0, + write_idx: 0, + count: 0, + } + } + + pub fn push(&mut self, data: u8) -> bool { + if self.count >= RX_BUFFER_SIZE { + return false; // Buffer full + } + self.buffer[self.write_idx] = data; + self.write_idx = (self.write_idx + 1) % RX_BUFFER_SIZE; + self.count += 1; + true + } + + pub fn pop(&mut self) -> Option { + if self.count == 0 { + return None; + } + let data = self.buffer[self.read_idx]; + self.read_idx = (self.read_idx + 1) % RX_BUFFER_SIZE; + self.count -= 1; + Some(data) + } + + pub fn is_empty(&self) -> bool { + self.count == 0 + } + + pub fn len(&self) -> usize { + self.count + } +} + +// Global RX buffer shared between interrupt handler and UART instance +static RX_BUFFER: Mutex> = Mutex::new(RefCell::new(RingBuffer::new())); +static RX_SIGNAL: Signal = + Signal::new(); + +// Debug counter for interrupt handler calls +static mut INTERRUPT_COUNT: u32 = 0; + +impl Uart { + /// Read a byte asynchronously using interrupts + pub async fn read_byte_async(&self) -> u8 { + loop { + // Check if we have data in the buffer + let byte = cortex_m::interrupt::free(|cs| { + let mut buffer = RX_BUFFER.borrow(cs).borrow_mut(); + buffer.pop() + }); + + if let Some(byte) = byte { + return byte; + } + + // Wait for the interrupt signal + RX_SIGNAL.wait().await; + } + } + + /// Check if there's data available in the RX buffer + pub fn rx_data_available(&self) -> bool { + cortex_m::interrupt::free(|cs| { + let buffer = RX_BUFFER.borrow(cs).borrow(); + !buffer.is_empty() + }) + } + + /// Try to read a byte from RX buffer (non-blocking) + pub fn try_read_byte(&self) -> Option { + cortex_m::interrupt::free(|cs| { + let mut buffer = RX_BUFFER.borrow(cs).borrow_mut(); + buffer.pop() + }) + } +} + +/// Type-level handler for LPUART2 interrupts, compatible with bind_interrupts!. +pub struct UartInterruptHandler; + +impl crate::interrupt::typelevel::Handler + for UartInterruptHandler +{ + unsafe fn on_interrupt() { + INTERRUPT_COUNT += 1; + + let lpuart = &*pac::Lpuart2::ptr(); + + // Check if we have RX data + if lpuart.stat().read().rdrf().is_rxdata() { + // Read the data byte + let data = (lpuart.data().read().bits() & 0xFF) as u8; + + // Store in ring buffer + cortex_m::interrupt::free(|cs| { + let mut buffer = RX_BUFFER.borrow(cs).borrow_mut(); + if buffer.push(data) { + // Data added successfully, signal waiting tasks + RX_SIGNAL.signal(()); + } + }); + } + // Always clear any error flags that might cause spurious interrupts + let _ = lpuart.stat().read(); + } +} -- cgit From d61f212b3378f2fd4fbf45dbcd4529ad6a2bfc43 Mon Sep 17 00:00:00 2001 From: Bogdan Petru Chircu Mare Date: Thu, 6 Nov 2025 12:57:06 -0800 Subject: feat(mcxa276): use external mcxa-pac; remove in-tree PAC; ADC: stop writing CMDL1.CTYPE (read-only) --- src/adc.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/adc.rs b/src/adc.rs index dd6530605..f56d744c6 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -1,4 +1,4 @@ -//! ADC driver +//! ADC driver use crate::pac; use core::sync::atomic::{AtomicBool, Ordering}; @@ -285,8 +285,7 @@ impl Adc { 1 => { adc.cmdl1().write(|w| { w.adch().variant(config.channel_number) - .ctype().variant(config.sample_channel_mode) - .mode().variant(config.conversion_resolution_mode) + .mode().variant(config.conversion_resolution_mode) }); adc.cmdh1().write(|w| unsafe { w.next().variant(config.chained_next_command_number) @@ -373,4 +372,4 @@ pub fn on_interrupt() { pub struct AdcHandler; impl crate::interrupt::typelevel::Handler for AdcHandler { unsafe fn on_interrupt() { on_interrupt(); } -} \ No newline at end of file +} -- cgit From cb2ac2790f4b037056f9571abeb4d62360199426 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Fri, 7 Nov 2025 10:06:55 -0800 Subject: Reduce number of features We don't need features for drivers that always exist. Signed-off-by: Felipe Balbi --- src/adc.rs | 155 ++++++++++++++++++++++++++++--------------------- src/clocks.rs | 9 ++- src/gpio.rs | 2 +- src/interrupt.rs | 2 +- src/lib.rs | 59 ++++++------------- src/lpuart/buffered.rs | 2 +- src/lpuart/mod.rs | 2 +- src/ostimer.rs | 8 +-- src/pins.rs | 1 - src/rtc.rs | 28 ++++----- src/uart.rs | 9 ++- 11 files changed, 136 insertions(+), 141 deletions(-) (limited to 'src') diff --git a/src/adc.rs b/src/adc.rs index f56d744c6..5625330e9 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -1,12 +1,12 @@ -//! ADC driver +//! ADC driver use crate::pac; use core::sync::atomic::{AtomicBool, Ordering}; -use crate::pac::adc1::ctrl::{CalAvgs}; -use crate::pac::adc1::cfg::{Refsel, Pwrsel, Tprictrl, Tres, Tcmdres, HptExdi}; -use crate::pac::adc1::tctrl::{Tcmd, Tpri}; +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::cmdh1::{Next, Avgs, Sts, Cmpen}; +use crate::pac::adc1::ctrl::CalAvgs; +use crate::pac::adc1::tctrl::{Tcmd, Tpri}; type Regs = pac::adc1::RegisterBlock; @@ -17,9 +17,7 @@ pub trait Instance { } /// Token for ADC1 -#[cfg(feature = "adc1")] pub type Adc1 = crate::peripherals::ADC1; -#[cfg(feature = "adc1")] impl Instance for crate::peripherals::ADC1 { #[inline(always)] fn ptr() -> *const Regs { @@ -28,7 +26,6 @@ impl Instance for crate::peripherals::ADC1 { } // Also implement Instance for the Peri wrapper type -#[cfg(feature = "adc1")] impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::ADC1> { #[inline(always)] fn ptr() -> *const Regs { @@ -97,16 +94,15 @@ pub struct ConvResult { pub conv_value: u16, } - pub struct Adc { _inst: core::marker::PhantomData, } impl Adc { /// initialize ADC - pub fn new(_inst: impl Instance, config : LpadcConfig) -> Self { + pub fn new(_inst: impl Instance, config: LpadcConfig) -> Self { let adc = unsafe { &*I::ptr() }; - + /* Reset the module. */ adc.ctrl().modify(|_, w| w.rst().held_in_reset()); adc.ctrl().modify(|_, w| w.rst().released_from_reset()); @@ -124,64 +120,81 @@ impl Adc { } /* Set calibration average mode. */ - adc.ctrl().modify(|_, w| w.cal_avgs().variant(config.conversion_average_mode)); - - adc.cfg().write(|w| unsafe { + 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, - }) - }); + 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) + 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) }); + 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 } + Self { + _inst: core::marker::PhantomData, + } } pub fn deinit(&self) { @@ -189,7 +202,6 @@ impl Adc { adc.ctrl().modify(|_, w| w.adcen().disabled()); } - pub fn get_default_config() -> LpadcConfig { LpadcConfig { enable_in_doze_mode: true, @@ -208,13 +220,14 @@ impl Adc { pub fn do_offset_calibration(&self) { let adc = unsafe { &*I::ptr() }; // Enable calibration mode - adc.ctrl().modify(|_, w| w.calofs().offset_calibration_request_pending()); + 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{ + pub fn get_gain_conv_result(&self, mut gain_adjustment: f32) -> u32 { let mut gcra_array = [0u32; 17]; let mut gcalr: u32 = 0; @@ -234,20 +247,21 @@ impl Adc { pub fn do_auto_calibration(&self) { let adc = unsafe { &*I::ptr() }; - adc.ctrl().modify(|_, w| w.cal_req().calibration_request_pending()); + 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 >> 0) + 1) >> 1) != 0 { - gcca |= !0xFFFF; + 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() + .write(|w| unsafe { w.bits(self.get_gain_conv_result(gcra)) }); adc.gcr0().modify(|_, w| w.rdy().set_bit()); @@ -284,15 +298,22 @@ impl Adc { match index { 1 => { adc.cmdl1().write(|w| { - w.adch().variant(config.channel_number) - .mode().variant(config.conversion_resolution_mode) + 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); + 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(); } @@ -371,5 +392,7 @@ pub fn on_interrupt() { pub struct AdcHandler; impl crate::interrupt::typelevel::Handler for AdcHandler { - unsafe fn on_interrupt() { on_interrupt(); } + unsafe fn on_interrupt() { + on_interrupt(); + } } diff --git a/src/clocks.rs b/src/clocks.rs index 5336c3efe..95d7ad567 100644 --- a/src/clocks.rs +++ b/src/clocks.rs @@ -112,10 +112,10 @@ pub unsafe fn init_fro16k(peripherals: &pac::Peripherals) { let vbat = &peripherals.vbat0; // Enable FRO16K oscillator vbat.froctla().modify(|_, w| w.fro_en().set_bit()); - + // Lock the control register vbat.frolcka().modify(|_, w| w.lock().set_bit()); - + // 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 @@ -130,7 +130,6 @@ pub unsafe fn enable_adc(peripherals: &pac::Peripherals) { pub unsafe fn select_adc_clock(peripherals: &pac::Peripherals) { // Use FRO_LF_DIV (already running) MUX=0 DIV=0 let mrcc = &peripherals.mrcc0; - mrcc.mrcc_adc_clksel() - .write(|w| w.mux().clkroot_func_0()); + mrcc.mrcc_adc_clksel().write(|w| w.mux().clkroot_func_0()); mrcc.mrcc_adc_clkdiv().write(|w| unsafe { w.bits(0) }); -} \ No newline at end of file +} diff --git a/src/gpio.rs b/src/gpio.rs index 08f375cba..faeefd333 100644 --- a/src/gpio.rs +++ b/src/gpio.rs @@ -66,7 +66,7 @@ pub trait PinId { } pub mod pins { - use super::{pac, AnyPin, PinId}; + use super::{AnyPin, PinId, pac}; macro_rules! define_pin { ($Name:ident, $port:literal, $pin:literal, $GpioBlk:ident) => { diff --git a/src/interrupt.rs b/src/interrupt.rs index 8226f01ab..d91e6479a 100644 --- a/src/interrupt.rs +++ b/src/interrupt.rs @@ -6,8 +6,8 @@ mod generated { embassy_hal_internal::interrupt_mod!(OS_EVENT, LPUART2, RTC, ADC1); } -pub use generated::interrupt::typelevel; pub use generated::interrupt::Priority; +pub use generated::interrupt::typelevel; use crate::pac::Interrupt; use core::sync::atomic::{AtomicU16, AtomicU32, Ordering}; diff --git a/src/lib.rs b/src/lib.rs index eb4727106..518fe01d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,31 +1,19 @@ #![no_std] pub mod clocks; // still provide clock helpers -#[cfg(feature = "gpio")] pub mod gpio; pub mod pins; // pin mux helpers pub mod reset; // reset control helpers +pub mod adc; pub mod config; pub mod interrupt; -pub mod ostimer; -pub mod uart; pub mod lpuart; +pub mod ostimer; pub mod rtc; -pub mod adc; +pub mod uart; -embassy_hal_internal::peripherals!( - #[cfg(feature = "lpuart2")] - LPUART2, - #[cfg(feature = "ostimer0")] - OSTIMER0, - #[cfg(feature = "gpio")] - GPIO, - #[cfg(feature = "rtc0")] - RTC0, - #[cfg(feature = "adc1")] - ADC1, -); +embassy_hal_internal::peripherals!(LPUART2, OSTIMER0, GPIO, RTC0, ADC1,); /// Get access to the PAC Peripherals for low-level register access. /// This is a lazy-initialized singleton that can be called after init(). @@ -42,43 +30,32 @@ pub fn pac() -> &'static pac::Peripherals { } } -pub use cortex_m_rt; -pub use mcxa276_pac as pac; +#[cfg(feature = "unstable-pac")] +pub use mcxa_pac as pac; +#[cfg(not(feature = "unstable-pac"))] +pub(crate) use mcxa_pac as pac; + // 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, pins::*}; pub use interrupt::InterruptExt; -#[cfg(feature = "ostimer0")] pub use ostimer::Ostimer0 as Ostimer0Token; -#[cfg(feature = "lpuart2")] -pub use uart::Lpuart2 as Uart2Token; -#[cfg(feature = "rtc0")] pub use rtc::Rtc0 as Rtc0Token; -#[cfg(feature = "adc1")] -pub use adc::Adc1 as Adc1Token; -#[cfg(feature = "gpio")] -pub use gpio::{pins::*, AnyPin, Flex, Gpio as GpioToken, Input, Level, Output}; +pub use uart::Lpuart2 as Uart2Token; /// 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). #[allow(unused_variables)] pub fn init(cfg: crate::config::Config) -> Peripherals { let peripherals = Peripherals::take(); - #[cfg(feature = "ostimer0")] - { - // Apply user-configured priority early; enabling is left to examples/apps - crate::interrupt::OS_EVENT.set_priority(cfg.time_interrupt_priority); - } - #[cfg(feature = "rtc0")] - { - // Apply user-configured priority early; enabling is left to examples/apps - crate::interrupt::RTC.set_priority(cfg.rtc_interrupt_priority); - } - #[cfg(feature = "adc1")] - { - // 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::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); peripherals } diff --git a/src/lpuart/buffered.rs b/src/lpuart/buffered.rs index 03673d975..e2382e86d 100644 --- a/src/lpuart/buffered.rs +++ b/src/lpuart/buffered.rs @@ -3,8 +3,8 @@ 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_hal_internal::atomic_ring_buffer::RingBuffer; use embassy_sync::waitqueue::AtomicWaker; use super::*; diff --git a/src/lpuart/mod.rs b/src/lpuart/mod.rs index 431547f86..99f4a4a66 100644 --- a/src/lpuart/mod.rs +++ b/src/lpuart/mod.rs @@ -6,7 +6,7 @@ use paste::paste; use crate::pac; 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, + Idlecfg as IdleConfig, Ilt as IdleType, M as DataBits, Pt as Parity, }; use crate::pac::lpuart0::modir::{Txctsc as TxCtsConfig, Txctssrc as TxCtsSource}; use crate::pac::lpuart0::stat::Msbf as MsbFirst; diff --git a/src/ostimer.rs b/src/ostimer.rs index b9688313a..6a4188db0 100644 --- a/src/ostimer.rs +++ b/src/ostimer.rs @@ -493,10 +493,8 @@ pub trait Instance { } // Token for OSTIMER0 provided by embassy-hal-internal peripherals macro. -#[cfg(feature = "ostimer0")] pub type Ostimer0 = crate::peripherals::OSTIMER0; -#[cfg(feature = "ostimer0")] impl Instance for crate::peripherals::OSTIMER0 { #[inline(always)] fn ptr() -> *const Regs { @@ -505,7 +503,6 @@ impl Instance for crate::peripherals::OSTIMER0 { } // Also implement Instance for the Peri wrapper type -#[cfg(feature = "ostimer0")] impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::OSTIMER0> { #[inline(always)] fn ptr() -> *const Regs { @@ -528,11 +525,10 @@ fn gray_to_bin(gray: u64) -> u64 { bin } -#[cfg(feature = "ostimer0")] pub mod time_driver { 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, + ALARM_ACTIVE, ALARM_CALLBACK, ALARM_FLAG, ALARM_TARGET_TIME, EVTIMER_HI_MASK, + EVTIMER_HI_SHIFT, LOW_32_BIT_MASK, Regs, bin_to_gray, now_ticks_read, }; use crate::pac; use core::sync::atomic::Ordering; diff --git a/src/pins.rs b/src/pins.rs index d46a3e6b3..1d92f9fef 100644 --- a/src/pins.rs +++ b/src/pins.rs @@ -57,7 +57,6 @@ pub unsafe fn configure_adc_pins() { .inv0() .lk() .lk0() - }); core::arch::asm!("dsb sy; isb sy"); } diff --git a/src/rtc.rs b/src/rtc.rs index f83baab5e..5e3dfe6c1 100644 --- a/src/rtc.rs +++ b/src/rtc.rs @@ -1,6 +1,6 @@ //! RTC DateTime driver. use crate::pac; -use crate::pac::rtc0::cr::{Um}; +use crate::pac::rtc0::cr::Um; use core::sync::atomic::{AtomicBool, Ordering}; type Regs = pac::rtc0::RegisterBlock; @@ -13,9 +13,7 @@ pub trait Instance { } /// Token for RTC0 -#[cfg(feature = "rtc0")] pub type Rtc0 = crate::peripherals::RTC0; -#[cfg(feature = "rtc0")] impl Instance for crate::peripherals::RTC0 { #[inline(always)] fn ptr() -> *const Regs { @@ -24,7 +22,6 @@ impl Instance for crate::peripherals::RTC0 { } // Also implement Instance for the Peri wrapper type -#[cfg(feature = "rtc0")] impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::RTC0> { #[inline(always)] fn ptr() -> *const Regs { @@ -87,7 +84,6 @@ pub fn convert_datetime_to_seconds(datetime: &RtcDateTime) -> u32 { seconds } - pub fn convert_seconds_to_datetime(seconds: u32) -> RtcDateTime { let mut seconds_remaining = seconds; let mut days = seconds_remaining / SECONDS_IN_A_DAY + 1; @@ -166,11 +162,15 @@ impl Rtc { 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) + w.cir() + .bits(config.compensation_interval) + .tcr() + .bits(config.compensation_time) }); - Self { _inst: core::marker::PhantomData } + Self { + _inst: core::marker::PhantomData, + } } pub fn set_datetime(&self, datetime: RtcDateTime) { @@ -178,7 +178,7 @@ impl Rtc { let seconds = convert_datetime_to_seconds(&datetime); rtc.tsr().write(|w| unsafe { w.bits(seconds) }); } - + pub fn get_datetime(&self) -> RtcDateTime { let rtc = unsafe { &*I::ptr() }; let seconds = rtc.tsr().read().bits(); @@ -203,7 +203,7 @@ impl Rtc { } } - pub fn get_alarm(&self) -> RtcDateTime{ + pub fn get_alarm(&self) -> RtcDateTime { let rtc = unsafe { &*I::ptr() }; let alarm_seconds = rtc.tar().read().bits(); convert_seconds_to_datetime(alarm_seconds) @@ -264,7 +264,7 @@ impl Rtc { ALARM_TRIGGERED.load(Ordering::Relaxed) } } - + pub fn on_interrupt() { let rtc = unsafe { &*pac::Rtc0::ptr() }; // Check if this is actually a time alarm interrupt @@ -277,5 +277,7 @@ pub fn on_interrupt() { pub struct RtcHandler; impl crate::interrupt::typelevel::Handler for RtcHandler { - unsafe fn on_interrupt() { on_interrupt(); } -} \ No newline at end of file + unsafe fn on_interrupt() { + on_interrupt(); + } +} diff --git a/src/uart.rs b/src/uart.rs index 45b6b2be3..65dd91492 100644 --- a/src/uart.rs +++ b/src/uart.rs @@ -15,9 +15,7 @@ pub trait Instance { } /// Token for LPUART2 provided by embassy-hal-internal peripherals macro. -#[cfg(feature = "lpuart2")] pub type Lpuart2 = crate::peripherals::LPUART2; -#[cfg(feature = "lpuart2")] impl Instance for crate::peripherals::LPUART2 { #[inline(always)] fn ptr() -> *const Regs { @@ -26,7 +24,6 @@ impl Instance for crate::peripherals::LPUART2 { } // Also implement Instance for the Peri wrapper type -#[cfg(feature = "lpuart2")] impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::LPUART2> { #[inline(always)] fn ptr() -> *const Regs { @@ -111,7 +108,7 @@ impl Uart { cortex_m::asm::delay(3); // Short delay for reset to take effect l.global().write(|w| w.rst().no_effect()); cortex_m::asm::delay(10); // Allow peripheral to stabilize after reset - // 2) BAUD + // 2) BAUD let (osr, sbr) = compute_osr_sbr(cfg.src_hz, cfg.baud); l.baud().modify(|_, w| { let w = match cfg.stop_bits { @@ -145,7 +142,9 @@ impl Uart { }); l.water() .modify(|_, w| unsafe { w.txwater().bits(0).rxwater().bits(0) }); - Self { _inst: core::marker::PhantomData } + Self { + _inst: core::marker::PhantomData, + } } /// Enable RX interrupts. The caller must ensure an appropriate IRQ handler is installed. -- cgit From e75066820ad320495ca70570641c90d75247b19b Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Fri, 7 Nov 2025 10:07:33 -0800 Subject: cargo +nightly fmt Signed-off-by: Felipe Balbi --- src/adc.rs | 24 ++++++------------ src/clocks.rs | 3 +-- src/gpio.rs | 8 +++--- src/interrupt.rs | 6 ++--- src/lib.rs | 12 ++++----- src/lpuart/buffered.rs | 19 +++------------ src/lpuart/mod.rs | 19 +++++---------- src/ostimer.rs | 66 +++++++++++++++++--------------------------------- src/pins.rs | 5 +--- src/rtc.rs | 3 ++- src/uart.rs | 13 +++++----- 11 files changed, 61 insertions(+), 117 deletions(-) (limited to 'src') diff --git a/src/adc.rs b/src/adc.rs index 5625330e9..d456971f7 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -1,7 +1,7 @@ //! ADC driver -use crate::pac; use core::sync::atomic::{AtomicBool, Ordering}; +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}; @@ -140,14 +140,10 @@ impl Adc { .variant(match config.trigger_priority_policy { TriggerPriorityPolicy::ConvPreemptSoftlyNotAutoResumed | TriggerPriorityPolicy::ConvPreemptSoftlyAutoRestarted - | TriggerPriorityPolicy::ConvPreemptSoftlyAutoResumed => { - Tprictrl::FinishCurrentOnPriority - } + | TriggerPriorityPolicy::ConvPreemptSoftlyAutoResumed => Tprictrl::FinishCurrentOnPriority, TriggerPriorityPolicy::ConvPreemptSubsequentlyNotAutoResumed | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoRestarted - | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoResumed => { - Tprictrl::FinishSequenceOnPriority - } + | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoResumed => Tprictrl::FinishSequenceOnPriority, _ => Tprictrl::AbortCurrentOnPriority, }) .tres() @@ -176,12 +172,8 @@ impl Adc { }); if config.enable_conv_pause { - adc.pause().modify(|_, w| unsafe { - w.pauseen() - .enabled() - .pausedly() - .bits(config.conv_pause_delay) - }); + adc.pause() + .modify(|_, w| unsafe { w.pauseen().enabled().pausedly().bits(config.conv_pause_delay) }); } else { adc.pause().write(|w| unsafe { w.bits(0) }); } @@ -247,8 +239,7 @@ impl Adc { pub fn do_auto_calibration(&self) { let adc = unsafe { &*I::ptr() }; - adc.ctrl() - .modify(|_, w| w.cal_req().calibration_request_pending()); + adc.ctrl().modify(|_, w| w.cal_req().calibration_request_pending()); while adc.gcc0().read().rdy().is_gain_cal_not_valid() {} @@ -260,8 +251,7 @@ impl Adc { 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().write(|w| unsafe { w.bits(self.get_gain_conv_result(gcra)) }); adc.gcr0().modify(|_, w| w.rdy().set_bit()); diff --git a/src/clocks.rs b/src/clocks.rs index 95d7ad567..65a17cef6 100644 --- a/src/clocks.rs +++ b/src/clocks.rs @@ -76,8 +76,7 @@ pub unsafe fn enable_ostimer0(peripherals: &pac::Peripherals) { pub unsafe fn select_uart2_clock(peripherals: &pac::Peripherals) { // Use FRO_LF_DIV (already running) MUX=0 DIV=0 let mrcc = &peripherals.mrcc0; - mrcc.mrcc_lpuart2_clksel() - .write(|w| w.mux().clkroot_func_0()); + mrcc.mrcc_lpuart2_clksel().write(|w| w.mux().clkroot_func_0()); mrcc.mrcc_lpuart2_clkdiv().write(|w| unsafe { w.bits(0) }); } diff --git a/src/gpio.rs b/src/gpio.rs index faeefd333..1e7214b28 100644 --- a/src/gpio.rs +++ b/src/gpio.rs @@ -66,7 +66,7 @@ pub trait PinId { } pub mod pins { - use super::{AnyPin, PinId, pac}; + use super::{pac, AnyPin, PinId}; macro_rules! define_pin { ($Name:ident, $port:literal, $pin:literal, $GpioBlk:ident) => { @@ -130,15 +130,13 @@ impl<'d> Flex<'d> { pub fn set_as_input(&mut self) { let mask = self.mask(); let gpio = self.gpio(); - gpio.pddr() - .modify(|r, w| unsafe { w.bits(r.bits() & !mask) }); + gpio.pddr().modify(|r, w| unsafe { w.bits(r.bits() & !mask) }); } pub fn set_as_output(&mut self) { let mask = self.mask(); let gpio = self.gpio(); - gpio.pddr() - .modify(|r, w| unsafe { w.bits(r.bits() | mask) }); + gpio.pddr().modify(|r, w| unsafe { w.bits(r.bits() | mask) }); } pub fn set_high(&mut self) { diff --git a/src/interrupt.rs b/src/interrupt.rs index d91e6479a..09d7acbef 100644 --- a/src/interrupt.rs +++ b/src/interrupt.rs @@ -6,11 +6,11 @@ mod generated { embassy_hal_internal::interrupt_mod!(OS_EVENT, LPUART2, RTC, ADC1); } -pub use generated::interrupt::Priority; -pub use generated::interrupt::typelevel; +use core::sync::atomic::{AtomicU16, AtomicU32, Ordering}; + +pub use generated::interrupt::{typelevel, Priority}; use crate::pac::Interrupt; -use core::sync::atomic::{AtomicU16, AtomicU32, Ordering}; /// Trait for configuring and controlling interrupts. /// diff --git a/src/lib.rs b/src/lib.rs index 518fe01d2..fe27aadba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,17 +30,17 @@ pub fn pac() -> &'static pac::Peripherals { } } -#[cfg(feature = "unstable-pac")] -pub use mcxa_pac as pac; -#[cfg(not(feature = "unstable-pac"))] -pub(crate) use mcxa_pac as pac; - // 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, pins::*}; +pub use gpio::pins::*; +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; pub use ostimer::Ostimer0 as Ostimer0Token; pub use rtc::Rtc0 as Rtc0Token; pub use uart::Lpuart2 as Uart2Token; diff --git a/src/lpuart/buffered.rs b/src/lpuart/buffered.rs index e2382e86d..0413fed8e 100644 --- a/src/lpuart/buffered.rs +++ b/src/lpuart/buffered.rs @@ -3,8 +3,8 @@ use core::marker::PhantomData; use core::sync::atomic::{AtomicBool, Ordering}; use core::task::Poll; -use embassy_hal_internal::Peri; use embassy_hal_internal::atomic_ring_buffer::RingBuffer; +use embassy_hal_internal::Peri; use embassy_sync::waitqueue::AtomicWaker; use super::*; @@ -87,15 +87,7 @@ impl<'a> BufferedLpuart<'a> { let state = T::buffered_state(); // Initialize the peripheral - Self::init::( - Some(&tx_pin), - Some(&rx_pin), - None, - None, - tx_buffer, - rx_buffer, - config, - )?; + Self::init::(Some(&tx_pin), Some(&rx_pin), None, None, tx_buffer, rx_buffer, config)?; Ok(Self { tx: BufferedLpuartTx { @@ -523,9 +515,7 @@ pub struct BufferedInterruptHandler { _phantom: PhantomData, } -impl crate::interrupt::typelevel::Handler - for BufferedInterruptHandler -{ +impl crate::interrupt::typelevel::Handler for BufferedInterruptHandler { unsafe fn on_interrupt() { let regs = T::info().regs; let state = T::buffered_state(); @@ -616,8 +606,7 @@ impl crate::interrupt::typelevel::Handler // 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()); + regs.ctrl().modify(|_, w| w.tie().disabled().tcie().enabled()); }); } } diff --git a/src/lpuart/mod.rs b/src/lpuart/mod.rs index 99f4a4a66..bed10bdb0 100644 --- a/src/lpuart/mod.rs +++ b/src/lpuart/mod.rs @@ -1,15 +1,13 @@ -use crate::interrupt; use core::marker::PhantomData; + use embassy_hal_internal::{Peri, PeripheralType}; use paste::paste; -use crate::pac; use crate::pac::lpuart0::baud::Sbns as StopBits; -use crate::pac::lpuart0::ctrl::{ - Idlecfg as IdleConfig, Ilt as IdleType, M as DataBits, Pt as Parity, -}; +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}; pub mod buffered; @@ -261,8 +259,7 @@ pub fn configure_baudrate(regs: Regs, baudrate_bps: u32, clock: Clock) -> Result /// 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)); + 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()); @@ -314,8 +311,7 @@ pub fn configure_fifo(regs: Regs, config: &Config) { }); // Enable TX/RX FIFOs - regs.fifo() - .modify(|_, w| w.txfe().enabled().rxfe().enabled()); + regs.fifo().modify(|_, w| w.txfe().enabled().rxfe().enabled()); // Flush FIFOs regs.fifo() @@ -818,10 +814,7 @@ impl<'a> LpuartTx<'a, Blocking> { } fn write_byte_internal(&mut self, byte: u8) -> Result<()> { - self.info - .regs - .data() - .modify(|_, w| unsafe { w.bits(u32::from(byte)) }); + self.info.regs.data().modify(|_, w| unsafe { w.bits(u32::from(byte)) }); Ok(()) } diff --git a/src/ostimer.rs b/src/ostimer.rs index 6a4188db0..a4cab6970 100644 --- a/src/ostimer.rs +++ b/src/ostimer.rs @@ -27,9 +27,10 @@ //! - Immediate wake for timestamps that would cause rollover issues #![allow(dead_code)] +use core::sync::atomic::{AtomicBool, Ordering}; + use crate::interrupt::InterruptExt; use crate::pac; -use core::sync::atomic::{AtomicBool, Ordering}; // PAC defines the shared RegisterBlock under `ostimer0`. type Regs = pac::ostimer0::RegisterBlock; @@ -129,18 +130,12 @@ pub(super) fn wait_for_match_write_complete(r: &Regs) -> bool { 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() - }); + 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) }); + 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); } } @@ -222,10 +217,7 @@ impl<'d, I: Instance> Ostimer<'d, I> { /// 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: impl Instance, cfg: Config, _p: &'d crate::pac::Peripherals) -> Self { - assert!( - cfg.clock_frequency_hz > 0, - "OSTIMER frequency must be greater than 0" - ); + assert!(cfg.clock_frequency_hz > 0, "OSTIMER frequency must be greater than 0"); if cfg.init_match_max { let r: &Regs = unsafe { &*I::ptr() }; @@ -268,12 +260,8 @@ impl<'d, I: Instance> Ostimer<'d, I> { // 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() - }); + r.osevent_ctrl() + .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); unsafe { crate::reset::assert::(peripherals); @@ -287,9 +275,7 @@ impl<'d, I: Instance> Ostimer<'d, I> { crate::reset::release::(peripherals); } - while !::is_released( - &peripherals.mrcc0, - ) { + while !::is_released(&peripherals.mrcc0) { cortex_m::asm::nop(); } @@ -363,12 +349,8 @@ impl<'d, I: Instance> Ostimer<'d, I> { critical_section::with(|_| { // Disable interrupt and clear flag - r.osevent_ctrl().write(|w| { - w.ostimer_intrflag() - .clear_bit_by_one() - .ostimer_intena() - .clear_bit() - }); + 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); @@ -526,15 +508,17 @@ fn gray_to_bin(gray: u64) -> u64 { } pub mod time_driver { - use super::{ - ALARM_ACTIVE, ALARM_CALLBACK, ALARM_FLAG, ALARM_TARGET_TIME, EVTIMER_HI_MASK, - EVTIMER_HI_SHIFT, LOW_32_BIT_MASK, Regs, bin_to_gray, now_ticks_read, - }; - use crate::pac; 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::pac; pub struct Driver; static TIMER_WAKER: AtomicWaker = AtomicWaker::new(); @@ -569,12 +553,8 @@ pub mod time_driver { critical_section::with(|_| { // Mask INTENA and clear flag - r.osevent_ctrl().write(|w| { - w.ostimer_intrflag() - .clear_bit_by_one() - .ostimer_intena() - .clear_bit() - }); + 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(); @@ -690,9 +670,7 @@ pub mod time_driver { /// Type-level handler to be used with bind_interrupts! for OS_EVENT. pub struct OsEventHandler; - impl crate::interrupt::typelevel::Handler - for OsEventHandler - { + impl crate::interrupt::typelevel::Handler for OsEventHandler { unsafe fn on_interrupt() { on_interrupt(); } diff --git a/src/pins.rs b/src/pins.rs index 1d92f9fef..f802568f3 100644 --- a/src/pins.rs +++ b/src/pins.rs @@ -84,10 +84,7 @@ pub unsafe fn set_pin_mux(port: u8, pin: u8, mux: u8) { }; if pin > max_pin { - panic!( - "Invalid pin {} for PORT{}, max pin is {}", - pin, port, max_pin - ); + panic!("Invalid pin {} for PORT{}, max pin is {}", pin, port, max_pin); } // Get the base address for the port diff --git a/src/rtc.rs b/src/rtc.rs index 5e3dfe6c1..d62da1f0a 100644 --- a/src/rtc.rs +++ b/src/rtc.rs @@ -1,7 +1,8 @@ //! RTC DateTime driver. +use core::sync::atomic::{AtomicBool, Ordering}; + use crate::pac; use crate::pac::rtc0::cr::Um; -use core::sync::atomic::{AtomicBool, Ordering}; type Regs = pac::rtc0::RegisterBlock; diff --git a/src/uart.rs b/src/uart.rs index 65dd91492..3209a318d 100644 --- a/src/uart.rs +++ b/src/uart.rs @@ -1,11 +1,13 @@ //! Minimal polling UART2 bring-up replicating MCUXpresso hello_world ordering. //! WARNING: This is a narrow implementation only for debug console (115200 8N1). -use crate::pac; use core::cell::RefCell; + use cortex_m::interrupt::Mutex; use embassy_sync::signal::Signal; +use crate::pac; + // svd2rust defines the shared LPUART RegisterBlock under lpuart0; all instances reuse it. type Regs = pac::lpuart0::RegisterBlock; @@ -108,7 +110,7 @@ impl Uart { cortex_m::asm::delay(3); // Short delay for reset to take effect l.global().write(|w| w.rst().no_effect()); cortex_m::asm::delay(10); // Allow peripheral to stabilize after reset - // 2) BAUD + // 2) BAUD let (osr, sbr) = compute_osr_sbr(cfg.src_hz, cfg.baud); l.baud().modify(|_, w| { let w = match cfg.stop_bits { @@ -234,8 +236,7 @@ impl RingBuffer { // Global RX buffer shared between interrupt handler and UART instance static RX_BUFFER: Mutex> = Mutex::new(RefCell::new(RingBuffer::new())); -static RX_SIGNAL: Signal = - Signal::new(); +static RX_SIGNAL: Signal = Signal::new(); // Debug counter for interrupt handler calls static mut INTERRUPT_COUNT: u32 = 0; @@ -279,9 +280,7 @@ impl Uart { /// Type-level handler for LPUART2 interrupts, compatible with bind_interrupts!. pub struct UartInterruptHandler; -impl crate::interrupt::typelevel::Handler - for UartInterruptHandler -{ +impl crate::interrupt::typelevel::Handler for UartInterruptHandler { unsafe fn on_interrupt() { INTERRUPT_COUNT += 1; -- cgit